npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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-50 regardless 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)}
    />
  );
}