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

mycrm-ui

v0.1.1

Published

> A headless, fully-typed React table component library. > 헤드리스 방식의 완전한 타입 지원 React 테이블 컴포넌트 라이브러리.

Readme

mycrm-ui

A headless, fully-typed React table component library. 헤드리스 방식의 완전한 타입 지원 React 테이블 컴포넌트 라이브러리.

mycrm-ui provides a powerful and flexible <Table> component for React applications. It is headless — no styles are bundled. You own the CSS entirely via classNames.

mycrm-ui는 React 애플리케이션을 위한 강력하고 유연한 <Table> 컴포넌트를 제공합니다. 헤드리스 방식으로 번들된 스타일이 없으며, classNames를 통해 CSS를 완전히 직접 제어합니다.


Features / 특징

  • Headless / 헤드리스 — 스타일 없음, CSS 완전 제어
  • Fully typed / 완전한 타입 지원 — TypeScript 제네릭 기반
  • Sorting / 정렬 — 단일 정렬 및 다중 정렬
  • Selection / 행 선택 — 체크박스 기반 다중 선택
  • Inline editing / 인라인 편집 — 셀 직접 편집 및 유효성 검사
  • Row actions / 행 액션 — 행 추가 및 삭제
  • Filters / 필터 — text, select, dateRange, numberRange
  • Virtual scroll / 가상 스크롤 — 대용량 데이터 성능 최적화
  • Infinite load / 무한 로드 — 스크롤 기반 데이터 추가 로드
  • Column manager / 컬럼 매니저 — 숨김, 순서 변경, 좌우 고정, 너비 조절
  • Grouped rows / 그룹 행 — 다단계 중첩 그룹 지원
  • Keyboard navigation / 키보드 탐색 — 방향키 셀 탐색
  • Tooltip — 셀 오버플로우 시 자동 툴팁
  • Context menu / 컨텍스트 메뉴 — 셀 우클릭 메뉴

Requirements / 요구사항

  • React >= 18

Installation / 설치

npm install mycrm-ui

Quick Start / 빠른 시작

import { Table, type ColumnDef } from 'mycrm-ui'

interface User {
  id: string
  name: string
  email: string
}

const columns: ColumnDef<User>[] = [
  { key: 'id',    label: 'ID' },
  { key: 'name',  label: 'Name' },
  { key: 'email', label: 'Email' },
]

const data: User[] = [
  { id: '1', name: 'Alice', email: '[email protected]' },
  { id: '2', name: 'Bob',   email: '[email protected]' },
]

export default function App() {
  return (
    <Table
      columns={columns}
      data={data}
      rowKey={(row) => row.id}
    />
  )
}

Table Props

| Prop | Type | Description / 설명 | |------|------|---------------------| | columns | ColumnDef<T>[] | Column definitions / 컬럼 정의 목록 | | data | T[] | Row data / 행 데이터 | | rowKey | (row: T) => string | Unique key extractor per row / 행 고유 키 추출 함수 | | classNames | TableClassNames | CSS class overrides for each element / 각 요소별 CSS 클래스 지정 | | sorting | SortingProps | Sort state and change handlers / 정렬 상태 및 변경 핸들러 | | selection | SelectionProps | Checkbox row selection / 체크박스 행 선택 | | editing | EditingProps | Inline cell editing / 셀 인라인 편집 | | rowActions | RowActionProps | Row add / delete actions / 행 추가·삭제 액션 | | columnManager | ColumnManagerProps | Hide, reorder, pin, resize columns / 컬럼 숨김·순서·고정·너비 조절 | | filter | FilterProps | Per-column filtering / 컬럼별 필터 | | loading | LoadingProps | Skeleton loading and empty state / 스켈레톤 로딩 및 빈 상태 | | scroll | ScrollProps | Virtual scroll and infinite load / 가상 스크롤·무한 로드 | | expand | ExpandProps<T> | Grouped and nested rows / 그룹·중첩 행 | | headerMenuItems | { label: string; onClick: () => void }[] | Custom header dropdown menu items / 헤더 드롭다운 메뉴 항목 | | headerMenuIcon | React.ReactNode | Custom header menu trigger icon / 헤더 메뉴 트리거 아이콘 | | onRowClick | (row, rowKey, event) => void | Row single-click handler / 행 클릭 핸들러 | | onRowDoubleClick | (row, rowKey, event) => void | Row double-click handler / 행 더블클릭 핸들러 | | rowClassName | (row, rowKey) => string \| undefined | Dynamic CSS class per row / 행별 동적 CSS 클래스 | | tooltip | boolean | Show tooltip on overflowing cell content / 셀 오버플로우 시 툴팁 표시 | | onCellCopy | (rowKey, colKey, value) => void | Called when a cell value is copied / 셀 값 복사 시 콜백 | | cellContextMenuItems | { label: string; onClick: (rowKey, colKey, value) => void }[] | Right-click context menu for cells / 셀 우클릭 컨텍스트 메뉴 | | keyboardNavigation | boolean | Enable arrow-key cell navigation / 방향키 셀 탐색 활성화 | | onFocusedCellChange | (cell: { rowKey: string; colKey: string } \| null) => void | Called when focused cell changes / 포커스 셀 변경 시 콜백 |


ColumnDef

각 컬럼의 동작과 표현 방식을 정의합니다. Defines the behavior and appearance of each column.

interface ColumnDef<T> {
  key: string                    // 컬럼 식별자 / column identifier
  label: string                  // 헤더에 표시될 텍스트 / header label text

  render?: (row: T) => React.ReactNode          // 셀 렌더 함수 / custom cell renderer
  width?: string                                // 컬럼 너비 (e.g. '120px') / column width
  minWidth?: number                             // 최소 너비 (px) / minimum width in px
  align?: 'left' | 'center' | 'right'          // 셀 정렬 / cell text alignment

  sortable?: boolean             // 정렬 가능 여부 / enable sorting
  editable?: boolean             // 인라인 편집 가능 여부 / enable inline editing
  insertable?: boolean           // 행 추가 폼에 포함 여부 / include in add-row form
  copyable?: boolean             // 셀 복사 가능 여부 / enable cell copy

  filterType?: 'text' | 'select' | 'dateRange' | 'numberRange'
  filterOptions?: { label: string; value: string }[]   // select 타입 옵션 목록
  filterPlaceholder?: string     // 필터 입력 placeholder

  renderEditCell?: (props: EditCellProps) => React.ReactNode  // 커스텀 편집 셀 렌더러
  validate?: (value: string) => string | null                 // 편집값 유효성 검사
  onValidationError?: (error: string) => void                 // 유효성 오류 핸들러
}

Usage / 사용법

Sorting / 정렬

다중 정렬(multi-sort)과 단일 정렬(single sort) 두 가지 방식을 지원합니다. Both multi-sort and single sort modes are supported.

Multi-sort / 다중 정렬

const [sorts, setSorts] = useState<SortState[]>([])

<Table
  sorting={{ sorts, onSortsChange: setSorts }}
  ...
/>

Single sort / 단일 정렬

const [sort, setSort] = useState<SortState | null>(null)

<Table
  sorting={{ sort, onSortChange: setSort }}
  ...
/>

Selection / 행 선택

체크박스를 통해 여러 행을 선택할 수 있습니다. Supports multi-row selection via checkboxes.

const [selectedKeys, setSelectedKeys] = useState<string[]>([])

<Table
  selection={{ enabled: true, keys: selectedKeys, onChange: setSelectedKeys }}
  ...
/>

// 선택된 행 처리 / handling selected rows
<button
  onClick={() => console.log('selected:', selectedKeys)}
  disabled={selectedKeys.length === 0}
>
  Delete ({selectedKeys.length})
</button>

Inline Editing / 인라인 편집

ColumnDef에서 editable: true로 설정한 컬럼은 셀 클릭 시 편집 가능한 상태로 전환됩니다. Columns with editable: true become editable on cell click.

renderCell로 편집 UI를 완전히 커스터마이징할 수 있습니다. Use renderCell to fully customize the editing UI.

<Table
  editing={{
    onCellChange: (rowKey, colKey, value) => {
      // 변경된 값을 서버 또는 상태에 반영 / persist the updated value
      console.log(rowKey, colKey, value)
    },
    renderCell: ({ value, onChange, onSave, onCancel, error }) => (
      <span style={{ display: 'flex', flexDirection: 'column' }}>
        <input
          value={value}
          onChange={(e) => onChange(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === 'Enter') onSave()
            if (e.key === 'Escape') onCancel()
          }}
          autoFocus
        />
        {error && <span style={{ color: 'red', fontSize: 12 }}>{error}</span>}
      </span>
    ),
  }}
  ...
/>

컬럼별로 renderEditCell을 설정하면 해당 컬럼만 별도의 편집 UI를 사용할 수 있습니다. Per-column renderEditCell overrides the global renderCell for that column only.

const columns: ColumnDef<Row>[] = [
  {
    key: 'memo',
    label: '메모',
    editable: true,
    renderEditCell: ({ value, onChange, onSave, onCancel }) => (
      <textarea
        value={value}
        onChange={(e) => onChange(e.target.value)}
        onKeyDown={(e) => {
          if (e.key === 'Escape') onCancel()
          if (e.key === 'Enter' && e.metaKey) onSave()
        }}
        autoFocus
      />
    ),
  },
]

유효성 검사는 컬럼별로 validate 함수로 정의합니다. Per-column validation is defined via the validate function.

{
  key: 'name',
  label: '이름',
  editable: true,
  validate: (value) => {
    if (!value.trim()) return '이름을 입력해주세요'
    if (value.trim().length < 2) return '2글자 이상 입력해주세요'
    return null
  },
  onValidationError: (error) => alert(error),
}

Row Actions / 행 추가·삭제

insertable: true로 설정된 컬럼이 행 추가 폼에 포함됩니다. Columns with insertable: true are included in the add-row form.

const [isAdding, setIsAdding] = useState(false)

<Table
  rowActions={{
    adding: isAdding,
    onAdd: (values) => {
      // values: Record<string, string> — insertable 컬럼의 입력값
      console.log('추가할 데이터:', values)
      setIsAdding(false)
    },
    onAddCancel: () => setIsAdding(false),
    deletable: true,
    onDelete: (rowKey) => console.log('삭제:', rowKey),
  }}
  ...
/>

Filters / 필터

ColumnDef.filterType으로 컬럼별 필터 유형을 지정합니다. Set ColumnDef.filterType to enable per-column filtering.

  • 'text' — 텍스트 검색 / text search input
  • 'select' — 드롭다운 선택 / dropdown (requires filterOptions)
  • 'dateRange' — 날짜 범위 (시작일 ~ 종료일) / date range input
  • 'numberRange' — 숫자 범위 / number range input
const [filters, setFilters] = useState<Record<string, string>>({})
const [showFilter, setShowFilter] = useState(false)

<Table
  filter={{
    enabled: showFilter,
    values: filters,
    onChange: (key, val) => setFilters((prev) => ({ ...prev, [key]: val })),
    debounce: 300,   // 입력 후 반영까지의 딜레이(ms) / debounce delay in ms
  }}
  headerMenuItems={[
    { label: showFilter ? '필터 숨기기' : '필터 보기', onClick: () => setShowFilter((v) => !v) },
    { label: '필터 초기화', onClick: () => setFilters({}) },
  ]}
  ...
/>

Virtual Scroll + Infinite Load / 가상 스크롤 + 무한 로드

수만 건의 데이터도 부드럽게 렌더링됩니다. rowHeight는 각 행의 고정 높이(px)입니다. Renders tens of thousands of rows smoothly. rowHeight is the fixed height (px) per row.

<Table
  scroll={{
    virtual: true,
    rowHeight: 44,
    stickyHeader: true,
    onLoadMore: fetchNextPage,           // 다음 페이지 로드 함수
    hasMore: !!hasNextPage,              // 추가 데이터 존재 여부
    loadingMore: isFetchingNextPage,     // 로딩 중 여부
  }}
  ...
/>

Column Manager / 컬럼 매니저

헤더 메뉴를 통해 컬럼 숨김, 순서 변경, 좌우 고정, 너비 조절이 가능합니다. The header menu allows hiding, reordering, pinning, and resizing columns.

const [hiddenKeys, setHiddenKeys] = useState<string[]>([])
const [columnOrder, setColumnOrder] = useState(() => columns.map((c) => c.key))
const [pinnedKeys, setPinnedKeys] = useState<{ left?: string[]; right?: string[] }>({})
const [columnWidths, setColumnWidths] = useState<Record<string, number>>({})

<Table
  columnManager={{
    hiddenKeys,
    onHiddenKeysChange: setHiddenKeys,
    order: columnOrder,
    onOrderChange: setColumnOrder,
    pinned: pinnedKeys,
    onPinnedChange: setPinnedKeys,
    pinnedBg: { header: '#1e293b', body: '#0f172a' },  // 고정 컬럼 배경색
    resizable: true,
    widths: columnWidths,
    onWidthChange: (key, width) =>
      setColumnWidths((prev) => ({ ...prev, [key]: width })),
  }}
  ...
/>

Grouped / Nested Rows / 그룹·중첩 행

ExpandDef로 그룹 행을 정의하고 다단계 중첩도 지원합니다. Define group rows with ExpandDef. Multi-level nesting is supported via childExpandDef.

interface Group {
  label: string
  items: Item[]
}

const expandDef: ExpandDef<Group, Item> = {
  children: (group) => group.items,
  childRowKey: (item) => item.id,
  childColumns: itemColumns,
  renderGroupLabel: (group) => (
    <strong>{group.label} ({group.items.length}건)</strong>
  ),
}

const [expandedKeys, setExpandedKeys] = useState<string[]>([])

<Table<Group>
  columns={[]}
  data={groups}
  rowKey={(g) => g.label}
  expand={{
    def: expandDef,
    keys: expandedKeys,
    onKeysChange: setExpandedKeys,
    childSelection: {
      enabled: true,
      keys: childSelectedKeys,
      onChange: setChildSelectedKeys,
    },
    childDeletable: true,
    onChildDelete: (groupKey, childKey) => console.log(groupKey, childKey),
  }}
/>

Loading / Empty State / 로딩·빈 상태

<Table
  loading={{
    enabled: isLoading,
    rowCount: 10,                        // 스켈레톤 행 수 / skeleton row count
    emptyText: '데이터가 없습니다.',      // 빈 상태 텍스트 / empty state text
    renderEmpty: () => <p>데이터 없음</p>, // 커스텀 빈 상태 렌더러 / custom empty renderer
  }}
  ...
/>

Styling / 스타일링

컴포넌트는 헤드리스 방식으로, 번들된 스타일이 없습니다. classNames prop으로 각 요소에 CSS 클래스를 지정하세요.

The component is headless — no styles are bundled. Pass CSS classes to each element via the classNames prop.

<Table
  classNames={{
    wrap: styles.tableWrap,
    table: styles.table,
    th: styles.th,
    tr: styles.tr,
    td: styles.td,
    tdFocused: styles.tdFocused,           // 키보드 포커스 셀
    tdEditing: styles.tdEditing,           // 편집 중인 셀
    filterRow: styles.filterRow,
    filterInput: styles.filterInput,
    filterSelect: styles.filterSelect,
    skeletonRow: styles.skeletonRow,
    skeletonCell: styles.skeletonCell,
    groupRow: styles.groupRow,
    childRow: styles.childRow,
    contextMenu: styles.contextMenu,
    tooltip: styles.tooltip,
    // ... 전체 목록은 TableClassNames 타입 참고
    // ... see TableClassNames type for the full list
  }}
  ...
/>

Versioning / 버전 관리

이 라이브러리는 Semantic Versioning을 따릅니다. This library follows Semantic Versioning.

| 변경 유형 | 버전 변경 | |-----------|-----------| | 버그 수정 | patch (0.1.0 → 0.1.1) | | 기능 추가 (하위 호환) | minor (0.1.0 → 0.2.0) | | 하위 호환 불가 변경 | major (0.1.0 → 1.0.0) |


License

MIT