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 tnuilibUsage
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.
