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

@geektech/one

v0.7.0

Published

One UI 是基于 TSone 的轻量级纯 TypeScript UI 组件库

Readme

One UI

One UI (@geektech/one) is a lightweight, zero-runtime-dependency UI component library for the TSone class-component framework. TSone is a peer dependency and must be installed by the consuming application.

Documentation: https://geektech-team.github.io/tsone/one/

Installation

bun add @geektech/tsone @geektech/one

Quick start

import {
  Component,
  createApp,
  createComponent,
  type VNode,
} from '@geektech/tsone';
import { OneButton, OneCard, OneInput } from '@geektech/one';

class App extends Component {
  protected initState(): object {
    return {};
  }

  protected initStyles(): void {}

  protected render(): VNode {
    return {
      tag: 'main',
      children: [
        createComponent(OneButton, {}, ['Save']),
        createComponent(OneInput, {
          placeholder: 'Project name',
          ariaLabel: 'Project name',
        }),
        createComponent(OneCard, { title: 'One UI' }, [
          'Lightweight components for TSone.',
        ]),
      ],
    };
  }
}

createApp({ root: App }).mount();

Component categories

  • Basic: OneButton, OneSpace
  • Layout: OneDivider, OneRow, OneCol
  • Form: OneForm, OneFormItem, OneInput, OneTextarea, OneSelect, OneCascader, OneTimePicker, OneCheckbox, OneCheckboxGroup, OneRadio, OneRadioGroup, OneSwitch, OneSlider, OneRate, OneUpload
  • Navigation: OneTabs, OneSteps, OneBreadcrumb, OnePagination
  • Data display: OneCard, OneTag, OneBadge, OneAvatar, OneProgress, OneEmpty, OneTable, OneTree, OneCollapse, OneSkeleton, OneDescriptions, OneTimeline, OneCarousel
  • Feedback and overlays: OneAlert, OneMessage, OneDialog, OneTooltip, OnePopover, OneLoading

Grid

OneRow and OneCol divide the available width into 24 columns. Give each column a span (1-24) and combine them freely; offset shifts a column right, and gutter sets the horizontal/vertical spacing.

import { OneRow, OneCol } from '@geektech/one';

new OneRow({
  gutter: [16, 16],
  children: [
    { component: OneCol, props: { span: 12 }, children: ['12'] },
    { component: OneCol, props: { span: 12 }, children: ['12'] },
  ],
});

new OneRow({
  gutter: [16, 16],
  children: [
    {
      component: OneCol,
      props: { span: 8, offset: 8 },
      children: ['8 + offset 8'],
    },
  ],
});

OneCol also supports responsive breakpoints xs / sm / md / lg / xl / xxl. Each accepts a number (span only) or { span, offset }; smaller breakpoint values cascade to larger ones until overridden.

new OneRow({
  gutter: [16, 16],
  children: [
    { component: OneCol, props: { xs: 24, md: 12 }, children: ['A'] },
    { component: OneCol, props: { xs: 24, md: 12 }, children: ['B'] },
  ],
});

Default breakpoints: xs < 576px, sm >= 576px, md >= 768px, lg >= 992px, xl >= 1200px, xxl >= 1600px. Override them with theme CSS variables --one-grid-breakpoint-sm / md / lg / xl / xxl.

Navigation

Use tabs for parallel views, breadcrumbs for a hierarchy, and pagination for a long collection. All three components support controlled state or cancellable events where native navigation is involved.

import {
  OneBreadcrumb,
  OnePagination,
  OneTabs,
  type OneBreadcrumbClickEvent,
  type OnePaginationChangeEvent,
  type OneTabsChangeEvent,
} from '@geektech/one';

const tabs = new OneTabs({
  value: 'overview',
  items: [
    { value: 'overview', label: 'Overview' },
    { value: 'security', label: 'Security' },
  ],
  children: [
    { tag: 'p', slot: 'overview', children: ['Overview content'] },
    { tag: 'p', slot: 'security', children: ['Security content'] },
  ],
});
tabs.on('change', (payload) => {
  const event = payload as OneTabsChangeEvent;
  tabs.setProps({ value: event.value });
});

const breadcrumb = new OneBreadcrumb({
  maxItems: 3,
  separator: '/',
  items: [
    { label: 'Home', href: '/' },
    { label: 'Projects', href: '/projects' },
    { label: 'Details', current: true },
  ],
});
breadcrumb.on('itemClick', (payload) => {
  const event = payload as OneBreadcrumbClickEvent;
  console.log(event.item, event.index);
});

const basicPagination = new OnePagination({ total: 95 });
const pagination = new OnePagination({
  total: 95,
  page: 1,
  pageSize: 10,
  pageSizeOptions: [10, 20, 50],
  showQuickJumper: true,
});
pagination.on('change', (payload) => {
  const event = payload as OnePaginationChangeEvent;
  pagination.setProps({ page: event.page, pageSize: event.pageSize });
});
void basicPagination;

Data display

Use tags for compact status, badges for counts, avatars for identity, progress bars for completion, empty states when a view has no content, tables for row data, collapsible panels for grouped detail, and skeletons while content loads. OneEmpty accepts an actions slot for the next useful action.

import { OneBadge, OneButton, OneEmpty, OneTag } from '@geektech/one';

new OneTag({ variant: 'success', closable: true });
new OneBadge({ value: 120, max: 99 });
const actions = [
  {
    component: OneButton,
    slot: 'actions',
    children: ['Create project'],
  },
];
new OneEmpty({ description: 'No results', children: actions });
import { OneAvatar, OneProgress } from '@geektech/one';

new OneAvatar({ text: 'M', variant: 'primary' });
new OneAvatar({ src: '/avatar.png', alt: 'Avatar', shape: 'square' });
new OneProgress({ percent: 65, showText: true });
new OneProgress({ percent: 30, variant: 'success' });
import { OneTable, OneCollapse, OneSkeleton } from '@geektech/one';

new OneTable({
  data: [
    { name: 'Eve Lin', role: 'Design' },
    { name: 'Ben Su', role: 'Frontend' },
  ],
  columns: [
    { key: 'name', title: 'Name' },
    { key: 'role', title: 'Role' },
  ],
});
new OneCollapse({
  accordion: true,
  defaultActive: ['basic'],
  items: [
    { value: 'basic', title: 'Basic usage', children: ['Content'] },
    { value: 'advanced', title: 'Advanced usage', children: ['More content'] },
  ],
});
new OneSkeleton({ rows: 3, title: true, avatar: true });

Feedback and overlays

Use OneAlert for persistent inline feedback, OneTooltip for a short description attached to one trigger element, and OneLoading for an in-progress indicator:

import { OneAlert, OneButton, OneLoading, OneTooltip } from '@geektech/one';

const alert = new OneAlert({
  title: 'Saved',
  variant: 'success',
  closable: true,
});

const tooltip = new OneTooltip({
  content: 'Copy link',
  placement: 'bottom-end',
  children: [{ component: OneButton, children: ['Copy'] }],
});

const loading = new OneLoading({ size: 'md', label: 'Loading' });

Use the imperative services for transient messages and confirmation flows:

import { oneDialog, oneMessage } from '@geektech/one';

oneMessage.success('Saved');

const confirmed = await oneDialog.confirm({
  title: 'Delete item?',
  description: 'This action cannot be undone.',
});

Button variants, sizes, loading and events

OneButton supports primary, secondary, and danger variants and sm, md, and lg sizes. Loading buttons are disabled and expose aria-busy.

import { OneButton } from '@geektech/one';

const button = new OneButton({
  variant: 'danger',
  size: 'lg',
  loading: false,
  children: ['Delete'],
});

button.on('click', (event) => {
  console.log('clicked', event);
  button.setProps({ loading: true });
});
button.mount(document.querySelector('#actions') as HTMLElement);

Controlled and uncontrolled inputs

Pass value for a controlled input and accept emitted values through setProps. Pass defaultValue when One UI should maintain the value.

import { OneInput, type OneInputValueEvent } from '@geektech/one';

const controlled = new OneInput({
  value: 'one',
  ariaLabel: 'Controlled project name',
});
controlled.on('input', (payload) => {
  const event = payload as OneInputValueEvent;
  controlled.setProps({ value: event.value });
});

const uncontrolled = new OneInput({
  defaultValue: 'draft',
  ariaLabel: 'Draft project name',
});
uncontrolled.on('change', (payload) => {
  const event = payload as OneInputValueEvent;
  console.log(event.value, event.originalEvent);
});

Multi-line textarea

OneTextarea shares the same controlled/uncontrolled boundary as OneInput and adds rows for the visible line count; combined with OneFormItem it participates in form validation.

import { OneTextarea, type OneTextareaValueEvent } from '@geektech/one';

const textarea = new OneTextarea({
  rows: 5,
  placeholder: 'Type additional notes',
  ariaLabel: 'Additional notes',
});
textarea.on('input', (payload) => {
  const event = payload as OneTextareaValueEvent;
  console.log(event.value);
});

Selection and time

Radio buttons pick one option out of several. Time pickers offer an editable text input plus a click-to-open hour/minute/second dropdown. Both support controlled and uncontrolled modes.

import {
  OneRadio,
  OneRadioGroup,
  OneTimePicker,
  type OneFieldValueEvent,
} from '@geektech/one';

const radio = new OneRadio({ value: 'design', defaultChecked: true });
const group = new OneRadioGroup({
  defaultValue: 'weekly',
  ariaLabel: 'Frequency',
  options: [
    { value: 'daily', label: 'Daily' },
    { value: 'weekly', label: 'Weekly' },
  ],
});
group.on('change', (payload) => {
  const event = payload as OneFieldValueEvent<string>;
  console.log(event.value);
});

new OneTimePicker({ defaultValue: '09:30', ariaLabel: 'Start time' });

Sliders, ratings and uploads

Sliders pick a number within a range, ratings express a star score, and uploads collect local file metadata. All three support controlled and uncontrolled modes.

import {
  OneSlider,
  OneRate,
  OneUpload,
  type OneSliderValueEvent,
  type OneRateValueEvent,
  type OneUploadChangeEvent,
} from '@geektech/one';

const slider = new OneSlider({ defaultValue: 60, showValue: true });
slider.on('change', (payload) => {
  const event = payload as OneSliderValueEvent;
  console.log(event.value);
});

const rating = new OneRate({ defaultValue: 4, allowClear: true });
rating.on('change', (payload) => {
  const event = payload as OneRateValueEvent;
  console.log(event.value);
});

const upload = new OneUpload({ multiple: true, ariaLabel: 'Attachments' });
upload.on('change', (payload) => {
  const event = payload as OneUploadChangeEvent;
  console.log(event.files.map((file) => file.name));
});

Carousel

OneCarousel shows an image list in a translating track. Autoplay, indicator dots, prev/next arrows and keyboard navigation are built in, and the current index supports both controlled and uncontrolled modes.

import { OneCarousel, type OneCarouselChangeEvent } from '@geektech/one';

const carousel = new OneCarousel({
  autoplay: true,
  interval: 3000,
  items: [
    { src: '/banner-1.png', alt: 'City skyline', caption: 'Skyline' },
    { src: '/banner-2.png', alt: 'Harbor sunset' },
  ],
});
carousel.on('change', (payload) => {
  const event = payload as OneCarouselChangeEvent;
  console.log(event.value);
});

Card slots

Children with slot: 'header' or slot: 'footer' fill the named regions; children without a slot fill the default body.

import { createComponent } from '@geektech/tsone';
import { OneButton, OneCard } from '@geektech/one';

const card = createComponent(OneCard, {}, [
  { tag: 'h2', slot: 'header', children: ['Account'] },
  { tag: 'p', children: ['Default slot content'] },
  { component: OneButton, slot: 'footer', children: ['Continue'] },
]);

Global themes

The immutable default theme is always available. Register any number of partial themes; omitted colors, typography, border, and radius values inherit from ONE_DEFAULT_THEME.

import { ONE_DEFAULT_THEME, oneTheme } from '@geektech/one';

oneTheme.init({
  defaultTheme: 'brand',
  themes: {
    brand: {
      colors: { primary: '#326bff' },
      typography: { fontFamily: 'Inter, sans-serif', lineHeight: '1.6' },
      border: { color: '#cbd5e1', width: '1px', style: 'solid' },
      radius: { sm: '4px', md: '8px', lg: '12px' },
    },
    night: { colors: { surface: '#101510', text: '#f4f8f4' } },
  },
});

oneTheme.switch('default');
console.log(oneTheme.currentTheme, ONE_DEFAULT_THEME.colors.primary);

The service writes to document.documentElement only. It does not create local themes, persist the selected name, or follow the operating-system scheme. Invalid configuration throws OneThemeConfigError; unknown theme names throw OneThemeNotFoundError.

Internationalization (i18n)

One UI ships built-in zh-CN and en message dictionaries for component defaults: placeholders, aria labels, validation messages, pagination text and empty states. The global oneI18n singleton starts in zh-CN. Switch the locale at runtime and mounted components update automatically.

import { oneI18n } from '@geektech/one';

oneI18n.setLocale('en');

// Defaults now render in English:
// <OneEmpty /> → "No data", <OnePagination /> → "Previous / Next",
// form validation → "This field is required".

Components that can show built-in text extend OneLocalizedComponent and resolve defaults through this.t(). Explicit props always take precedence over translated defaults:

import { OneEmpty, oneI18n } from '@geektech/one';

const empty = new OneEmpty(); // renders "暂无数据"
const custom = new OneEmpty({ description: 'No results' }); // prop wins
oneI18n.setLocale('en'); // both components re-render; the prop stays

Translate your own messages by merging into the built-in dictionary, or create a fully isolated instance:

import { createOneI18n, oneI18n } from '@geektech/one';

oneI18n.mergeMessages({
  'zh-CN': { 'app.greeting': '你好,{name}!' },
  en: { 'app.greeting': 'Hello, {name}!' },
});
console.log(oneI18n.t('app.greeting', { name: 'One' }));

const custom = createOneI18n({ locale: 'en' }); // isolated instance

Lookup order is locale → fallbackLocale (default zh-CN) → key; messages use {placeholder} interpolation. OneLocalizedComponent instances subscribe on their first t() call and unsubscribe after unmount, so locale changes re-render only live components. Form validation messages are resolved at validation time — re-validate after a locale switch to see updated messages.

CSS variable overrides

One UI styles are scoped to .one-* selectors. Override public CSS variables on an application container or :root:

.my-app {
  --one-color-primary: #326bff;
  --one-radius-md: 12px;
}

The matching fallback values are also available from ONE_THEME_DEFAULTS.

Development

bun install
bun test packages/one
bunx tsc --noEmit --project packages/one/tsconfig.json
bun run --cwd packages/one build

Run the complete publish verification from the repository root:

bun test packages/one/tests/package-contract.test.ts packages/one/tests/component-types.test.ts packages/one/tests/style-contract.test.ts packages/one/tests/package-smoke.test.ts && bunx tsc --noEmit --project packages/one/tsconfig.json && bun run --cwd packages/one build && bun pm pack --cwd packages/one --dry-run