tccd-ui
v0.2.7
Published
Reusable React + Tailwind components
Readme
TCCD UI
A small package of all our UI elements for bootstrapping our applications! No need to recreate everything from scrath
Components
Textboxes
Buttons
Pages/Screens
Misc
Shared Types
ButtonTypes
"primary" | "secondary" | "tertiary" | "danger" | "ghost" | "basic";Controls the visual style of the button, including background, text, border, and hover states.
| Type | Description | Default Appearance | Hover Behavior |
| ----------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| primary | The main call-to-action style. Use for the most important action on a page or in a section. | Filled with bg-primary, text-text, and a matching border-primary. | Background becomes transparent (bg-background), border and text switch to primary. |
| secondary | An alternative action style, used alongside a primary button without competing for attention. | Filled with bg-secondary, text-text, and a matching border-secondary. | Background becomes transparent, border and text switch to secondary. |
| tertiary | A lower-emphasis filled style, useful for less prominent actions. | Filled with bg-contrast, text-text, and a matching border-contrast. | Background becomes transparent, border switches to tertiary, text becomes black. |
| danger | Indicates a destructive or irreversible action (e.g. delete, remove). | Transparent background with border-primary and text-primary. | Fills with bg-primary, text switches to text-text. |
| ghost | A minimal style with no fill or border color emphasis, used for low-priority actions. | text-secondary with a border-secondary, no background fill. | Background fills with secondary at 30% opacity. |
| basic | The most neutral style, typically used for generic or utility actions. | border-contrast, no explicit text or background color set. | Background fills with background-contrast at 30% opacity. |
Note: All button types share a common disabled state, applying
opacity-50regardless of type.
ButtonWidths
"auto" | "small" | "medium" | "large" | "xl" | "full" | "fit";Controls the width of the button.
| Width | CSS Class | Description |
| -------- | --------- | ---------------------------------------------------------------------------------------------- |
| auto | w-auto | Width is determined by the browser's default sizing behavior. |
| small | w-24 | Fixed width, suited for compact buttons (e.g. icon-only or short labels like "OK"). |
| medium | w-32 | Fixed width, suited for typical short-to-medium length labels. |
| large | w-48 | Fixed width, suited for longer labels or buttons needing more visual weight. |
| xl | w-64 | Fixed width, suited for prominent buttons or longer text content. |
| full | w-full | Expands to fill the full width of its parent container. |
| fit | w-fit | Width shrinks to fit the button's content exactly, with no extra padding beyond what's needed. |
DropdownOption
| Prop | Type | Required | Description |
| ---------- | --------- | -------- | ---------------------------------- |
| value | string | Yes | The option's underlying value. |
| label | string | Yes | The option's display text. |
| disabled | boolean | No | Disables selection of this option. |
InfoScreenProps
| Prop | Type | Required | Description |
| --------- | -------- | -------- | ------------------------- |
| title | string | Yes | Screen title. |
| message | string | Yes | Screen message/body text. |
MediaItem
| Prop | Type | Required | Description |
| ------- | ------------------ | -------- | ------------------------------------- |
| id | string \| number | Yes | Unique identifier for the media item. |
| type | string | Yes | The media's type. |
| src | string | Yes | Media source URL. |
| thumb | string | No | Thumbnail source URL. |
| alt | string | No | Alt text. |
MediaItem is used by FullScreenViewer (documented under Misc) to render a set of media items in a fullscreen overlay.
DropdownMenuItem
| Prop | Type | Required | Description |
| ----------- | -------------------------------------------------------- | -------- | -------------------------------------------- |
| title | string | Yes | Item label text. |
| icon | React.ComponentType<{ size?: number; color?: string }> | No | Icon component rendered alongside the title. |
| iconColor | string | No | Color passed to icon. |
| action | string | No | Action identifier for this item. |
| onClick | () => void | No | Called when the item is clicked. |
DropdownMenuProps (used by DropdownPopup)
{
isOpen: boolean;
onClose: () => void;
items: DropdownMenuItem[];
position?: "top" | "bottom" | "left" | "right";
width?: string;
triggerRef?: React.RefObject<HTMLElement | null>;
alignToTrigger?: boolean;
}| Prop | Type | Required | Description |
| ---------------- | ---------------------------------------- | -------- | ---------------------------------------------------------------- |
| isOpen | boolean | Yes | Controls visibility of the popup. |
| onClose | () => void | Yes | Called when the popup requests to close. |
| items | DropdownMenuItem[] | Yes | List of clickable items shown in the popup. |
| position | "top" \| "bottom" \| "left" \| "right" | No | Side of the trigger the popup opens on. Defaults to "bottom". |
| width | string | No | Tailwind width class applied to the popup. Defaults to "w-48". |
| triggerRef | React.RefObject | No | Ref to the element the popup is positioned/triggered from. |
| alignToTrigger | boolean | No | Aligns the popup to the trigger element. Defaults to false. |
Components
Textboxes
InputField
A labeled single-line text input.
| Prop | Type | Required | Description |
| ---------------- | -------------------------------- | -------- | ------------------------------------------ |
| label | string | Yes | Field label. |
| labelClassName | string | No | Additional CSS classes for the label. |
| id | string | Yes | Element id. |
| value | string | Yes | Current input value. |
| placeholder | string | Yes | Placeholder text. |
| onChange | (e: React.ChangeEvent) => void | Yes | Native change handler. |
| error | string | No | Error message to display. |
| errorClassName | string | No | Additional CSS classes for the error text. |
Example
import { InputField } from "tccd-ui";
import { useState } from "react";
function Example() {
const [name, setName] = useState("");
return (
<InputField
id="full-name"
label="Full name"
placeholder="Jane Doe"
value={name}
onChange={(e) => setName(e.target.value)}
/>
);
}PasswordField
A labeled password input.
| Prop | Type | Required | Description |
| ---------------- | -------------------------------- | -------- | ------------------------------------------ |
| id | string | Yes | Element id. |
| value | string | Yes | Current value. |
| label | string | Yes | Field label. |
| labelClassName | string | No | Additional CSS classes for the label. |
| onChange | (e: React.ChangeEvent) => void | Yes | Native change handler. |
| error | string | No | Error message to display. |
| errorClassName | string | No | Additional CSS classes for the error text. |
Example
import { PasswordField } from "tccd-ui";
import { useState } from "react";
function Example() {
const [password, setPassword] = useState("");
return (
<PasswordField
id="password"
label="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
);
}NumberField
A labeled numeric input.
| Prop | Type | Required | Description |
| ---------------- | ----------------------------------- | -------- | ------------------------------------------ |
| id | string | Yes | Element id. |
| label | string | Yes | Field label. |
| labelClassName | string | No | Additional CSS classes for the label. |
| value | number \| string | Yes | Current value. |
| onChange | (value: number \| string) => void | Yes | Called with the new value directly. |
| min | number | No | Minimum allowed value. |
| max | number | No | Maximum allowed value. |
| step | number | No | Increment/decrement step. |
| placeholder | string | No | Placeholder text. |
| disabled | boolean | No | Disables the field. |
| error | string | No | Error message to display. |
| errorClassName | string | No | Additional CSS classes for the error text. |
| className | string | No | Additional CSS classes. |
This component was reworked in this update: onChange used to hand back the raw ChangeEvent, and consumers read e.target.value themselves; it now hands back the new value directly. value/onChange are also now typed number | string instead of always string, and placeholder is now optional instead of required. min, max, step, disabled, and className are new. The previous maxLength prop (which was a no-op in the old implementation) has been removed entirely.
onChange is called with a number whenever the field holds a value, and with an empty string ("") when the field is cleared. If min/max are set, a typed value is clamped to that range before onChange fires — so onChange never reports a value outside [min, max], even momentarily.
Example
import { NumberField } from "tccd-ui";
import { useState } from "react";
function Example() {
const [quantity, setQuantity] = useState<number | string>("");
return (
<NumberField
id="quantity"
label="Quantity"
placeholder="0"
value={quantity}
min={0}
max={100}
step={1}
onChange={(value) => setQuantity(value)}
/>
);
}SearchField
A search input.
| Prop | Type | Required | Description |
| ------------- | ------------------------- | -------- | --------------------------------- |
| value | string | Yes | Current search value. |
| onChange | (value: string) => void | Yes | Called with the new search value. |
| placeholder | string | No | Placeholder text. |
| className | string | No | Additional CSS classes. |
Example
import { SearchField } from "tccd-ui";
import { useState } from "react";
function Example() {
const [query, setQuery] = useState("");
return (
<SearchField
placeholder="Search products..."
value={query}
onChange={setQuery}
/>
);
}TextAreaField
A labeled multi-line text input.
| Prop | Type | Required | Description |
| ---------------- | -------------------------------- | -------- | ------------------------------------------ |
| label | string | Yes | Field label. |
| labelClassName | string | No | Additional CSS classes for the label. |
| id | string | Yes | Element id. |
| value | string | Yes | Current value. |
| placeholder | string | Yes | Placeholder text. |
| maxLength | number | No | Maximum character length. |
| onChange | (e: React.ChangeEvent) => void | Yes | Native change handler. |
| error | string | No | Error message to display. |
| errorClassName | string | No | Additional CSS classes for the error text. |
Example
import { TextAreaField } from "tccd-ui";
import { useState } from "react";
function Example() {
const [bio, setBio] = useState("");
return (
<TextAreaField
id="bio"
label="Bio"
placeholder="Tell us about yourself"
value={bio}
maxLength={280}
onChange={(e) => setBio(e.target.value)}
/>
);
}HtmlEditorField
A small HTML source editor with an Edit / Preview toggle. Meant for composing raw HTML that gets sent to a backend as-is (e.g. an email htmlBody) — the "Edit" tab is a plain textarea for the HTML source, and the "Preview" tab renders that source inside a sandboxed <iframe sandbox="allow-same-origin"> (via srcDoc) so scripts in the content never execute, even while you're actively composing it.
| Prop | Type | Required | Description |
| ------------------ | ----------------------------- | -------- | ------------------------------------------------------------------|
| id | string | Yes | Element id for the textarea. |
| label | string | Yes | Field label. |
| labelClassName | string | No | Additional CSS classes for the label. |
| value | string | Yes | Current HTML source. |
| onChange | (newValue: string) => void | Yes | Called with the new HTML source directly. |
| placeholder | string | No | Placeholder shown in the Edit tab. |
| error | string | No | Error message to display. |
| errorClassName | string | No | Additional CSS classes for the error text. |
| disabled | boolean | No | Disables editing and switching tabs. Defaults to false. |
| maxLength | number | No | Maximum character length, with a counter shown in the Edit tab. |
| rows | number | No | Textarea rows, also used to size the Preview pane. Defaults to 10. |
| defaultMode | "edit" \| "preview" | No | Which tab is shown first. Defaults to "edit". |
| className | string | No | Additional CSS classes for the textarea. |
| previewClassName | string | No | Additional CSS classes for the preview container. |
Example
import { HtmlEditorField } from "tccd-ui";
import { useState } from "react";
function Example() {
const [htmlBody, setHtmlBody] = useState("<p>Hello!</p>");
return (
<HtmlEditorField
id="html-body"
label="Email Body"
value={htmlBody}
onChange={setHtmlBody}
/>
);
}
// then, when sending:
// fetch("/api/emails", {
// method: "POST",
// headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
// body: JSON.stringify({ from, to, subject, htmlBody }),
// });The Preview tab shows "Nothing to preview yet" when value is empty. The sandboxed iframe intentionally omits allow-scripts, so any <script> tags in the HTML are inert in the preview — this mirrors how the mock email inbox itself renders stored HTML emails safely.
HtmlEmailViewer
A read-only viewer for a received email's htmlBody. There is no editing — this is HtmlEditorField's counterpart for the reading side (e.g. the mock-email inbox's email preview), not the composing side. Content renders the same way HtmlEditorField's Preview tab does: inside a sandboxed <iframe sandbox="allow-same-origin"> (via srcDoc), so scripts in the HTML never execute.
When htmlBody is empty or not provided, it shows a "No HTML content available" message instead of a blank box.
| Prop | Type | Required | Description |
| ----------------- | --------- | -------- | -----------------------------------------------------------------------------|
| id | string | No | Element id for the root container. |
| label | string | No | Caption shown above the content, e.g. "Email Body". Omit to render without one. |
| labelClassName | string | No | Additional CSS classes for the label. |
| htmlBody | string | No | The HTML content to render. |
| height | number | No | Height in pixels of the content box. Defaults to 240. |
| className | string | No | Additional CSS classes for the content box. |
Example
import { HtmlEmailViewer } from "tccd-ui";
function EmailPreview({ email }: { email: { htmlBody?: string } }) {
return <HtmlEmailViewer label="Email Body" htmlBody={email.htmlBody} />;
}TextDisplayEdit
A value shown as plain text, with the option to switch it into an editable input in place.
| Prop | Type | Required | Description |
| ------------- | ---------------------------- | -------- | ------------------------------------------------------- |
| label | string | Yes | Field label. |
| value | string | Yes | Current value. |
| onChange | (newValue: string) => void | No | Called with the new value once edited. |
| disabled | boolean | No | Disables switching into edit mode. Defaults to false. |
| icon | React.ReactNode | No | Icon rendered to trigger/indicate edit mode. |
| placeholder | string | No | Placeholder text shown when the value is empty. |
Example
import { TextDisplayEdit } from "tccd-ui";
import { useState } from "react";
function Example() {
const [nickname, setNickname] = useState("Jane");
return (
<TextDisplayEdit
label="Nickname"
value={nickname}
onChange={setNickname}
placeholder="Add a nickname"
/>
);
}This is a lighter-weight component than the other textbox fields, so it doesn't carry as many props. Omitting onChange does not disable edit mode — the user can still switch into editing and type — it just means any edits made can't be persisted anywhere, since there's no handler to send them to.
Buttons
Button
Renders a clickable button with configurable style, width, and state.
| Prop | Type | Required | Description |
| ------------ | ----------------- | -------- | ---------------------------------------- |
| buttonText | string | No | Text label shown on the button. |
| buttonIcon | React.ReactNode | No | Icon rendered alongside/instead of text. |
| onClick | () => void | Yes | Click handler. |
| type | ButtonTypes | Yes | Visual style variant. |
| disabled | boolean | No | Disables the button. |
| loading | boolean | No | Shows a loading state. |
| width | ButtonWidths | No | Controls button width. |
| className | string | No | Additional CSS classes. |
Example
import { Button } from "tccd-ui";
import { ButtonTypes, ButtonWidths } from "tccd-ui";
function Example() {
return (
<Button
buttonText="Save changes"
type={ButtonTypes.PRIMARY}
width={ButtonWidths.MEDIUM}
onClick={() => console.log("saved")}
/>
);
}Checkbox
A labeled checkbox input.
| Prop | Type | Required | Description |
| ---------- | ------------ | -------- | ------------------------------------ |
| label | string | Yes | Text label next to the checkbox. |
| checked | boolean | Yes | Checked state. |
| onChange | () => void | Yes | Called when the checkbox is toggled. |
Example
import { Checkbox } from "tccd-ui";
import { useState } from "react";
function Example() {
const [checked, setChecked] = useState(false);
return (
<Checkbox
label="Accept terms and conditions"
checked={checked}
onChange={() => setChecked(!checked)}
/>
);
}Radiobutton
A single radio-style toggle control.
| Prop | Type | Required | Description |
| ---------- | ----------------------------- | -------- | ------------------------------------ |
| label | string | Yes | Text label next to the radio button. |
| checked | boolean | Yes | Checked state. |
| onChange | (newValue: boolean) => void | Yes | Called with the new checked state. |
Example
import { Radiobutton } from "tccd-ui";
import { useState } from "react";
function Example() {
const [selected, setSelected] = useState(false);
return (
<Radiobutton
label="Email me updates"
checked={selected}
onChange={(newValue) => setSelected(newValue)}
/>
);
}Pages/Screens
LoadingPage
An animated, full-screen loading indicator. Takes no props.
Example
import { LoadingPage } from "tccd-ui";
function Example() {
return <LoadingPage />;
}SuccessScreen
A full info screen for success states.
| Prop | Type | Required | Description |
| --------- | -------- | -------- | ------------------------- |
| title | string | Yes | Screen title. |
| message | string | Yes | Screen message/body text. |
Example
import { SuccessScreen } from "tccd-ui";
function Example() {
return (
<SuccessScreen
title="Payment successful"
message="Your order has been confirmed and is on its way."
/>
);
}ErrorScreen
A full info screen for error states, extending InfoScreenProps.
| Prop | Type | Required | Description |
| -------------------- | ----------------- | -------- | ------------------------------------------ |
| title | string | Yes | Screen title. |
| message | string | Yes | Screen message/body text. |
| showAdditionalInfo | boolean | No | Toggles display of additional info. |
| children | React.ReactNode | No | Additional content rendered in the screen. |
Example
import { ErrorScreen } from "tccd-ui";
function Example() {
return (
<ErrorScreen
title="Something went wrong"
message="We couldn't load your data. Please try again."
showAdditionalInfo
>
<p>Error code: 500</p>
</ErrorScreen>
);
}children and showAdditionalInfo are independent of one another: children renders whenever it's passed, regardless of showAdditionalInfo. showAdditionalInfo separately toggles a set of built-in, static troubleshooting tips (not derived from children or from anything else you pass in) suggesting steps the user can try before escalating the problem further.
InfoScreen
A generic full-screen informational message.
| Prop | Type | Required | Description |
| --------- | -------- | -------- | ------------------------- |
| title | string | Yes | Screen title. |
| message | string | Yes | Screen message/body text. |
Example
import { InfoScreen } from "tccd-ui";
function Example() {
return (
<InfoScreen
title="No results found"
message="Try adjusting your filters and search again."
/>
);
}Unauthorized
A full-screen state shown when the user lacks access privileges. Takes no props.
Example
import { Unauthorized } from "tccd-ui";
function Example() {
return <Unauthorized />;
}Misc
DatePicker
A labeled date input with optional min/max constraints and error display.
| Prop | Type | Required | Description |
| ---------------- | ------------------------ | -------- | ------------------------------------------ |
| label | string | No | Field label. |
| labelClassName | string | No | Additional CSS classes for the label. |
| id | string | No | Element id (for label association). |
| value | string | Yes | Current date value. |
| onChange | (date: string) => void | Yes | Called with the new date value. |
| error | string | No | Error message to display. |
| errorClassName | string | No | Additional CSS classes for the error text. |
| disabled | boolean | No | Disables the field. |
| minDate | string | No | Minimum selectable date. |
| maxDate | string | No | Maximum selectable date. |
value, minDate, and maxDate all use a date-only ISO 8601 string in YYYY-MM-DD format (e.g. "2026-01-01"), produced via date.toISOString().split("T")[0]. onChange receives the new date in that same format, or undefined if cleared.
Example
import { DatePicker } from "tccd-ui";
import { useState } from "react";
const formatDate = (date: Date | null) => {
if (!date) return "";
return date.toISOString().split("T")[0];
};
function Example() {
const [startDate, setStartDate] = useState<string | undefined>();
const [endDate, setEndDate] = useState<string | undefined>();
return (
<DatePicker
id="start-date"
label="Start Date"
value={formatDate(startDate ? new Date(startDate) : null)}
onChange={(date) => setStartDate(date || undefined)}
maxDate={endDate ? formatDate(new Date(endDate)) : undefined}
/>
);
}DateTimePicker
A labeled date-and-time input with optional min/max constraints and error display, working off Unix timestamps rather than date strings.
| Prop | Type | Required | Description |
| ---------------- | ----------------------------- | -------- | ------------------------------------------ |
| label | string | No | Field label. |
| labelClassName | string | No | Additional CSS classes for the label. |
| id | string | No | Element id. |
| value | number \| null \| undefined | Yes | Current value, as a timestamp. |
| onChange | (timestamp: number) => void | Yes | Called with the new timestamp. |
| error | string | No | Error message to display. |
| errorClassName | string | No | Additional CSS classes for the error text. |
| disabled | boolean | No | Disables the field. |
| minDate | number | No | Minimum selectable date, as a timestamp. |
| maxDate | number | No | Maximum selectable date, as a timestamp. |
Example
import { DateTimePicker } from "tccd-ui";
import { useState } from "react";
function Example() {
const [scheduledAt, setScheduledAt] = useState<number | null>(null);
return (
<DateTimePicker
id="scheduled-at"
label="Scheduled for"
value={scheduledAt}
onChange={(timestamp) => setScheduledAt(timestamp)}
minDate={Date.now()}
/>
);
}value, minDate, and maxDate are all in milliseconds (i.e. Date.now()/date.getTime()), not seconds.
TimePicker
A labeled time input with error display.
| Prop | Type | Required | Description |
| ---------------- | ------------------------ | -------- | ------------------------------------------------------- |
| label | string | No | Field label. |
| id | string | No | Element id. |
| value | string | Yes | Current time value. |
| onChange | (time: string) => void | Yes | Called with the new time value. |
| error | string | No | Error message to display. |
| errorClassName | string | No | Additional CSS classes for the error text. |
| disabled | boolean | No | Disables the field. |
| use24Hour | boolean | No | Displays the time in 24-hour format instead of 12-hour. |
Example
import { TimePicker } from "tccd-ui";
import { useState } from "react";
function Example() {
const [time, setTime] = useState("");
return (
<TimePicker
id="reminder-time"
label="Reminder time"
value={time}
onChange={setTime}
use24Hour
/>
);
}DropdownMenu
A select/dropdown built from a list of DropdownOptions.
| Prop | Type | Required | Description |
| ---------------- | ------------------------- | -------- | ------------------------------------------- |
| label | string | No | Field label. |
| labelClassName | string | No | Additional CSS classes for the label. |
| placeholder | string | No | Placeholder text when no value is selected. |
| options | DropdownOption[] | Yes | List of selectable options. |
| value | string | No | Currently selected option's value. |
| onChange | (value: string) => void | Yes | Called with the newly selected value. |
| error | string | No | Error message to display. |
| errorClassName | string | No | Additional CSS classes for the error text. |
| disabled | boolean | No | Disables the dropdown. |
| className | string | No | Additional CSS classes. |
| id | string | No | Element id. |
Example
import { DropdownMenu } from "tccd-ui";
import { useState } from "react";
const countryOptions = [
{ value: "eg", label: "Egypt" },
{ value: "us", label: "United States" },
{ value: "de", label: "Germany", disabled: true },
];
function Example() {
const [country, setCountry] = useState("");
return (
<DropdownMenu
id="country"
label="Country"
placeholder="Select a country"
options={countryOptions}
value={country}
onChange={setCountry}
/>
);
}DropdownPopup
A small popup menu of clickable items/actions, shown near a trigger element and dismissed via onClose.
Built from the shared DropdownMenuProps type (see Shared Types above) and a list of DropdownMenuItems.
| Prop | Type | Required | Description |
| ---------------- | ---------------------------------------- | -------- | ---------------------------------------------------------------- |
| isOpen | boolean | Yes | Controls visibility of the popup. |
| onClose | () => void | Yes | Called when the popup requests to close. |
| items | DropdownMenuItem[] | Yes | List of clickable items shown in the popup. |
| position | "top" \| "bottom" \| "left" \| "right" | No | Side of the trigger the popup opens on. Defaults to "bottom". |
| width | string | No | Tailwind width class applied to the popup. Defaults to "w-48". |
| triggerRef | React.RefObject | No | Ref to the element the popup is positioned/triggered from. |
| alignToTrigger | boolean | No | Aligns the popup to the trigger element. Defaults to false. |
Example
import { DropdownPopup } from "tccd-ui";
import { useRef, useState } from "react";
import { Trash2, Pencil } from "lucide-react";
function Example() {
const [isOpen, setIsOpen] = useState(false);
const triggerRef = useRef<HTMLButtonElement>(null);
return (
<>
<button ref={triggerRef} onClick={() => setIsOpen(true)}>
Actions
</button>
<DropdownPopup
isOpen={isOpen}
onClose={() => setIsOpen(false)}
triggerRef={triggerRef}
alignToTrigger
items={[
{ title: "Edit", icon: Pencil, onClick: () => console.log("edit") },
{
title: "Delete",
icon: Trash2,
onClick: () => console.log("delete"),
},
]}
/>
</>
);
}Logo
A hardcoded brand logo rendered as inline SVG. It accepts no explicit typed props — the component spreads any props you pass directly onto the underlying <svg> element ((props) => <svg {...props}>...</svg>), so it can be resized/restyled via standard SVG/HTML attributes like className, width, height, or style.
Example
import { Logo } from "tccd-ui";
function Example() {
return <Logo className="h-8 w-auto" />;
}
function LargeExample() {
return <Logo width={280} height={105} />;
}Modal
A dialog overlay component.
| Prop | Type | Required | Description |
| ----------- | ------------ | -------- | ---------------------------------------- |
| title | string | Yes | Modal title. |
| isOpen | boolean | Yes | Controls visibility. |
| onClose | () => void | Yes | Called when the modal requests to close. |
| children | ReactNode | Yes | Modal body content. |
| className | string | No | Additional CSS classes. |
Example
import { Modal } from "tccd-ui";
import { useState } from "react";
function Example() {
const [isOpen, setIsOpen] = useState(false);
return (
<Modal
title="Confirm deletion"
isOpen={isOpen}
onClose={() => setIsOpen(false)}
>
<p>Are you sure you want to delete this item?</p>
</Modal>
);
}LazyImageLoader
A media component that lazy-loads its source. Accepts either an image or a video.
| Prop | Type | Required | Description |
| ----------------- | ------------------ | -------- | ----------------------------------------------------------------- |
| src | string | Yes | Image or video source URL. |
| alt | string | Yes | Alt text. |
| className | string | No | Additional CSS classes, applied to the wrapping |
|. |
| objectClassName | string | No | Additional CSS classes applied to the image/video element itself. |
| width | string \| number | No | Media width. |
| height | string \| number | No | Media height. |
Example
import { LazyImageLoader } from "tccd-ui";
function Example() {
return (
<LazyImageLoader
src="/images/hero-banner.jpg"
alt="Product hero banner"
width={600}
height={400}
objectClassName="rounded-lg"
/>
);
}src now "accepts images or videos" per the interface comment, with no separate type/isVideo prop — the component determines which one to render by checking src's file extension.
FullScreenViewer
Views a list of MediaItems fullscreen, one at a time.
| Prop | Type | Required | Description |
| ---------- | ----------------- | -------- | ------------------------------------------------------------ |
| isOpen | boolean | Yes | Controls visibility of the viewer. |
| items | MediaItem[] | Yes | The list of media items to view. |
| index | number | Yes | Index of the currently displayed item within items. |
| setIndex | React.Dispatch> | Yes | State setter used by the viewer to change the current index. |
| onClose | () => void | Yes | Called when the viewer requests to close. |
Unlike most other components, index isn't paired with an onChange-style callback — instead the viewer is handed the raw setIndex state setter directly, and calls it itself (e.g. on next/previous navigation) to update the parent's state.
Example
import { FullScreenViewer } from "tccd-ui";
import { useState } from "react";
const photos = [
{ id: 1, type: "image", src: "/images/photo-1.jpg" },
{ id: 2, type: "image", src: "/images/photo-2.jpg" },
];
function Example() {
const [isOpen, setIsOpen] = useState(false);
const [index, setIndex] = useState(0);
return (
<FullScreenViewer
isOpen={isOpen}
items={photos}
index={index}
setIndex={setIndex}
onClose={() => setIsOpen(false)}
/>
);
}