aravint-ui-navigation
v1.0.43
Published
A reusable, dependency-free React + TypeScript tabs component with numbered badges, sticky tab bar, and mobile-responsive sizing — while preserving the original desktop/tablet look exactly.
Readme
CustomTabs
A reusable, dependency-free React + TypeScript tabs component with numbered badges, sticky tab bar, and mobile-responsive sizing — while preserving the original desktop/tablet look exactly.
Features
- Controlled or uncontrolled usage (
value/onValueChangeordefaultValue) - Numbered badges per tab (optional)
- Optional icons per tab
- Disabled tab support
- Sticky tab bar (
position: sticky; top: 0) - Active-tab underline-hiding trick (
marginBottom: -1) to visually merge the active tab with its content panel - Responsive breakpoints for tablet (
≤768px) and phone (≤480px) that only affect sizing (padding, font-size, gap, badge size) — colors, borders, and desktop layout are untouched - On phones, tabs wrap to a new row instead of overflowing or forcing horizontal scroll
Installation
Copy CustomTabs.tsx into your project (e.g. src/components/CustomTabs.tsx). No extra dependencies — just React.
Basic Usage
import CustomTabs, { TabItem } from "@digitus-fci-oa/navigation";
const tabs: TabItem[] = [
{ id: "overview", label: "Overview", content: <OverviewPanel /> },
{ id: "details", label: "Details", content: <DetailsPanel /> },
{ id: "history", label: "History", content: <HistoryPanel />, disabled: true },
];
function App() {
const [activeTab, setActiveTab] = useState("overview");
return (
<div className="h-screen bg-gray-50 flex flex-col">
<div className="flex-1 min-h-0">
<CustomTabs
tabs={tabs}
value={activeTab}
onValueChange={setActiveTab}
showTabNumbers
className="h-full"
tabListClassName="bg-white"
/>
</div>
</div>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| tabs | TabItem[] | — | Required. Array of tab definitions. |
| defaultValue | string | first tab's id | Initial active tab (uncontrolled mode). |
| value | string | — | Active tab id (controlled mode). Providing this switches the component to controlled. |
| onValueChange | (value: string) => void | — | Called when the user selects a tab. |
| className | string | "" | Class on the outer wrapper. |
| tabListClassName | string | "" | Class on the tab bar container. |
| tabButtonClassName | string | "" | Class on each tab button. |
| contentClassName | string | "" | Class on the content panel. |
| showTabNumbers | boolean | true | Show/hide the numbered badge on each tab. |
| showContent | boolean | true | Show/hide the content panel entirely (render tab bar only). |
| animated | boolean | true | Enables the opacity transition on the content panel. |
TabItem
| Field | Type | Description |
|---|---|---|
| id | string | Required. Unique tab identifier. |
| label | string | Required. Tab display text. |
| content | React.ReactNode | Content rendered when this tab is active. |
| disabled | boolean | Disables selection of this tab. |
| icon | React.ReactNode | Optional icon rendered before the label. |
Controlled vs Uncontrolled
- Uncontrolled: omit
value. The component manages its own active tab internally, seeded bydefaultValue(or the first tab). - Controlled: pass
value+onValueChange. You own the active-tab state; the component only reports selection events.
Styling Approach
Colors, borders, radii, and the sticky/active-tab layout tricks are set as inline styles and are the same across all screen sizes — this is what keeps the desktop/tablet appearance unchanged.
Only size-related properties (padding, font-size, gap, badge width/height) live in an injected <style> block using CSS classes (.ctabs-list, .ctabs-btn, .ctabs-badge, .ctabs-label), because inline styles can't respond to @media queries. This block ships inside the component, so no external CSS file is required.
Breakpoints
| Breakpoint | Behavior |
|---|---|
| Desktop (default) | Original sizing: 8px 20px padding, 14px font, 20px badges. |
| Tablet (≤768px) | Slightly reduced padding/font/badge size. Tab bar scrolls horizontally if tabs overflow. |
| Phone (≤480px) | Further reduced padding/font/badge size. Tab bar wraps to a new row instead of scrolling, so every label stays fully visible. |
To customize breakpoint values, edit the @media blocks inside the component's <style> tag directly.
Notes / Gotchas
marginBottom: -1on the active tab intentionally overlaps the tab bar's bottom border so the active tab visually "merges" into its content panel. This depends onalignItems: "flex-end"on the tab list — don't remove that when customizing.- The phone-only
flex-wrap: wrapsetsoverflow-x: visibleto explicitly undo the tablet breakpoint'soverflow-x: auto, since media query rules don't reset each other automatically. - If you have a large number of tabs, consider whether wrapping (phone behavior) or horizontal scroll suits your use case better, and adjust the
480pxblock accordingly.
Stepper Component
A reusable workflow stepper component for displaying progress through a series of steps.
Installation
Install the package:
npm install @digitus-fci-oa/navigationImport Styles
Import the stylesheet once in your application's global CSS file (e.g. index.css or App.css).
@import "@digitus-fci-oa/navigation/dist/navigation.css";Usage
import React from "react";
import { Stepper } from "@digitus-fci-oa/navigation";
const steps = [
{ id: "1", title: "Initiated", date: "21 May 2026", time: "09:40 AM" },
{ id: "2", title: "RoHS Approval", date: "21 May 2026", time: "09:40 AM" },
{ id: "3", title: "Planner Approval", date: "21 May 2026", time: "09:40 AM" },
{ id: "4", title: "Manufacturing Head Approval" },
{ id: "5", title: "Engineering Head Approval" },
];
export default function ApprovalTracker() {
return (
<Stepper
steps={steps}
currentStep={4}
/>
);
}Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| steps | StepItem[] | Required | List of workflow steps. |
| currentStep | number | Required | Current active step (starts from 1). |
| completedColor | string | #059669 | Color of completed steps. |
| activeColor | string | #059669 | Color of the active step. |
| pendingColor | string | #9ca3af | Color of pending steps. |
| lineColor | string | #e5e7eb | Color of the connector line. |
| showTime | boolean | true | Shows or hides the time below each step. |
| orientation | "horizontal" \| "vertical" \| "responsive" | "responsive" | Stepper layout. |
StepItem
interface StepItem {
id: string;
title: string;
date?: string;
time?: string;
}Example
const steps = [
{
id: "1",
title: "Initiated",
date: "21 May 2026",
time: "09:40 AM",
},
{
id: "2",
title: "RoHS Approval",
date: "21 May 2026",
time: "09:40 AM",
},
{
id: "3",
title: "Planner Approval",
date: "21 May 2026",
time: "09:40 AM",
},
{
id: "4",
title: "Manufacturing Head Approval",
},
];Notes
currentStepis 1-based.- The first step is treated as the starting point.
- Completed steps display a check icon.
dateandtimeare optional for each step.- When
showTime={false}, only the date is displayed.
SummaryDetailsCard
Reusable, responsive summary card — rows of label/value fields separated by dividers (e.g. PNC/RDR detail header, ticket summary, order details).
Usage
import { DetailRow, SummaryDetailsCard } from "@digitus-fci-oa/navigation";
## Import Styles
Import the stylesheet once in your application's global CSS file (e.g. `index.css` or `App.css`).
```css
@import "@digitus-fci-oa/navigation/dist/navigation.css";
const rows: DetailRow[] = [
[
{ label: 'Title', value: 'Title will be show here' },
{ label: 'Remark', value: 'Remark will be show here...'},
],
[
{ label: 'PNC Number', value: 'PNC-2026-0001' },
{ label: 'PNC Date', value: '26-06-2026 11:00AM' },
{ label: 'Requester', value: 'Prakash Kumar' },
{ label: 'Flag Name', value: 'M/L, F' },
],
[
{ label: 'Pending With', value: 'RoHS Team' },
{ label: 'Created On', value: '26-06-2026 11:00AM' },
{ label: 'Last Updated', value: '27-06-2026 10:00 AM' },
],
];
<SummaryDetailsCard rows={rows} />Props
| Prop | Type | Default | Description |
| ----------- | ------------- | -------------- | ----------------------------------------------- |
| rows | DetailRow[] | — | Rows of fields, top to bottom, each row on its own divider-separated line |
| className | string | 'mt-2 mx-2' | Extra classes on the outer card |
Field (DetailRow = DetailField[])
| Prop | Type | Default | Description |
| ------- | ------------------- | ----------- | ------------------------------------------------------------------------------------ |
| label | string | — | Small uppercase label |
| value | ReactNode | — | The value to display |
| size | 'default' \| 'lg' | 'default' | 'lg' for hero fields like Title/Remark |
| span | 1 \| 2 \| 3 \| 4 | 1 | How many of the 4 grid columns this field occupies (desktop/tablet — mobile is always full width) |
Layout
- Every row uses the same fixed 4-column grid, so fields stay aligned across rows regardless of how many fields a row has.
- Below 640px, the grid collapses to a single column and every field
goes full width, regardless of its
span. - Styled with plain CSS (
SummaryDetailsCard.css), not Tailwind utilities, so it renders correctly no matter what the consuming app's Tailwind (or non-Tailwind) setup looks like. Colors/spacing are theme-able via CSS variables on.summary-detail-card.
EmptyState
A reusable component for displaying an empty state message when no data or content is available.
Features
- Reusable and customizable
- Optional icon support
- Custom title and description
- Supports additional CSS classes
- Responsive design using Tailwind CSS
Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| title | string | Yes | Title displayed in the empty state. |
| description | string \| React.ReactNode | Yes | Description displayed below the title. |
| icon | React.ReactNode | No | Optional icon or illustration. |
| className | string | No | Additional CSS classes for the container. |
Usage
import { EmptyState } from "@your-package/navigation";
<EmptyState
title="No Confirmation Received"
description="No confirmation has been received for this request yet. Once the respective team confirms the action, the details will be displayed here."
/>With an Icon
import { EmptyState } from "@digitus-fci-oa/navigation";
import { InfoIcon } from "./InfoIcon";
<EmptyState
icon={<InfoIcon />}
title="No Data Found"
description="There are no records available."
/>Example
<EmptyState
title="No Confirmation Received"
description={
<>
No confirmation has been received for this request yet.
<br />
Once the respective team confirms the action, the details will be displayed here.
</>
}
/>Output
Displays a centered empty state containing:
- Optional icon
- Title
- Description
