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

@dinhn1202/ui-kit

v0.2.5

Published

DTO shared React TypeScript UI kit based on Ant Design

Readme

@dinhn1202/ui-kit

DTO shared React TypeScript UI kit based on Ant Design v6.

Live demo

Open the DTO UI Kit demo

Installation

npm install @dinhn1202/ui-kit

Peer dependencies:

npm install react react-dom antd@^6.5.0

CustomGantt also installs its Gantt renderer and required MUI runtime dependencies with the UI kit package.

Usage

import { CustomButton, CustomThemeProvider } from "@dinhn1202/ui-kit";

function App() {
  return (
    <CustomThemeProvider>
      <CustomButton type="primary">Save</CustomButton>
    </CustomThemeProvider>
  );
}

export default App;

Override theme

import { CustomThemeProvider } from "@dinhn1202/ui-kit";

const customTheme = {
  token: {
    colorPrimary: "#722ed1",
    borderRadius: 12,
  },
};

function App() {
  return (
    <CustomThemeProvider theme={customTheme}>
      <YourApp />
    </CustomThemeProvider>
  );
}

Available components

import {
  CustomButton,
  CustomDatePicker,
  CustomGantt,
  CustomInput,
  CustomInputNumber,
  CustomLoading,
  CustomModal,
  CustomRadio,
  CustomRadioGroup,
  CustomSelect,
  CustomTable,
  CustomThemeProvider,
  CustomTimePicker,
  CustomTreeSelect,
  CustomUpload,
  CustomUploadDragger,
  CustomUserSelect,
  type UserSearchService,
} from "@dinhn1202/ui-kit";

CustomButton provides an optional customVariant (primary, secondary, or danger). CustomSelect, CustomInputNumber, CustomDatePicker, CustomTimePicker, CustomTreeSelect, and CustomTable provide an optional fullWidth prop. By default, CustomTable adds client-side sorting, value filters, global search, and multi-column text filtering to flat, simple dataIndex columns. Set a column's dataType: "date" to use a calendar for its header and multi-column filters, with matching performed by calendar day and chronological sorting. Header filters and toolbar filters share the same applied values. Its toolbar also provides a searchable column chooser with drag-and-drop reordering. Set enableColumnFeatures={false} to render the unenhanced Ant Design table; enableGlobalSearch and enableMultiColumnFiltering can be disabled independently. Pagination is shown by default with a page size selector and quick jumper; pass pagination={false} to disable it. Pass a unique columnStorageKey to persist column chooser visibility and ordering in localStorage. Without this prop, the layout is kept only for the current component session. A saved layout is reset automatically whenever the table column schema changes.

When serverSideRender is enabled, header value filters stay available. The first time a column's filter dropdown opens, the table requests only the first 100 rows that match the configured base filters and lists their distinct values. Scrolling near the end requests the next 100 rows; repeated scroll events are coalesced and no request is made after the server reports the end. Typing in Search in filters is debounced, resets the option list, and sends a server-side contains filter for that column so matches outside the loaded batch can be found. Selected values remain available while searching and loading more options. Selecting values applies them on the server as columnFilters and reloads the first page. Date columns keep their calendar filter and are matched by day.

SharePoint server-side paging

Use serverSideRender to let CustomTable own loading, cursor paging, page cache, sorting, filtering, and loading state. The PnPjs adapter uses the SPFI instance already configured by the consuming application, so the UI kit does not bundle a second copy of PnPjs. Cursor paging requires the async item iterator available in PnPjs v4.

import { useMemo } from "react";
import {
  createPnPjsTableService,
  CustomTable,
  type TableColumnsType,
} from "@dinhn1202/ui-kit";

type ProjectItem = {
  ID: number;
  Title: string;
  Modified: string;
};

const columns: TableColumnsType<ProjectItem> = [
  { dataIndex: "ID", title: "ID" },
  { dataIndex: "Title", title: "Title" },
  { dataIndex: "Modified", dataType: "date", title: "Modified" },
];

const projectService = useMemo(
  () => createPnPjsTableService<ProjectItem>(sp),
  [sp],
);

<CustomTable<ProjectItem>
  columns={columns}
  serverSideRender={{
    service: projectService,
    tableName: "Projects",
    pageSize: 50,
    filters: [{ field: "Status", value: "Active" }],
    sort: { field: "Modified", direction: "descend" },
    searchFields: ["Title"],
  }}
/>;

select defaults to the columns' simple dataIndex fields and rowKey defaults to ID. Pagination uses read-only simple mode because SharePoint list items use cursor/skip-token paging rather than numeric offsets. Pass filter for a base OData filter, sort for an initial server sort, and expand/select for lookup fields. When a filtered exact count is unavailable, pagination grows progressively as the user moves forward instead of loading the full list just to count it. Structured filters support eq, ne, gt, ge, lt, le, contains, and startsWith; use filter only for advanced raw OData clauses.

Joining lookup fields

SharePoint joins a child list through a Lookup column on the main list. Declare each lookup column in expand and point the table column's dataIndex at the child field with an array path:

type ProjectItem = {
  ID: number;
  Title: string;
  Category?: { ID: number; Title: string };
};

const columns: TableColumnsType<ProjectItem> = [
  { dataIndex: "ID", title: "ID" },
  { dataIndex: "Title", title: "Project" },
  { dataIndex: ["Category", "Title"], title: "Category" },
];

<CustomTable<ProjectItem>
  columns={columns}
  serverSideRender={{
    service: projectService,
    tableName: "Projects",
    expand: ["Category"],
  }}
/>;

The kit adds the joined path (Category/Title) to select automatically and expands the lookup through PnPjs, so sorting, column filters, the full filter option scan, and global search work on the joined field like any plain field. Person columns follow the same pattern, for example expand: ["Author"] with dataIndex: ["Author", "Title"]. Multi-select lookups return arrays, so give those columns a custom render instead of an automatic path. If you pass a custom select, include the joined paths yourself.

Joining two separate lists without a Lookup

When the two lists share no Lookup relationship, join them in memory with createJoinedListsTableService. Both lists are loaded completely once per table instance and cached, so sorting, column filters, global search, and numbered paging all work on the joined columns:

import { createJoinedListsTableService, CustomTable } from "@dinhn1202/ui-kit";

type ProjectItem = {
  ID: number;
  Title: string;
  CategoryCode?: string;
  Category?: { ID: number; Code: string; Title: string };
};

const projectService = useMemo(
  () =>
    createJoinedListsTableService<ProjectItem>(sp, {
      primaryList: "Projects",
      primaryJoinColumn: "CategoryCode",
      childList: "Categories",
      childJoinColumn: "Code",
      childPropertyName: "Category",
      primarySelect: ["ID", "Title", "CategoryCode"],
      childSelect: ["ID", "Code", "Title"],
    }),
  [sp],
);

const columns: TableColumnsType<ProjectItem> = [
  { dataIndex: "ID", title: "ID" },
  { dataIndex: "Title", title: "Project" },
  { dataIndex: ["Category", "Title"], title: "Category" },
];

<CustomTable<ProjectItem>
  columns={columns}
  serverSideRender={{
    service: projectService,
    tableName: "Projects",
    searchFields: ["Title", "Category/Title"],
  }}
/>;

The join matches primaryJoinColumn values against childJoinColumn values; rows without a match render null. Sorting by Category/Title, filtering the Category/Title column, and searching across joined fields all work because the adapter evaluates them over the cached joined rows. Raw OData filter is not supported by this adapter; use structured filters instead. This pattern fits lists of up to a few tens of thousands of rows; larger lists should use a real Lookup or a server-side join.

ASP.NET Core and SQL Server paging

Use the offset adapter for a .NET API that returns { items, total }. Unlike SharePoint cursor paging, this mode enables numbered pages and quick-jump.

import { useMemo } from "react";
import { createDotNetTableService, CustomTable } from "@dinhn1202/ui-kit";

const projectApi = useMemo(
  () =>
    createDotNetTableService<ProjectItem>({
      endpoint: "/api/table-query",
      headers: () => ({ Authorization: `Bearer ${accessToken}` }),
    }),
  [accessToken],
);

<CustomTable<ProjectItem>
  columns={columns}
  serverSideRender={{
    service: projectApi,
    tableName: "Projects",
    pageSize: 50,
    filters: [{ field: "Status", value: "Active" }],
    sort: { field: "Modified", direction: "descend" },
    searchFields: ["Title", "ProjectCode"],
  }}
/>;

The adapter sends a POST body containing tableName, page, pageSize, select, structured filters, column filters, sorting, and search. The API must return this shape:

{
  "items": [{ "ID": 1, "Title": "Project A" }],
  "total": 30000
}

Create the service once (outside the component or with useMemo) so its page cache survives re-renders. Raw filter clauses are intentionally rejected by the .NET adapter. The API must allowlist table and column names and parameterize filter values; never concatenate client-provided schema names or values into SQL. A controller and EF Core reference implementation is available in examples/dotnet.

All other Ant Design props remain available on their DTO wrappers.

CustomLoading renders the four-color animated dot treatment from the Framas splash reference. Pass fullScreen for a fixed viewport overlay, or wrap content with overlay to cover only that content area. Use logoSrc to render a brand image above the dots.

Gantt

CustomGantt renders a read-only timeline with nested items, dependencies, progress, optional phase and signal columns, tooltips, context actions, and drag-to-reorder callbacks. Dates may be Date, ISO strings, or Dayjs values. Invalid or missing dates are safely rendered from timelineStart while the list continues to show - for unavailable source values.

import {
  CustomGantt,
  type GanttItem,
} from "@dinhn1202/ui-kit";

const items: GanttItem<{ sourceId: number }>[] = [
  {
    id: "release",
    title: "Release 1",
    type: "project",
    start: "2026-08-01",
    end: "2026-09-15",
    progress: 40,
    metadata: { sourceId: 10 },
  },
  {
    id: "implementation",
    title: "Implementation",
    parentId: "release",
    dependencyIds: [],
    start: "2026-08-05",
    end: "2026-08-28",
    owner: "Maya Chen",
    signals: {
      notes: { available: true, isNew: true },
      files: { available: true, tooltip: "Specification.pdf" },
    },
    metadata: { sourceId: 11 },
  },
];

<CustomGantt
  items={items}
  timelineStart="2026-08-01"
  timelineEnd="2026-09-30"
  viewMode="weeks"
  showPhaseColumn
  getContextMenuItems={() => [{ key: "edit", label: "Edit item" }]}
  onContextAction={(item, action) => console.log(action, item.metadata)}
  onItemClick={(item) => console.log(item)}
  onReorder={(source, target) => console.log(source.id, target.id)}
  onSignalClick={(item, signal) => console.log(item.id, signal)}
/>

The component owns only presentation and transient expansion state. Persisting reordering, edits, context actions, or signal actions remains the responsibility of the consuming application.

User Select service

CustomUserSelect supports two data modes. Pass service to search a directory with debouncing, cancellation of stale requests, and optional avatar resolution. Pass dataSource when users are already available locally; this disables the search input and does not call the service.

import { CustomUserSelect, type UserSearchService } from "@dinhn1202/ui-kit";

const graphUserService: UserSearchService = {
  searchUsers: (query, signal) => graphDirectory.searchUsers(query, signal),
  getUserAvatar: (user, signal) => graphDirectory.getAvatarUrl(user.id, signal),
};

<CustomUserSelect
  fullWidth
  placeholder="Search by name or email"
  service={graphUserService}
  onChange={(userId, user) => console.log(userId, user)}
/>
<CustomUserSelect
  dataSource={users}
  placeholder="Select a user"
  onChange={(userId, user) => console.log(userId, user)}
/>

Users can include { id, displayName, email?, avatarUrl?, jobTitle?, officeLocation? }. Dropdown items show an avatar and display name; hovering an item shows its available email, job title, and office location. If avatarUrl is not included in service mode, implement getUserAvatar to fetch the Graph profile photo; initials are shown while no photo is available.

Node.js compatibility

This package is configured for:

Node.js v18.20.8
Vite 6.4.3
@vitejs/plugin-react 4.7.0
vite-plugin-dts 4.5.4

Do not upgrade Vite to v7+ unless Node.js is upgraded to a compatible version.

Notes

This package wraps Ant Design components while preserving their original props. Each DTO component extends the corresponding Ant Design component props.