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

@bavuchoko/js-grid

v0.1.24

Published

React data grid (JsGrid) with toolbar, column layout, and pagination

Readme

React/TypeScript 기반의 그리드 컴포넌트(JsGrid)입니다. 컬럼 표시/숨김, 순서 변경, 고정(Alt+클릭), 정렬, 페이지네이션, 행 선택, 내장 툴바(컬럼 설정·전체화면 등), 커스텀 툴바(ToolbarAsyncAction·ToolbarDataTransfer·ToolbarUploadAction) 등을 제공합니다.

개발 실행

npm install
npm run dev

빠른 시작

JsGridheader(컬럼 정의)와 data(페이지 데이터)를 받아 렌더링합니다.

import type { Header, HeaderState, Page } from "./app/type/Type";
import JsGrid from "./app/JsGrid";

const header: Header[] = [
  { key: "title", label: "제목", type: "string" },
  { key: "createdAt", label: "등록일", type: "date" },
];

const data = {
  content: [
    { title: "첫 번째", createdAt: "2026-01-02" },
    { title: "두 번째", createdAt: "2026-01-03" },
  ],
  pageable: { pageNumber: 0, pageSize: 15, size: 15 },
  totalElements: 2,
  totalPages: 1,
};

export default function Example() {
  return (
    <JsGrid
      header={header}
      data={data}
      onPageChange={(p: Page) => console.log("pageable", p)}
    />
  );
}

Props (핵심)

데이터/컬럼

  • header?: Header[]: 컬럼 정의
    • key: 행 데이터에서 값을 꺼낼 키. 점 표기(creator.name) 지원
    • label: 헤더 텍스트
    • type: 'string' | 'number' | 'state' | 'date' | 'score'
    • render: 셀 커스텀 렌더링(선택)
      • 함수형: ({ row, value, columnKey, rowIndex }) => ReactNode
      • JSX/ReactNode: element일 경우 내부적으로 row/value/columnKey/rowIndex props를 주입하여 렌더링
    • editor: 편집 UI 컴포넌트(선택). editable={true}일 때만 해당 컬럼 본문 셀 더블클릭으로 연다. 함수형·JSX 주입 지원, onChange, onClose 주입.
    • filterable: true면 헤더에 엑셀 스타일 필터 아이콘(깔때기)이 표시되어 클라이언트 사이드 다중 선택 필터링이 활성화된다. (자세한 내용은 Header.filterable — 엑셀 스타일 헤더 필터 참고)
    • getFilterValue: 필터 후보값을 커스텀 추출(선택). 생략 시 keygetValue(row, key) 또는 children resolver를 사용.
  • data?: { content?: unknown[]; pageable?: Page; totalElements?: number; totalPages?: number }: 페이지 데이터

Header.render 사용 예시

함수형:

const header: Header[] = [
  {
    key: "title",
    label: "제목",
    type: "string",
    render: ({ value }) => <b>{String(value ?? "")}</b>,
  },
];

JSX element 주입형:

const MyCell = (props: any) => (
  <span>
    {props.rowIndex}: {String(props.value ?? "")}
  </span>
);

const header: Header[] = [
  { key: "title", label: "제목", type: "string", render: <MyCell /> },
];

Header.editor — 더블클릭 인라인 편집

JsGrideditable={true} 를 넘기고, editor가 정의된 컬럼만 본문 셀 더블클릭 시 편집 UI가 셀 위치에 열립니다. (editable이 없거나 falseeditor가 있어도 더블클릭 편집은 동작하지 않습니다.)

값 반영은 onCellChange 또는 에디터에서 직접 API 호출 후 data 갱신으로 처리합니다.

const header: Header[] = [
  {
    key: "assetName",
    label: "자산명",
    type: "string",
    editor: ({ value, onChange, onClose }) => (
      <input
        autoFocus
        value={String(value ?? "")}
        onChange={(e) => onChange(e.target.value)}
        onKeyDown={(e) => {
          if (e.key === "Enter") onChange((e.target as HTMLInputElement).value, { close: true });
          if (e.key === "Escape") onClose();
        }}
        style={{ width: "100%", boxSizing: "border-box" }}
      />
    ),
  },
];

<JsGrid
  editable={true}
  header={header}
  data={pageData}
  onCellChange={({ row, rowIndex, columnKey, value }) => {
    // row / API로 갱신 후 setState → data.content 재전달
    console.log("cell change", rowIndex, columnKey, value, row);
  }}
/>
  • onChange(nextValue, { close: true }) — 값 적용 후 팝업 닫기
  • onClose() — 적용 없이 닫기(모달·드로어 완료 버튼 등)
  • 인풋·달력·드로어·모달 등 임의 React 컴포넌트editor에 넣을 수 있습니다.

Header.filterable — 엑셀 스타일 헤더 필터

filterable: true를 지정한 컬럼은 헤더에 깔때기 아이콘이 표시되고, 클릭하면 엑셀과 비슷한 체크리스트 드롭다운이 열립니다.

  • 검색 input — 부분 일치(contains)로 옵션 좁히기
  • 전체 선택 — 검색 결과 한정, indeterminate 지원
  • 값 목록 — 현재 페이지 데이터에서 추출한 고유값 + 행 수, 가나다·숫자 정렬
  • (비어 있음)null / undefined / 빈 문자열은 자동으로 한 옵션으로 묶임
  • 초기화 / 적용 — 0개 체크 상태에서는 「적용」이 비활성화(필터를 풀려면 「초기화」)

여러 컬럼에 동시에 적용하면 AND로 결합되고, 현재 페이지 행만 필터링합니다(현재 페이지에 로드된 데이터 한정).

const header: Header[] = [
  { key: "status", label: "상태", type: "string", filterable: true },
  {
    key: "createdBy",
    label: "등록자",
    type: "string",
    filterable: true,
    // 중첩 객체에서 표시·필터 키 추출. 미지정 시 `getValue(row, "createdBy")` 사용.
    getFilterValue: (row) => (row as any).createdBy?.name,
  },
];

getFilterValue가 배열을 반환하면 각 원소가 별도 옵션으로 펼쳐지고, 그 중 하나라도 선택값에 매칭되면 행이 표시됩니다(다중 태그 셀에 유용).

클라이언트 사이드 필터입니다. 다른 페이지의 데이터는 옵션 목록에 나오지 않고, PaginationtotalElements는 서버 응답값이 그대로 유지됩니다.

이벤트/액션

  • onPageChange?: (pageable: Page) => void: 페이지/정렬 변경 시 호출 (서버 페이징 연결용)
  • onHeaderSave?: (headers: HeaderState[]) => void: 컬럼 visible 상태 저장 (현재 컬럼 설정 저장)
  • onHeaderReset?: () => void | Promise<void>: 컬럼 설정 메뉴에서 "초기화" 클릭 시 호출. async/Promise 처리 중에는 저장과 동일하게 툴바·본문 로딩이 표시된다.
  • onRowClick?: (row: unknown) => void: 체크박스를 제외한 행 클릭 시 호출 (클릭된 행의 데이터 객체 전달)
    • Header.render가 있는 셀은 기본적으로 onRowClick으로 이벤트가 전파되지 않습니다.
  • editable?: boolean: true일 때만 Header.editor 더블클릭 편집 활성 (기본 false)
  • onCellChange?: (event) => void | Promise<void>: Header.editor에서 onChange 호출 시 전달 (row, rowIndex, columnKey, value, previousValue)
  • enableRowSelection?: boolean: true면 체크박스 열·행 선택 UI를 표시한다. 삭제·보내기 등은 toolbarEnd의 **ToolbarDataTransfer**로 처리한다(선택과 콜백을 분리).
    • 선택 키는 기본 row.id(또는 rowSelectionIdKey / getRowSelectionId).
    • 조회 데이터·페이지 메타가 바뀌면 체크가 전부 해제된다(검색 조건 변경 후 잘못된 행 삭제 방지).
    • 페이지 이동 시에도 선택이 초기화된다.
  • enablePseudoFullscreen?: boolean: 전체화면(pseudo fullscreen) 토글 기능 사용 여부 (기본값: true)

스타일

  • style?: React.CSSProperties: JsGrid 루트 컨테이너 스타일 오버라이드
    • 기본 스타일은 유지되고, style을 넘기면 해당 값이 덮어써집니다.
    • pseudo fullscreen(전체화면)일 때는 레이아웃을 위해 일부 값(width/height/position/zIndex/boxShadow 등)이 강제로 적용됩니다.

예시:

<JsGrid
  header={header}
  data={data}
  onRowClick={(row) => console.log("row click", row)}
  style={{ height: 500, borderRadius: 12 }}
/>

툴바 커스텀 아이콘 / UI

그리드 기본 툴바는 컬럼 설정·전체화면 등입니다. 삭제·다운로드·업로드·필터 등은 toolbarStart / toolbarEnd에 커스텀 컴포넌트로 넣습니다.

| Prop | 타입 | 위치 | |------|------|------| | toolbarStart | ReactNode | (api) => ReactNode | 툴바 왼쪽 — 「헤더 고정」 안내(자물쇠) 오른쪽 | | toolbarEnd | ReactNode | (api) => ReactNode | 툴바 오른쪽 — 기본 액션 아이콘들 |

아이콘 크기는 기본 툴바와 맞추려면 약 18×18px을 권장합니다.

ToolbarDataTransfer — 선택 행 전달(삭제 등)

enableRowSelection과 함께 사용한다. 클릭 시 선택된 행 데이터 배열onTransfer가 호출되고, 성공 시 선택이 해제된다.

import JsGrid, { ToolbarDataTransfer } from "@bavuchoko/js-grid";
import Trash from "./TrashIcon";

<JsGrid
  enableRowSelection
  toolbarEnd={() => (
    <ToolbarDataTransfer
      hint="선택 항목 삭제"
      busyHint="삭제 중…"
      overlayLabel="삭제 중…"
      onTransfer={async (rows) => {
        await deleteApi(rows);
      }}
    >
      <Trash />
    </ToolbarDataTransfer>
  )}
/>

확인창·추가 검증은 children을 함수로 넘겨 transferSelected() 전에 처리할 수 있다.

| Prop | 적용 시점 | |------|-----------| | className | 기본 아이콘 래퍼에 항상 | | disabledClassName | 선택 없음 등 클릭 불가(not-allowed)일 때 | | busyClassName | onTransfer 처리 중(wait)일 때 |

<ToolbarDataTransfer
  className="my-grid-delete"
  disabledClassName="my-grid-delete--disabled"
  busyClassName="my-grid-delete--busy"
  onTransfer={onDelete}
>
  <Trash />
</ToolbarDataTransfer>

children을 함수로 쓰면 클래스는 직접 ctx.disabled / ctx.busy에 맞춰 붙이면 된다.

ToolbarAsyncAction으로 감싸서 넣기 (권장)

API 호출 등 Promise가 끝날 때까지 로딩(아이콘 스피너, 필요 시 본문 블러)을 쓰려면, 커스텀 아이콘을 ToolbarAsyncAction으로 감싼 뒤 toolbarStart / toolbarEnd에 넣으면 됩니다.

import JsGrid, { ToolbarAsyncAction } from "@bavuchoko/js-grid";

<JsGrid
  header={header}
  data={data}
  toolbarEnd={() => (
    <>
      <ToolbarAsyncAction
        hint="다운로드"
        busyHint="다운로드 중…"
        overlayLabel="다운로드 중…"
        onClick={async () => {
          await exportExcel();
        }}
      >
        <DownloadIcon />
      </ToolbarAsyncAction>
      <ToolbarAsyncAction hint="새로고침" onClick={onRefresh}>
        <RefreshIcon />
      </ToolbarAsyncAction>
    </>
  )}
/>

| ToolbarAsyncAction prop | 설명 | |-------------------------|------| | onClick | 클릭 시 실행. async + await 로 API가 끝날 때까지 스피너 유지 | | hint | 평소 툴팁 (@bavuchoko/js-tooltip) | | busyHint | 로딩 중 툴팁 (생략 시 hint 유지) | | overlayLabel | 지정 시 본문(테이블·페이지네이션) 블러 + 가운데 문구. 생략 시 아이콘 스피너만 | | accentColor | 스피너 색 (기본 #2563eb) | | disabled | 비활성 | | children | 아이콘·SVG 등 (18×18px 권장) |

  • onClick동기만 실행되면 스피너가 거의 보이지 않을 수 있습니다.
  • overlayLabel + 본문 블러를 쓰려면 toolbarStart / toolbarEnd함수형 () => (...) 으로 넘기는 것을 권장합니다.

ToolbarUploadAction — Excel 업로드 패널

클릭 시 내장과 동일한 업로드 패널(파일 목록·Excel accept·다중 업로드·패널 내 업로드 버튼)이 열립니다. 예전 onUploadFiles prop 대신 사용합니다.

import JsGrid, { ToolbarUploadAction, DEFAULT_EXCEL_UPLOAD_ACCEPT } from "@bavuchoko/js-grid";

<JsGrid
  header={header}
  data={data}
  toolbarEnd={() => (
    <ToolbarUploadAction
      hint="업로드"
      busyHint="업로드 중…"
      overlayLabel="업로드 중…"
      accept={DEFAULT_EXCEL_UPLOAD_ACCEPT}
      multiple={false}
      onUploadConfirm={async (files) => {
        await uploadApi(files);
      }}
    />
  )}
/>

| ToolbarUploadAction prop | 설명 | |----------------------------|------| | onUploadConfirm | 패널에서 업로드 확인 시 (files: File[]) => void \| Promise<void>. resolve 시 패널 닫힘, reject 시 패널에 오류 | | accept | <input accept> (기본 Excel 허용 문자열) | | multiple | 다중 파일 (기본 false) | | hint / busyHint / overlayLabel | 툴팁·본문 블러 문구 | | children | 업로드 아이콘 대체 UI (생략 시 기본 업로드 아이콘) |

toolbarEnd함수형 () => (...) 으로 넘겨야 업로드 API 처리 중 본문 블러가 연동됩니다.

아이콘 여러 개

개수 제한 없이, Fragment(<>...</>) 또는 div로 나열합니다.

<JsGrid
  toolbarStart={() => (
    <div style={{ display: "flex", alignItems: "center", gap: 16 }}>
      <ToolbarAsyncAction hint="검색" overlayLabel="검색 중…" onClick={onSearch}>
        <SearchIcon />
      </ToolbarAsyncAction>
      <ToolbarAsyncAction hint="새로고침" overlayLabel="새로고침 중…" onClick={onRefresh}>
        <RefreshIcon />
      </ToolbarAsyncAction>
    </div>
  )}
  toolbarEnd={() => (
    <>
      <ToolbarUploadAction onUploadConfirm={onUpload} />
      <ToolbarAsyncAction hint="필터" onClick={onFilter}>
        <FilterIcon />
      </ToolbarAsyncAction>
    </>
  )}
/>
  • 내장: enableRowSelection, onHeaderSave
  • 커스텀: ToolbarDataTransfer, ToolbarUploadAction, ToolbarAsyncAction
  • 왼쪽·오른쪽에 나눠 넣으려면 toolbarStart / toolbarEnd를 각각 사용합니다.
  • 동시에 여러 API를 돌리지 않도록, 필요하면 앱에서 클릭 가드·disabled를 추가하세요.

로딩 없이 버튼만 넣기

왼쪽에 필터 버튼

<JsGrid
  header={header}
  data={data}
  toolbarStart={
    <button type="button" onClick={() => openFilter()}>
      필터
    </button>
  }
/>

커스텀 영역은 클래스 js-grid-toolbar-custom, js-grid-toolbar-custom-start, js-grid-toolbar-custom-end로 감싸져 있어 필요 시 CSS로 간격·정렬을 조정할 수 있습니다.

본문 블러만 직접 제어 (runToolbarAction / setBodyOverlay)

아이콘 UI는 직접 만들고, 테이블 블러·오버레이만 그리드에 맡길 때 (ToolbarUploadActionsetBodyOverlay를 내부에서 사용):

import type { JsGridToolbarApi } from "@bavuchoko/js-grid";

<JsGrid
  toolbarEnd={(api: JsGridToolbarApi) => (
    <button
      type="button"
      onClick={() =>
        void api.runToolbarAction("처리 중…", async () => {
          await myApi();
        })
      }
    >
      실행
    </button>
  )}
/>

패키지 export: JsGrid, ToolbarAsyncAction, ToolbarDataTransfer, ToolbarUploadAction, DEFAULT_EXCEL_UPLOAD_ACCEPT, useJsGridToolbar, useJsGridRowSelection, JsGridToolbarApi, JsGridRowSelectionApi, JsGridToolbarSlot

UI 기능

  • 정렬: 헤더 클릭으로 정렬 변경(ASC/DESC)
  • 컬럼 고정: 헤더를 Alt+클릭하면 해당 컬럼까지 왼쪽 고정
  • 컬럼 필터: Header.filterable: true인 컬럼은 헤더 깔때기 아이콘으로 엑셀 스타일 다중 선택 필터(검색·전체 선택·카운트 표시)를 사용합니다. 여러 컬럼 동시 적용 시 AND 결합, 클라이언트 사이드(현재 페이지) 동작.
  • 컬럼 설정: 컬럼 표시/숨김 및 순서 변경(툴바의 컬럼 아이콘)
  • 컬럼 넓이 조정(리사이즈): 헤더 셀의 **오른쪽 경계(리사이즈 핸들)**를 드래그하여 너비를 조절
    • 체크박스/행번호 컬럼은 리사이즈 대상이 아닙니다.
    • 최소/최대 너비는 고정값으로 제한됩니다. (현재: 64px ~ 640px)
    • 저장/복원:
      • 사용자가 조정한 너비는 onHeaderSave(headers)로 내려오는 HeaderState.width에 포함됩니다.
      • 저장한 너비를 다시 적용하려면, 다음 렌더에서 해당 컬럼의 header.width(px)에 주입해 주세요.
  • pseudo fullscreen: 툴바의 전체화면 아이콘으로 토글 (ESC로 종료)
    • enablePseudoFullscreen={false}면 전체화면 버튼/동작이 비활성화됩니다.
  • 행 선택: enableRowSelection이면 체크박스 컬럼이 나타납니다. 선택은 행 id(등) 기준이며, 데이터 재조회·페이지 변경 시 선택이 초기화됩니다. 삭제 버튼은 ToolbarDataTransfertoolbarEnd에 넣습니다.

서버 페이징 연결 예시

<JsGrid
  header={header}
  data={pageResponse}
  onPageChange={(next) => {
    // next.pageNumber, next.pageSize/size, next.sort 등이 들어옵니다.
    fetchList(next);
  }}
/>