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

tnuilib

v1.0.9

Published

React Component Library With Animations

Readme

tnuilib Library

tnuilib is a component library for loading components with animations and others.

Installation

Use the package manager npm or pnpm to install tnuilib.

npm install tnuilib

or

pnpm add tnuilib

Usage

Pending Components

Bouncing Dots

import { BouncingDots } from "tnuilib";

function Home() {
  return <BouncingDots duration={1} gap={4} dotSize="22px" color="#fff" />;
}

conditional rendering

import { BouncingDots } from "tnuilib";

function Home() {
  return (
    isPending && (
      <BouncingDots duration={1} gap={4} dotSize="22px" color="#fff" />
    )
  );
}

Blinking Dots

<BlinkingDots duration={1} gap={6} dotSize="22px" color="#fff" />

Border Spinner


<BorderSpinner transparentSides="t" size="30px" color="#0070f3" borderWidth="2px" duration={1.5} />

#### In tailwindcss, normal component
<div className="h-10 w-10 border-2 border-y-black border-x-transparent animate-spin rounded-full" />
<div className="h-8 w-8 border-2 border-y-transparent border-r-transparent rounded-full animate-spin"

Circle Spinner with Border


<CircleSpinner color="#0070f3" borderWidth="20px"
borderCircle={{circle: true, circleColor: "#0070f3", circleWidth: "5px"}} duration={1.5} />

#### In tailwindcss normal component
<div className="border-20 border-y-transparent animate-spin rounded-full" />

Circle Spinner without Border

<CircleSpinner hideTwoSide={true} color="#0070f3" borderWidth="18px" duration={2} />

#### In tailwindcss
<div className="border-20 border-y-transparent rounded-full animate-spin" />

Dropdown Component

Simple Dropdown

import { HomeIcon, TvIcon } from "@heroicons/react/16/solid";
import { useState } from "react";
import { Dropdown } from "tnuilib";

const dropdownList = [
  { label: "Apple", value: "apple", icon: <TvIcon className="w-4 h-4" /> },
  {
    label: "Banana",
    value: "banana",
    icon: "🍌",
  },
];
export default function Page() {
  const [fruit, setFruit] = useState(null);

  return (
    <>
      <Dropdown
        items={dropdownList}
        dropDownIcon={<HomeIcon className="w-4 h-4" />}
        btnStyle={{
          width: 300,
          btnBackground: "lightgray",
          btnColor: "darkblue",
          paddingX: 20,
          paddingY: 10,
        }}
        onSelect={setFruit}
        menuStyle={{ menuBackground: "darkcyan", hideScrollBar: true }}
        itemStyle={{ itemColor: "white", itemBackground: "374859" }}
      />

      {fruit && <>{fruit}</>}
    </>
  );
}

Search Bar

Simple search bar

Use directly inside a client component

"use client";

import { useState } from "react";
import { SearchBar } from "tnuilib";

export default function SearchPage() {
  const [value, setValue] = useState("");
  return (
    <div className="flex flex-col p-4">
      {" "}
      {/* outer div for padding and width */}
      <SearchBar
        size="lg"
        searchValue={value}
        setSearchValue={setValue}
        bgColor="gray"
        placeholder="Search items..."
        color="white"
        isPending={true} // set pending status for search
      />
      {value && value} {/* output (for test)*/}
    </div>
  );
}

Tabs

Tabs navigation

Able to use only in client component

<Tabs defaultValue="home" activeColor="#faac15" underline>
  {" "}
  // underline is optional
  {/* Tabs list styling */}
  <TabsList className="flex gap-4 border-b border-gray-200">
    {" "}
    // tabs list // triggers are buttons to change tabs
    <TabsTrigger
      value="home"
      // You can also add hover, font styles, etc.
      className="px-4 py-2 transition-colors"
    >
      Home
    </TabsTrigger>
    <TabsTrigger value="products" className="px-4 py-2 transition-colors">
      Products
    </TabsTrigger>
    <TabsTrigger value="contact" className="px-4 py-2 transition-colors">
      Contact
    </TabsTrigger>
  </TabsList>
  {/* Set contents according to Tab Name */}
  <TabsContent value="home" className="p-4 bg-white shadow">
    <p>Home Content Here</p>
  </TabsContent>
  <TabsContent value="products" className="p-4 bg-white shadow">
    <p>Products Content Here</p>
  </TabsContent>
  <TabsContent value="contact" className="p-4 bg-white shadow">
    <p>Contact Content Here</p>
  </TabsContent>
</Tabs>

Breadcrumbs

Breadcrumbs navigation

Create breadcrumbs arrays of title, href and icon Then, use the breadcrumbs in the BreacCrumbs component

import { Breadcrumbs } from "tnuilib";

export default function Page() {
  const breadcrumbs = [
    {
      title: "Home",
      href: "#",
      icon: <CogIcon className="md:w-6 md:h-6 w-5 h-5" />,
    },
    {
      title: "Product",
      href: "#",
      active: true, // set active
      icon: <HomeIcon className="md:w-6 md:h-6 w-5 h-5" />,
    },
    {
      title: "About",
      href: "#",
      icon: <EyeIcon className="md:w-6 md:h-6 w-5 h-5" />,
    },
  ];

  // crumbStyle can be "slash" or "arrow"
  // size can be "xs, sm, base, lg and xl (base is default)
  return <Breadcrumbs breadcrumbs={breadcrumbs} crumbStyle="slash" size="xs" />;
}

Toast Component

Notification

Toast must be wrapped within the client component.

Better to create a ToastProviderComponent

export default function ToastProviderComponent({
  children,
}: {
  children: React.ReactNode;
}) {
  return <ToastProvider>{children}</ToastProvider>;
}

Then create a child component that use toast

export function ToastItem() {
  const { addToast } = useToast();
  return (
    <button
      onClick={() =>
        addToast("Hey There!", { type: "success", position: "top-center" })
      }
      className="bg-blue-500 text-white px-4 py-2 rounded"
    >
      Show Toast
    </button>
  );
}

Call the full Component Tree in server component

export default async function ServerComponent() {
  return (
    <ToastComponent>
      <ToastItem></ToastItem>
    </ToastComponent>
  );
}

NavigationBar Component

Easy Navigation Bar for simple use

Better to create a client NavbarWrapper component

"use client";

import { NavigationBar } from "tnuilib";
import {
  ArrowDownCircleIcon,
  ArrowsUpDownIcon,
  HomeModernIcon,
  NewspaperIcon,
  UsersIcon,
} from "@heroicons/react/24/outline";

const items = [
  // items to show in navbar
  { icon: HomeModernIcon, href: "/", title: "Home" },
  { icon: NewspaperIcon, href: "/events", title: "Events" },
  { icon: UsersIcon, href: "/users", title: "Users" },
];

export default function NavbarWrapper() {
  return (
    <>
      <div className="h-screen hidden md:flex">
        {" "}
        // important to add this for full high in desktop
        <NavigationBar
          logo={<div className="w-8 h-8 rounded-full">MyLogo</div>}
          variant="vertical" // vertical variant for desktop
          items={items}
          logout={{
            icon: ArrowDownCircleIcon, // icon for logout button
            title: "Logout", // logout button title
            onClick: () => console.log("Logout clicked"), // add logout action here
          }}
          styles={{
            // add custom styles but in react syntax
            container: { backgroundColor: "transparent" },
            item: { borderRadius: 12, borderWidth: 4, borderColor: "#e5e7eb" },
            icon: { color: "#2563eb" },
          }}
        />
      </div>
      <div className="block md:hidden pt-2">
        <div className="h-12 rounded-full">MyLogo</div> // Logo for horizontal
        <NavigationBar
          variant="horizontal" // horizontal variant for small screens
          items={items}
          logout={{
            icon: ArrowsUpDownIcon,
            title: "Logout",
            href: "/logout", // if there is no actions required, use href
          }}
        />
      </div>
    </>
  );
}

In Layout.tsx in Next.js add some width to navigation for desktop screens.

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        // wrap with the fixed div
        <div className="fixed w-full md:w-56 z-20 bg-gray-50 dark:bg-gray-800 max-h-screen">
          <NavbarWrapper />
        </div>
        // move children to the right to avoid overlapping
        <div className="md:pl-56 md:pt-8 md:mt-0 w-full">{children}</div>
      </body>
    </html>
  );
}

============================

Slide Nav Bar
Simple navigation bar

Better to create a client Wrapper component

"use client";

import { FaceSmileIcon, TvIcon } from "@heroicons/react/24/outline";
import { SlideNavBar } from "tnuilib";

export default function SlideNavBarPage() {
  return (
    <SlideNavBar
      links={[
        { name: "a", href: "/", icon: <FaceSmileIcon className="w-4 h-4" /> },
      ]}
      logo="My App"
      icon={TvIcon}
      children={<p>Other Contents Here</p>}
    />
  );
}

Finally, put it in rootlayout in Next.js

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        <SlideNavBarPage />
        {children}
      </body>
    </html>
  );
}
Gemini Style Side Bar

Better to create a client Wrapper component

"use client";

import { useState } from "react";
import { GeminiSidebar } from "tnuilib";
import {
  EnvelopeIcon as ChatIcon,
  PlusIcon,
  CogIcon as SettingsIcon,
} from "@heroicons/react/24/outline";

export default function GeminiWrapper() {
  const [isCollapsed, setIsCollapsed] = useState(false); // open and close side bar

  // Sample data for recent items
  const itemHistory = [
    {
      id: "c1",
      title: "Recipe for chocolate cake",
      icon: ChatIcon,
      href: "/chat/1",
    },
    { id: "c2", title: "React Sidebar Logic", icon: ChatIcon, href: "/chat/2" },
    {
      id: "c3",
      title: "Project Alpha Roadmap",
      icon: ChatIcon,
      href: "/chat/3",
    },
  ];

  // Footer items
  const footerLinks = [
    { id: "f1", title: "Settings", icon: SettingsIcon, href: "/settings" },
    { id: "f2", title: "Help", icon: ChatIcon, href: "/help" }, // ChatIcon for Help
  ];

  return (
    <div className="fixed h-screen">
      {" "}
      // fixed style side bar.
      <GeminiSidebar
        logo={
          <div
            style={{ fontWeight: "bold", fontSize: "18px", color: "#4285f4" }}
          >
            MyApp
          </div>
        }
        topButton={{
          id: "new",
          title: "Add Item",
          icon: PlusIcon,
          onClick: () => console.log("New Item initialized!"), // add item button click
        }}
        items={itemHistory}
        footerItems={footerLinks}
        collapsed={isCollapsed}
        onToggle={() => setIsCollapsed(!isCollapsed)}
        onSearchClick={() => console.log("Search button clicked!")} // search button click
        styles={{
          container: { background: "#131314" }, // Force  Dark
          item: { borderRadius: "20px" }, // rounded pill shape
          text: { fontSize: "14px" },
        }}
      />
    </div>
  );
}

Finally, put it in layout.tsx in Next.js

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        <GeminiWrapper />
        {children}
      </body>
    </html>
  );
}

Buttons

Simple Button

Simple Button Component (able to use className and style for custom designs)

<Button
  className={`px-8 py-2.5 text-sm gap-4
          bg-linear-to-r from-cyan-600 to-indigo-900 
         hover:from-indigo-700 hover:to-cyan-700
        `}
  icon={<TvIcon className="w-4 h-4" />}
  onClick={() => alert("clicked")}
>
  Btn
</Button>

Borderspin Button

Usage: size can be 150px or 2rem and so on.

<BorderSpinButton
  bgColor="#333"
  size="150px"
  textColor="#fff"
  borderWidth="4px"
  borderColor="#f1f1f1"
  icon={<HomeIcon className="w-4 h-4" />}
  onClick={() => alert("spin button")}
>
  Conic
</BorderSpinButton>

Underline Button

Use in a client component

<UnderlineButton
  underlineColor="gray"
  underlineHeight={4}
  onClick={() => alert("Hello")}
  className="w-20 rounded-md py-1 bg-blue-500" // use tailwindcss
>
  {/* availability to add children for custom design */}
  <div className="flex items-center justify-center gap-2">
    <FaceFrownIcon className="w-4 h-4" /> Button
  </div>
</UnderlineButton>

Hover Fill Button

Use in a client component similar to Underline button

<HoverFillButton
  className="h-10 w-32 bg-blue-500 rounded-md"
  hoverColor="green"
  opacity={0.6}
  duration={1000}
  onClick={() => alert("hello")}
>
  Btn
</HoverFillButton>

Outline Animated Buttons

Outline Animate Button

<OutlineAnimateButton
  borderColor="red"
  borderWidth={3}
  duration={400}
  bgColor="yellow"
  textColor="blue"
>
  Animate Outline
</OutlineAnimateButton>

Outline Circle Button

Able to use like other buttons

<OutlineCircleButton
  className="rounded-md"
  borderColor="yellow"
  borderWidth={3}
>
  Circle Outline
</OutlineCircleButton>

Outline Circle Paralle Button

Able to use like other buttons

<OutlineCircleParallel
  className="shadow-sm text-sm border-b"
  borderColor="black"
  duration={1000} // default 400
  borderWidth={2}
>
  Outline circle parallel
</OutlineCircleParallel>

Outline Button

Able to use like other buttons

<OutlineButton style={{ backgroundColor: "blue" }} borderColor={"white"}>
  Outilne Button
</OutlineButton>

Text Change Button

Text change on hover

<TextChangeButton
  className="w-28 text-xs"
  labelColor="gray"
  Ficon={<FaceSmileIcon className="w-4 h-4" />}
  Licon={<FaceFrownIcon className="w-4 h-4" />}
/>

Switch Toggler

Normatlly useState can be used to control on and off

const [enabled, setEnabled] = useState(true);

But startTransition can also be used to control pending state

const [isPending, startTransition] = useTransition();

Use toggler in parent component

return (
  <>
    <SwitchToggler
      width={50}
      height={26}
      activeColor="#10b981"
      inactiveColor="#e5e7eb"
      loading={isPending} // loading state
      defaultChecked={true} // make default check
      onChange={setEnabled} // without transition, state updates immediately
      // onChange={(next) => startTransition(() => setEnabled(next))}
      // checked={enabled}             // not mainly required, you can comment it out
      // disabled={false}                 // when true, cannot toggle
    />

    {/* display state here*/}
    {enabled ? "enabled" : "disabled"}
  </>
);

Fixed Pagination

useState can be used to set the current page to be sync.

const [current, setCurrent] = useState(1); // 1 is current page.

Default pagination usage

return (
  <Pagination
    totalCount={400}
    pageSize={10}
    currentPage={current}
    onPageChange={setCurrent}
  />
);

Customize pagination design

return (
  <Pagination
    totalCount={700}
    pageSize={10}
    currentPage={current}
    onPageChange={setCurrent}
    activeColor="blue"
    activeTextColor="yellow"
    inactiveTextColor="gray"
    borderRadius={100}
    gap={10}
  />
);

Date Pick Wheel

Wheel style date picker (customize)

const [date, setDate] = useState(new Date());
return (
  <>
    <DatePickWheel
      bgColor="orange" // default light blue
      textColor="white" // default dark blue
      wheelWidth={250} // default 200, min 150
      itemHeight={20} // default 20
      visibleItems={5} // default 7
      totalYears={200} // default 50
      startYear={1990} // default 2000
      onChange={(d) => setDate(d)}
    />
    {date.toDateString()}
  </>
);

Date Pick Calendar

Calendar to pick a date or a set of start date, end date, shift month and year

Create a component to use start and end dates For example, check the following CalendarDates component:

/* this component is used to show selected start and end dates */
import { useCalendar } from "tnuilib";

export default function CalendarDates() {
  const { calendar, totalDays } = useCalendar();

  const ClearDatesButton = () => {
    const { setCalendar } = useCalendar();

    const handleClear = () => {
      setCalendar({
        startDate: undefined,
        endDate: undefined,
        mode: "start", // reset flow
      });
    };

    return (
      <button
        className="px-4 py-2 bg-blue-500 rounded-md text-sm"
        onClick={handleClear}
      >
        Clear Dates
      </button>
    );
  };

  return (
    <>
      Start Date: <>{calendar.startDate?.toDateString()}</>
      End Date: <>{calendar.endDate?.toDateString()}</>
      Duration: {totalDays}
      <ClearDatesButton />
    </>
  );
}

Wrap Calenar and CalenarDates by CalendarProvider.

for closed dates, new Date() function has to be used to define dates in calendar. for selecting only a date set isRange = {false} // default {true} and output is start_date

/* in this component, you can style themes, select a single or a range of days, add close dates with titles, default start date and default end dates. */
"use client";
import { Calendar, CalendarProvider } from "tnuilib";
import CalendarDates from "./calendar-dates";

export default function Page() {
  const myTheme = {
    calendarBg: "#1a1a1a", // Dark mode
    primaryColor: "#f59e0b", // Amber selected color
    rangeBg: "#451a03", // Dark amber range
    todayColor: "#fbbf24", // Bright amber for today
    textColor: "#f3f4f6", // White-ish text
    mutedTextColor: "#6b7280",
    fontSize: "12px",
    width: "400px",
  };

  const closedDates = [
    // YYYY-MM-DD - use new Date()
    { date: new Date(2026, 2, 25), title: "Public Holiday" },
    { date: new Date(2026, 2, 28), title: "Maintenance Day" },
    { date: new Date(2026, 3, 1), title: "Fully Booked" },
  ];

  return (
    <>
      <CalendarProvider 
      isRange={true}  // require to select a date or range of dates
      theme={myTheme} 
      closedDates={closedDates}
      startDate={new Date(2026, 7, 25)}  // default start date
      endDate={new Date(2026, 8, 23)} // default end date
      skipWeekends={true} // skip weekends in counting days (default false)
      skipClosedDates={true} // skip closed dates in counting days (default false)
      >  
        <Calendar/>
        <CalendarDates />
      </CalendarProvider>
    </>
  );
}

Smart Positioning

to position elements within boundaries (used getBoundingClientRect() from react)

 const {ref, tooltipRef, coords} = useSmartPosition(<HTMLDivElement>)
coords contains
coords.position ("top" or "bottom"),
coords.left & coords.top (automatically calculated)

ref types are:

ref pass by useSmartPosition(<...>), parent (allow other types like then tag is used)
tooltipRef be only so only is able to used inside tooltip

Create a parent component (component can be designed here)

import { useSmartPosition } from "./smart-position";

export const ToolTip = () => {
  const { ref, tooltipRef, coords } = useSmartPosition<HTMLDivElement>( // can use react elements like <HTMLButtonElement> as parent
    { gap: 4 }, // set gap
  );

  return (
    <div className="">
      <div
        ref={ref} // parent ref for parent <HTMLDivElement>
        className="absolute bottom-0 rounded-lg bg-blue-600 px-4 py-2 text-white"
      >
        Hover Me
      </div>
      <div
        ref={tooltipRef} // child component, only <div> is allowed here
        className="fixed z-50 rounded bg-gray-900 px-2 py-1 text-sm text-white shadow-xl"
        style={{
          top: coords.top,
          left: coords.left,
        }}
      >
        Dynamic Tooltip ({coords.position})
      </div>
      ,
    </div>
  );
};

Finally, use in a main component

<ToolTip />

Date funcitons

dateConverter(date: Date) Function

Formats a date for UI. (format into a human-readable string.)

e.g. const date = new Date("2026-03-12"); console.log(DateConverter(date)); output => 12 / Mar / 2026 _ return String _

## dateConverter(date: Date)

dateTimeFormatter(date: Date) Function

For display formatting (format into a human-readable string.)

Similar and internally equivalent to dateObj.toLacaleDateString e.g. new Intl.DateTimeFormat("en-US", {dateStyle: "long"}) _ return String _

## dateTimeFormatter(date: Date)

normalizeDateKey(date: Date | string) Function

Create a stable date key (create consistent string like YYYY-MM-DD)

e.g. normalizeDateKey("2026-03-12T10:45:00"); output => 2026-03-12 ** Useful for database keys, calendar events, object keys, group by date *** return string & date Object ***

## normalizeDateKey('2026-5-5');

toLocalDate(date: Date) Function

Convert UTC vs Locale Date (Fix timezone differences.)

e.g. const d = new Date("2026-03-12T00:00:00Z"); console.log(toLocalDate(d)); ** d is the 'local equivalent date'. *** return Date object ***

## toLocalDate(date);

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Please make sure to update tests as appropriate.

License

MIT