@comitor/ui 1.10.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -5
- package/dist/data-grid/compose-refs.d.ts +19 -0
- package/dist/data-grid/compose-refs.js +41 -0
- package/dist/data-grid/compose-refs.js.map +1 -0
- package/dist/data-grid/data-grid-cell-variants.d.ts +15 -0
- package/dist/data-grid/data-grid-cell-variants.js +1738 -0
- package/dist/data-grid/data-grid-cell-variants.js.map +1 -0
- package/dist/data-grid/data-grid-cell-wrapper.d.ts +9 -0
- package/dist/data-grid/data-grid-cell-wrapper.js +179 -0
- package/dist/data-grid/data-grid-cell-wrapper.js.map +1 -0
- package/dist/data-grid/data-grid-cell.d.ts +8 -0
- package/dist/data-grid/data-grid-cell.js +101 -0
- package/dist/data-grid/data-grid-cell.js.map +1 -0
- package/dist/data-grid/data-grid-column-header.d.ts +12 -0
- package/dist/data-grid/data-grid-column-header.js +207 -0
- package/dist/data-grid/data-grid-column-header.js.map +1 -0
- package/dist/data-grid/data-grid-context-menu.d.ts +12 -0
- package/dist/data-grid/data-grid-context-menu.js +161 -0
- package/dist/data-grid/data-grid-context-menu.js.map +1 -0
- package/dist/data-grid/data-grid-filter-menu.d.ts +23 -0
- package/dist/data-grid/data-grid-filter-menu.js +725 -0
- package/dist/data-grid/data-grid-filter-menu.js.map +1 -0
- package/dist/data-grid/data-grid-keyboard-shortcuts.d.ts +31 -0
- package/dist/data-grid/data-grid-keyboard-shortcuts.js +229 -0
- package/dist/data-grid/data-grid-keyboard-shortcuts.js.map +1 -0
- package/dist/data-grid/data-grid-locale.d.ts +24 -0
- package/dist/data-grid/data-grid-locale.js +25 -0
- package/dist/data-grid/data-grid-locale.js.map +1 -0
- package/dist/data-grid/data-grid-paste-dialog.d.ts +11 -0
- package/dist/data-grid/data-grid-paste-dialog.js +104 -0
- package/dist/data-grid/data-grid-paste-dialog.js.map +1 -0
- package/dist/data-grid/data-grid-presence.d.ts +14 -0
- package/dist/data-grid/data-grid-presence.js +16 -0
- package/dist/data-grid/data-grid-presence.js.map +1 -0
- package/dist/data-grid/data-grid-row-height-menu.d.ts +25 -0
- package/dist/data-grid/data-grid-row-height-menu.js +53 -0
- package/dist/data-grid/data-grid-row-height-menu.js.map +1 -0
- package/dist/data-grid/data-grid-row.d.ts +28 -0
- package/dist/data-grid/data-grid-row.js +206 -0
- package/dist/data-grid/data-grid-row.js.map +1 -0
- package/dist/data-grid/data-grid-search.d.ts +10 -0
- package/dist/data-grid/data-grid-search.js +184 -0
- package/dist/data-grid/data-grid-search.js.map +1 -0
- package/dist/data-grid/data-grid-select-column.d.ts +12 -0
- package/dist/data-grid/data-grid-select-column.js +179 -0
- package/dist/data-grid/data-grid-select-column.js.map +1 -0
- package/dist/data-grid/data-grid-skeleton.d.ts +13 -0
- package/dist/data-grid/data-grid-skeleton.js +52 -0
- package/dist/data-grid/data-grid-skeleton.js.map +1 -0
- package/dist/data-grid/data-grid-sort-menu.d.ts +23 -0
- package/dist/data-grid/data-grid-sort-menu.js +283 -0
- package/dist/data-grid/data-grid-sort-menu.js.map +1 -0
- package/dist/data-grid/data-grid-view-menu.d.ts +24 -0
- package/dist/data-grid/data-grid-view-menu.js +74 -0
- package/dist/data-grid/data-grid-view-menu.js.map +1 -0
- package/dist/data-grid/data-grid.d.ts +44 -0
- package/dist/data-grid/data-grid.js +238 -0
- package/dist/data-grid/data-grid.js.map +1 -0
- package/dist/data-grid/filters.d.ts +27 -0
- package/dist/data-grid/filters.js +233 -0
- package/dist/data-grid/filters.js.map +1 -0
- package/dist/data-grid/index.d.ts +25 -0
- package/dist/data-grid/index.js +95 -0
- package/dist/data-grid/index.js.map +1 -0
- package/dist/data-grid/messages-en.d.ts +294 -0
- package/dist/data-grid/messages-en.js +279 -0
- package/dist/data-grid/messages-en.js.map +1 -0
- package/dist/data-grid/messages-vi.d.ts +12 -0
- package/dist/data-grid/messages-vi.js +279 -0
- package/dist/data-grid/messages-vi.js.map +1 -0
- package/dist/data-grid/messages.d.ts +567 -0
- package/dist/data-grid/messages.js +20 -0
- package/dist/data-grid/messages.js.map +1 -0
- package/dist/data-grid/sortable.d.ts +50 -0
- package/dist/data-grid/sortable.js +361 -0
- package/dist/data-grid/sortable.js.map +1 -0
- package/dist/data-grid/toast.d.ts +10 -0
- package/dist/data-grid/toast.js +21 -0
- package/dist/data-grid/toast.js.map +1 -0
- package/dist/data-grid/types.d.ts +189 -0
- package/dist/data-grid/types.js +1 -0
- package/dist/data-grid/types.js.map +1 -0
- package/dist/data-grid/use-as-ref.d.ts +5 -0
- package/dist/data-grid/use-as-ref.js +13 -0
- package/dist/data-grid/use-as-ref.js.map +1 -0
- package/dist/data-grid/use-badge-overflow.d.ts +24 -0
- package/dist/data-grid/use-badge-overflow.js +123 -0
- package/dist/data-grid/use-badge-overflow.js.map +1 -0
- package/dist/data-grid/use-callback-ref.d.ts +10 -0
- package/dist/data-grid/use-callback-ref.js +12 -0
- package/dist/data-grid/use-callback-ref.js.map +1 -0
- package/dist/data-grid/use-data-grid-undo-redo.d.ts +45 -0
- package/dist/data-grid/use-data-grid-undo-redo.js +348 -0
- package/dist/data-grid/use-data-grid-undo-redo.js.map +1 -0
- package/dist/data-grid/use-data-grid.d.ts +372 -0
- package/dist/data-grid/use-data-grid.js +2697 -0
- package/dist/data-grid/use-data-grid.js.map +1 -0
- package/dist/data-grid/use-debounced-callback.d.ts +6 -0
- package/dist/data-grid/use-debounced-callback.js +19 -0
- package/dist/data-grid/use-debounced-callback.js.map +1 -0
- package/dist/data-grid/use-isomorphic-layout-effect.d.ts +5 -0
- package/dist/data-grid/use-isomorphic-layout-effect.js +6 -0
- package/dist/data-grid/use-isomorphic-layout-effect.js.map +1 -0
- package/dist/data-grid/use-lazy-ref.d.ts +5 -0
- package/dist/data-grid/use-lazy-ref.js +12 -0
- package/dist/data-grid/use-lazy-ref.js.map +1 -0
- package/dist/data-grid/utils.d.ts +81 -0
- package/dist/data-grid/utils.js +409 -0
- package/dist/data-grid/utils.js.map +1 -0
- package/package.json +33 -15
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/data-grid/data-grid-paste-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport type { TableMeta } from \"@tanstack/react-table\";\nimport * as React from \"react\";\nimport { Button } from \"../components/ui/button.js\";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle\n} from \"../components/ui/dialog.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { useDataGridMessages } from \"./data-grid-locale.js\";\nimport type { PasteDialogState } from \"./types.js\";\nimport { useAsRef } from \"./use-as-ref.js\";\n\ninterface DataGridPasteDialogProps<TData> {\n tableMeta: TableMeta<TData>;\n pasteDialog: PasteDialogState;\n}\n\nexport function DataGridPasteDialog<TData>({ tableMeta, pasteDialog }: DataGridPasteDialogProps<TData>) {\n const onPasteDialogOpenChange = tableMeta?.onPasteDialogOpenChange;\n const onCellsPaste = tableMeta?.onCellsPaste;\n\n if (!pasteDialog.open) return null;\n\n return (\n <PasteDialog\n pasteDialog={pasteDialog}\n onPasteDialogOpenChange={onPasteDialogOpenChange}\n onCellsPaste={onCellsPaste}\n />\n );\n}\n\ninterface PasteDialogProps\n extends Pick<TableMeta<unknown>, \"onPasteDialogOpenChange\" | \"onCellsPaste\">,\n Required<Pick<TableMeta<unknown>, \"pasteDialog\">> {}\n\nconst PasteDialog = React.memo(PasteDialogImpl, (prev, next) => {\n if (prev.pasteDialog.open !== next.pasteDialog.open) return false;\n if (!next.pasteDialog.open) return true;\n if (prev.pasteDialog.rowsNeeded !== next.pasteDialog.rowsNeeded) return false;\n\n return true;\n});\n\nfunction PasteDialogImpl({ pasteDialog, onPasteDialogOpenChange, onCellsPaste }: PasteDialogProps) {\n const msg = useDataGridMessages();\n\n const propsRef = useAsRef({\n onPasteDialogOpenChange,\n onCellsPaste\n });\n\n const expandRadioRef = React.useRef<HTMLInputElement | null>(null);\n\n const onOpenChange = React.useCallback(\n (open: boolean) => {\n propsRef.current.onPasteDialogOpenChange?.(open);\n },\n [propsRef]\n );\n\n const onCancel = React.useCallback(() => {\n propsRef.current.onPasteDialogOpenChange?.(false);\n }, [propsRef]);\n\n const onContinue = React.useCallback(() => {\n propsRef.current.onCellsPaste?.(expandRadioRef.current?.checked ?? false);\n }, [propsRef]);\n\n return (\n <Dialog open={pasteDialog.open} onOpenChange={onOpenChange}>\n <DialogContent data-grid-popover=\"\">\n <DialogHeader>\n <DialogTitle>{msg.pasteDialog.title}</DialogTitle>\n <DialogDescription>{msg.pasteDialog.description(pasteDialog.rowsNeeded)}</DialogDescription>\n </DialogHeader>\n <div className=\"flex flex-col gap-3 py-1\">\n {/* biome-ignore lint/a11y/noLabelWithoutControl: <RadioItem> bọc sẵn <input type=\"radio\"> bên trong */}\n <label className=\"flex cursor-pointer items-start gap-3\">\n <RadioItem ref={expandRadioRef} name=\"expand-option\" value=\"expand\" defaultChecked />\n <div className=\"flex flex-col gap-1\">\n <span className=\"font-medium text-sm leading-none\">{msg.pasteDialog.expandTitle}</span>\n <span className=\"text-muted-foreground text-sm\">\n {msg.pasteDialog.expandDescription(pasteDialog.rowsNeeded)}\n </span>\n </div>\n </label>\n {/* biome-ignore lint/a11y/noLabelWithoutControl: <RadioItem> bọc sẵn <input type=\"radio\"> bên trong */}\n <label className=\"flex cursor-pointer items-start gap-3\">\n <RadioItem name=\"expand-option\" value=\"no-expand\" />\n <div className=\"flex flex-col gap-1\">\n <span className=\"font-medium text-sm leading-none\">{msg.pasteDialog.keepTitle}</span>\n <span className=\"text-muted-foreground text-sm\">{msg.pasteDialog.keepDescription}</span>\n </div>\n </label>\n </div>\n <DialogFooter className=\"gap-2\">\n <Button variant=\"outline\" onClick={onCancel}>\n {msg.pasteDialog.cancel}\n </Button>\n <Button onClick={onContinue}>{msg.pasteDialog.confirm}</Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n\n/**\n * Radio thuần HTML (không dùng RadioGroup của Radix) để bấm vào cả khối `<label>`\n * là chọn được, và để đọc trạng thái qua ref lúc bấm \"Tiếp tục\".\n */\nfunction RadioItem({ className, ...props }: React.ComponentProps<\"input\">) {\n return (\n <input\n type=\"radio\"\n className={cn(\n // Cùng bộ token với `RadioGroupItem` của gói (`--choice-edge` cho viền nghỉ,\n // `--control-on` cho viền + chấm khi chọn) — đừng quay lại `--primary`/`--ring`: đó là\n // bậc tô NỀN, đem vẽ NÉT thì ở light chỉ 1,73:1.\n \"relative mt-0.5 size-4 shrink-0 appearance-none rounded-full border border-choice-edge outline-none transition-colors dark:bg-input-fill\",\n \"hover:border-choice-edge-strong checked:border-control-on\",\n \"focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n \"checked:before:absolute checked:before:start-1/2 checked:before:top-1/2 checked:before:size-2 checked:before:-translate-x-1/2 checked:before:-translate-y-1/2 checked:before:rounded-full checked:before:bg-control-on checked:before:content-['']\",\n className\n )}\n {...props}\n />\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
interface DataGridCellPresence {
|
|
4
|
+
color: string;
|
|
5
|
+
name: string;
|
|
6
|
+
}
|
|
7
|
+
interface DataGridPresenceProviderProps {
|
|
8
|
+
value: Map<string, DataGridCellPresence>;
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
declare function DataGridPresenceProvider({ value, children }: DataGridPresenceProviderProps): React.JSX.Element;
|
|
12
|
+
declare function useDataGridPresence(cellKey: string): DataGridCellPresence | null;
|
|
13
|
+
|
|
14
|
+
export { type DataGridCellPresence, DataGridPresenceProvider, useDataGridPresence };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
const DataGridCellPresenceContext = React.createContext(null);
|
|
5
|
+
function DataGridPresenceProvider({ value, children }) {
|
|
6
|
+
return /* @__PURE__ */ jsx(DataGridCellPresenceContext.Provider, { value, children });
|
|
7
|
+
}
|
|
8
|
+
function useDataGridPresence(cellKey) {
|
|
9
|
+
const map = React.useContext(DataGridCellPresenceContext);
|
|
10
|
+
return map?.get(cellKey) ?? null;
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
DataGridPresenceProvider,
|
|
14
|
+
useDataGridPresence
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=data-grid-presence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/data-grid/data-grid-presence.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\ninterface DataGridCellPresence {\n color: string;\n name: string;\n}\n\nconst DataGridCellPresenceContext = React.createContext<Map<string, DataGridCellPresence> | null>(null);\n\ninterface DataGridPresenceProviderProps {\n value: Map<string, DataGridCellPresence>;\n children: React.ReactNode;\n}\n\nfunction DataGridPresenceProvider({ value, children }: DataGridPresenceProviderProps) {\n return <DataGridCellPresenceContext.Provider value={value}>{children}</DataGridCellPresenceContext.Provider>;\n}\n\nfunction useDataGridPresence(cellKey: string): DataGridCellPresence | null {\n const map = React.useContext(DataGridCellPresenceContext);\n return map?.get(cellKey) ?? null;\n}\n\nexport { type DataGridCellPresence, DataGridPresenceProvider, useDataGridPresence };\n"],"mappings":";AAiBS;AAfT,YAAY,WAAW;AAOvB,MAAM,8BAA8B,MAAM,cAAwD,IAAI;AAOtG,SAAS,yBAAyB,EAAE,OAAO,SAAS,GAAkC;AACpF,SAAO,oBAAC,4BAA4B,UAA5B,EAAqC,OAAe,UAAS;AACvE;AAEA,SAAS,oBAAoB,SAA8C;AACzE,QAAM,MAAM,MAAM,WAAW,2BAA2B;AACxD,SAAO,KAAK,IAAI,OAAO,KAAK;AAC9B;","names":[]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Table } from '@tanstack/react-table';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { SelectContent } from '../components/ui/select.js';
|
|
4
|
+
import { DataGridLocale } from './messages.js';
|
|
5
|
+
import { DataGridMessages } from './messages-en.js';
|
|
6
|
+
import '@radix-ui/react-select';
|
|
7
|
+
import './messages-vi.js';
|
|
8
|
+
|
|
9
|
+
interface DataGridRowHeightMenuProps<TData> extends React.ComponentProps<typeof SelectContent> {
|
|
10
|
+
table: Table<TData>;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
/** className cho nút mở menu (mặc định cao 8 như các nút khác trên thanh công cụ) */
|
|
13
|
+
triggerClassName?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Ngôn ngữ của menu: `"en"` (mặc định) · `"vi"`, hoặc cả một bảng chuỗi tự soạn.
|
|
16
|
+
*
|
|
17
|
+
* ⚠ Thanh công cụ nằm NGOÀI `<DataGrid>`, nên `locale` truyền cho lưới không với tới đây. Bọc
|
|
18
|
+
* cả màn bằng `DataGridLocaleProvider`, hoặc truyền prop này cho từng menu. Prop thắng provider.
|
|
19
|
+
*/
|
|
20
|
+
locale?: DataGridLocale | DataGridMessages;
|
|
21
|
+
}
|
|
22
|
+
/** Chọn chiều cao dòng của lưới (thấp/vừa/cao/rất cao) — đặt trên thanh công cụ. */
|
|
23
|
+
declare function DataGridRowHeightMenu<TData>({ table, disabled, triggerClassName, locale, ...props }: DataGridRowHeightMenuProps<TData>): React.JSX.Element;
|
|
24
|
+
|
|
25
|
+
export { DataGridRowHeightMenu };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { AlignVerticalSpaceAroundIcon, ChevronsDownUpIcon, EqualIcon, MinusIcon } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../components/ui/select.js";
|
|
6
|
+
import { cn } from "../lib/cn.js";
|
|
7
|
+
import { useResolvedDataGridMessages } from "./data-grid-locale.js";
|
|
8
|
+
function buildRowHeights(msg) {
|
|
9
|
+
return [
|
|
10
|
+
{ label: msg.rowHeight.short, value: "short", icon: MinusIcon },
|
|
11
|
+
{ label: msg.rowHeight.medium, value: "medium", icon: EqualIcon },
|
|
12
|
+
{ label: msg.rowHeight.tall, value: "tall", icon: AlignVerticalSpaceAroundIcon },
|
|
13
|
+
{ label: msg.rowHeight.extraTall, value: "extra-tall", icon: ChevronsDownUpIcon }
|
|
14
|
+
];
|
|
15
|
+
}
|
|
16
|
+
function DataGridRowHeightMenu({
|
|
17
|
+
table,
|
|
18
|
+
disabled,
|
|
19
|
+
triggerClassName,
|
|
20
|
+
locale,
|
|
21
|
+
...props
|
|
22
|
+
}) {
|
|
23
|
+
const msg = useResolvedDataGridMessages(locale);
|
|
24
|
+
const rowHeights = React.useMemo(() => buildRowHeights(msg), [msg]);
|
|
25
|
+
const rowHeight = table.options.meta?.rowHeight;
|
|
26
|
+
const onRowHeightChange = table.options.meta?.onRowHeightChange;
|
|
27
|
+
const selectedRowHeight = React.useMemo(
|
|
28
|
+
() => rowHeights.find((option) => option.value === rowHeight) ?? rowHeights[0],
|
|
29
|
+
[rowHeight, rowHeights]
|
|
30
|
+
);
|
|
31
|
+
return /* @__PURE__ */ jsxs(Select, { value: rowHeight, onValueChange: onRowHeightChange, disabled, children: [
|
|
32
|
+
/* @__PURE__ */ jsx(
|
|
33
|
+
SelectTrigger,
|
|
34
|
+
{
|
|
35
|
+
"aria-label": msg.rowHeight.label,
|
|
36
|
+
size: "sm",
|
|
37
|
+
className: cn("w-auto gap-2 font-normal", triggerClassName),
|
|
38
|
+
children: /* @__PURE__ */ jsx(SelectValue, { placeholder: msg.rowHeight.label, children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
39
|
+
/* @__PURE__ */ jsx(selectedRowHeight.icon, { "aria-hidden": "true", className: "size-3.5 text-muted-foreground" }),
|
|
40
|
+
selectedRowHeight.label
|
|
41
|
+
] }) })
|
|
42
|
+
}
|
|
43
|
+
),
|
|
44
|
+
/* @__PURE__ */ jsx(SelectContent, { ...props, children: /* @__PURE__ */ jsx(SelectGroup, { children: rowHeights.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option.value, children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
45
|
+
/* @__PURE__ */ jsx(option.icon, { "aria-hidden": "true", className: "size-4 text-muted-foreground" }),
|
|
46
|
+
option.label
|
|
47
|
+
] }) }, option.value)) }) })
|
|
48
|
+
] });
|
|
49
|
+
}
|
|
50
|
+
export {
|
|
51
|
+
DataGridRowHeightMenu
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=data-grid-row-height-menu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/data-grid/data-grid-row-height-menu.tsx"],"sourcesContent":["\"use client\";\n\nimport type { Table } from \"@tanstack/react-table\";\nimport { AlignVerticalSpaceAroundIcon, ChevronsDownUpIcon, EqualIcon, MinusIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from \"../components/ui/select.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { useResolvedDataGridMessages } from \"./data-grid-locale.js\";\nimport type { DataGridLocale, DataGridMessages } from \"./messages.js\";\n\n/*\n * Bốn mức chiều cao. PHẢI dựng theo bảng chuỗi chứ không đóng băng thành hằng ở đầu module —\n * nhãn (\"Short\" / \"Thấp\") đổi theo ngôn ngữ, còn `value` và icon thì không.\n */\nfunction buildRowHeights(msg: DataGridMessages) {\n // Tuple (không phải mảng) để `rowHeights[0]` có kiểu chắc chắn — bốn mức là cố định.\n return [\n { label: msg.rowHeight.short, value: \"short\" as const, icon: MinusIcon },\n { label: msg.rowHeight.medium, value: \"medium\" as const, icon: EqualIcon },\n { label: msg.rowHeight.tall, value: \"tall\" as const, icon: AlignVerticalSpaceAroundIcon },\n { label: msg.rowHeight.extraTall, value: \"extra-tall\" as const, icon: ChevronsDownUpIcon }\n ] as const;\n}\n\ninterface DataGridRowHeightMenuProps<TData> extends React.ComponentProps<typeof SelectContent> {\n table: Table<TData>;\n disabled?: boolean;\n /** className cho nút mở menu (mặc định cao 8 như các nút khác trên thanh công cụ) */\n triggerClassName?: string;\n /**\n * Ngôn ngữ của menu: `\"en\"` (mặc định) · `\"vi\"`, hoặc cả một bảng chuỗi tự soạn.\n *\n * ⚠ Thanh công cụ nằm NGOÀI `<DataGrid>`, nên `locale` truyền cho lưới không với tới đây. Bọc\n * cả màn bằng `DataGridLocaleProvider`, hoặc truyền prop này cho từng menu. Prop thắng provider.\n */\n locale?: DataGridLocale | DataGridMessages;\n}\n\n/** Chọn chiều cao dòng của lưới (thấp/vừa/cao/rất cao) — đặt trên thanh công cụ. */\nexport function DataGridRowHeightMenu<TData>({\n table,\n disabled,\n triggerClassName,\n locale,\n ...props\n}: DataGridRowHeightMenuProps<TData>) {\n const msg = useResolvedDataGridMessages(locale);\n const rowHeights = React.useMemo(() => buildRowHeights(msg), [msg]);\n const rowHeight = table.options.meta?.rowHeight;\n const onRowHeightChange = table.options.meta?.onRowHeightChange;\n\n const selectedRowHeight = React.useMemo(\n () => rowHeights.find((option) => option.value === rowHeight) ?? rowHeights[0],\n [rowHeight, rowHeights]\n );\n\n return (\n <Select value={rowHeight} onValueChange={onRowHeightChange} disabled={disabled}>\n <SelectTrigger\n aria-label={msg.rowHeight.label}\n size=\"sm\"\n className={cn(\"w-auto gap-2 font-normal\", triggerClassName)}\n >\n <SelectValue placeholder={msg.rowHeight.label}>\n <span className=\"flex items-center gap-2\">\n <selectedRowHeight.icon aria-hidden=\"true\" className=\"size-3.5 text-muted-foreground\" />\n {selectedRowHeight.label}\n </span>\n </SelectValue>\n </SelectTrigger>\n <SelectContent {...props}>\n <SelectGroup>\n {rowHeights.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n <span className=\"flex items-center gap-2\">\n <option.icon aria-hidden=\"true\" className=\"size-4 text-muted-foreground\" />\n {option.label}\n </span>\n </SelectItem>\n ))}\n </SelectGroup>\n </SelectContent>\n </Select>\n );\n}\n"],"mappings":";AAgEU,SACE,KADF;AA7DV,SAAS,8BAA8B,oBAAoB,WAAW,iBAAiB;AACvF,YAAY,WAAW;AACvB,SAAS,QAAQ,eAAe,aAAa,YAAY,eAAe,mBAAmB;AAC3F,SAAS,UAAU;AACnB,SAAS,mCAAmC;AAO5C,SAAS,gBAAgB,KAAuB;AAE9C,SAAO;AAAA,IACL,EAAE,OAAO,IAAI,UAAU,OAAO,OAAO,SAAkB,MAAM,UAAU;AAAA,IACvE,EAAE,OAAO,IAAI,UAAU,QAAQ,OAAO,UAAmB,MAAM,UAAU;AAAA,IACzE,EAAE,OAAO,IAAI,UAAU,MAAM,OAAO,QAAiB,MAAM,6BAA6B;AAAA,IACxF,EAAE,OAAO,IAAI,UAAU,WAAW,OAAO,cAAuB,MAAM,mBAAmB;AAAA,EAC3F;AACF;AAiBO,SAAS,sBAA6B;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAsC;AACpC,QAAM,MAAM,4BAA4B,MAAM;AAC9C,QAAM,aAAa,MAAM,QAAQ,MAAM,gBAAgB,GAAG,GAAG,CAAC,GAAG,CAAC;AAClE,QAAM,YAAY,MAAM,QAAQ,MAAM;AACtC,QAAM,oBAAoB,MAAM,QAAQ,MAAM;AAE9C,QAAM,oBAAoB,MAAM;AAAA,IAC9B,MAAM,WAAW,KAAK,CAAC,WAAW,OAAO,UAAU,SAAS,KAAK,WAAW,CAAC;AAAA,IAC7E,CAAC,WAAW,UAAU;AAAA,EACxB;AAEA,SACE,qBAAC,UAAO,OAAO,WAAW,eAAe,mBAAmB,UAC1D;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,cAAY,IAAI,UAAU;AAAA,QAC1B,MAAK;AAAA,QACL,WAAW,GAAG,4BAA4B,gBAAgB;AAAA,QAE1D,8BAAC,eAAY,aAAa,IAAI,UAAU,OACtC,+BAAC,UAAK,WAAU,2BACd;AAAA,8BAAC,kBAAkB,MAAlB,EAAuB,eAAY,QAAO,WAAU,kCAAiC;AAAA,UACrF,kBAAkB;AAAA,WACrB,GACF;AAAA;AAAA,IACF;AAAA,IACA,oBAAC,iBAAe,GAAG,OACjB,8BAAC,eACE,qBAAW,IAAI,CAAC,WACf,oBAAC,cAA8B,OAAO,OAAO,OAC3C,+BAAC,UAAK,WAAU,2BACd;AAAA,0BAAC,OAAO,MAAP,EAAY,eAAY,QAAO,WAAU,gCAA+B;AAAA,MACxE,OAAO;AAAA,OACV,KAJe,OAAO,KAKxB,CACD,GACH,GACF;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Row, TableMeta, VisibilityState, ColumnPinningState } from '@tanstack/react-table';
|
|
2
|
+
import { VirtualItem } from '@tanstack/react-virtual';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { RowHeightValue, CellPosition, Direction } from './types.js';
|
|
5
|
+
|
|
6
|
+
interface DataGridRowProps<TData> extends React.ComponentProps<"div"> {
|
|
7
|
+
row: Row<TData>;
|
|
8
|
+
tableMeta: TableMeta<TData>;
|
|
9
|
+
virtualItem: VirtualItem;
|
|
10
|
+
measureElement: (node: Element | null) => void;
|
|
11
|
+
rowMapRef: React.RefObject<Map<number, HTMLDivElement>>;
|
|
12
|
+
rowHeight: RowHeightValue;
|
|
13
|
+
columnVisibility: VisibilityState;
|
|
14
|
+
columnPinning: ColumnPinningState;
|
|
15
|
+
focusedCell: CellPosition | null;
|
|
16
|
+
editingCell: CellPosition | null;
|
|
17
|
+
cellSelectionKeys: Set<string>;
|
|
18
|
+
searchMatchColumns: Set<string> | null;
|
|
19
|
+
activeSearchMatch: CellPosition | null;
|
|
20
|
+
dir: Direction;
|
|
21
|
+
readOnly: boolean;
|
|
22
|
+
stretchColumns: boolean;
|
|
23
|
+
adjustLayout: boolean;
|
|
24
|
+
}
|
|
25
|
+
declare const DataGridRow: typeof DataGridRowImpl;
|
|
26
|
+
declare function DataGridRowImpl<TData>({ row, tableMeta, virtualItem, measureElement, rowMapRef, rowHeight, columnVisibility, columnPinning, focusedCell, editingCell, cellSelectionKeys, searchMatchColumns, activeSearchMatch, dir, readOnly, stretchColumns, adjustLayout, className, style, ref, ...props }: DataGridRowProps<TData>): React.JSX.Element;
|
|
27
|
+
|
|
28
|
+
export { DataGridRow };
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
import { useComposedRefs } from "./compose-refs.js";
|
|
6
|
+
import { DataGridCell } from "./data-grid-cell.js";
|
|
7
|
+
import {
|
|
8
|
+
flexRender,
|
|
9
|
+
getCellKey,
|
|
10
|
+
getColumnBorderVisibility,
|
|
11
|
+
getColumnPinningStyle,
|
|
12
|
+
getRowHeightValue
|
|
13
|
+
} from "./utils.js";
|
|
14
|
+
const DataGridRow = React.memo(DataGridRowImpl, (prev, next) => {
|
|
15
|
+
const prevRowIndex = prev.virtualItem.index;
|
|
16
|
+
const nextRowIndex = next.virtualItem.index;
|
|
17
|
+
if (prev.row.id !== next.row.id) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if (prev.row.original !== next.row.original) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (prev.virtualItem.start !== next.virtualItem.start) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
const prevHasFocus = prev.focusedCell?.rowIndex === prevRowIndex;
|
|
27
|
+
const nextHasFocus = next.focusedCell?.rowIndex === nextRowIndex;
|
|
28
|
+
if (prevHasFocus !== nextHasFocus) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
if (nextHasFocus && prevHasFocus) {
|
|
32
|
+
if (prev.focusedCell?.columnId !== next.focusedCell?.columnId) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const prevHasEditing = prev.editingCell?.rowIndex === prevRowIndex;
|
|
37
|
+
const nextHasEditing = next.editingCell?.rowIndex === nextRowIndex;
|
|
38
|
+
if (prevHasEditing !== nextHasEditing) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
if (nextHasEditing && prevHasEditing) {
|
|
42
|
+
if (prev.editingCell?.columnId !== next.editingCell?.columnId) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (prev.cellSelectionKeys !== next.cellSelectionKeys) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (prev.columnVisibility !== next.columnVisibility) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if (prev.rowHeight !== next.rowHeight) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
if (prev.columnPinning !== next.columnPinning) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
if (prev.readOnly !== next.readOnly) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if (prev.searchMatchColumns !== next.searchMatchColumns) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
if (prev.activeSearchMatch?.columnId !== next.activeSearchMatch?.columnId) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
if (prev.dir !== next.dir) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (prev.adjustLayout !== next.adjustLayout) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
if (prev.stretchColumns !== next.stretchColumns) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
});
|
|
78
|
+
function DataGridRowImpl({
|
|
79
|
+
row,
|
|
80
|
+
tableMeta,
|
|
81
|
+
virtualItem,
|
|
82
|
+
measureElement,
|
|
83
|
+
rowMapRef,
|
|
84
|
+
rowHeight,
|
|
85
|
+
columnVisibility,
|
|
86
|
+
columnPinning,
|
|
87
|
+
focusedCell,
|
|
88
|
+
editingCell,
|
|
89
|
+
cellSelectionKeys,
|
|
90
|
+
searchMatchColumns,
|
|
91
|
+
activeSearchMatch,
|
|
92
|
+
dir,
|
|
93
|
+
readOnly,
|
|
94
|
+
stretchColumns,
|
|
95
|
+
adjustLayout,
|
|
96
|
+
className,
|
|
97
|
+
style,
|
|
98
|
+
ref,
|
|
99
|
+
...props
|
|
100
|
+
}) {
|
|
101
|
+
const virtualRowIndex = virtualItem.index;
|
|
102
|
+
const onRowChange = React.useCallback(
|
|
103
|
+
(node) => {
|
|
104
|
+
if (typeof virtualRowIndex === "undefined") return;
|
|
105
|
+
if (node) {
|
|
106
|
+
measureElement(node);
|
|
107
|
+
rowMapRef.current?.set(virtualRowIndex, node);
|
|
108
|
+
} else {
|
|
109
|
+
rowMapRef.current?.delete(virtualRowIndex);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
[virtualRowIndex, measureElement, rowMapRef]
|
|
113
|
+
);
|
|
114
|
+
const rowRef = useComposedRefs(ref, onRowChange);
|
|
115
|
+
const isRowSelected = row.getIsSelected();
|
|
116
|
+
const visibleCells = React.useMemo(() => row.getVisibleCells(), [row, columnVisibility, columnPinning]);
|
|
117
|
+
return /* @__PURE__ */ jsx(
|
|
118
|
+
"div",
|
|
119
|
+
{
|
|
120
|
+
role: "row",
|
|
121
|
+
"aria-rowindex": virtualRowIndex + 2,
|
|
122
|
+
"aria-selected": isRowSelected,
|
|
123
|
+
"data-index": virtualRowIndex,
|
|
124
|
+
"data-slot": "grid-row",
|
|
125
|
+
tabIndex: -1,
|
|
126
|
+
...props,
|
|
127
|
+
ref: rowRef,
|
|
128
|
+
className: cn(
|
|
129
|
+
"absolute flex w-full border-b [content-visibility:auto]",
|
|
130
|
+
!adjustLayout && "will-change-transform",
|
|
131
|
+
className
|
|
132
|
+
),
|
|
133
|
+
style: {
|
|
134
|
+
height: `${getRowHeightValue(rowHeight)}px`,
|
|
135
|
+
...adjustLayout ? { top: `${virtualItem.start}px` } : { transform: `translateY(${virtualItem.start}px)` },
|
|
136
|
+
...style
|
|
137
|
+
},
|
|
138
|
+
children: visibleCells.map((cell, colIndex) => {
|
|
139
|
+
const columnId = cell.column.id;
|
|
140
|
+
const isCellFocused = focusedCell?.rowIndex === virtualRowIndex && focusedCell?.columnId === columnId;
|
|
141
|
+
const isCellEditing = editingCell?.rowIndex === virtualRowIndex && editingCell?.columnId === columnId;
|
|
142
|
+
const isCellSelected = cellSelectionKeys?.has(getCellKey(virtualRowIndex, columnId)) ?? false;
|
|
143
|
+
const isSearchMatch = searchMatchColumns?.has(columnId) ?? false;
|
|
144
|
+
const isActiveSearchMatch = activeSearchMatch?.columnId === columnId;
|
|
145
|
+
const nextCell = visibleCells[colIndex + 1];
|
|
146
|
+
const isLastColumn = colIndex === visibleCells.length - 1;
|
|
147
|
+
const { showEndBorder, showStartBorder } = getColumnBorderVisibility({
|
|
148
|
+
column: cell.column,
|
|
149
|
+
nextColumn: nextCell?.column,
|
|
150
|
+
isLastColumn
|
|
151
|
+
});
|
|
152
|
+
return /* @__PURE__ */ jsx(
|
|
153
|
+
"div",
|
|
154
|
+
{
|
|
155
|
+
role: "gridcell",
|
|
156
|
+
"aria-colindex": colIndex + 1,
|
|
157
|
+
"data-highlighted": isCellFocused ? "" : void 0,
|
|
158
|
+
"data-slot": "grid-cell",
|
|
159
|
+
tabIndex: -1,
|
|
160
|
+
className: cn({
|
|
161
|
+
grow: stretchColumns && columnId !== "select",
|
|
162
|
+
"border-e": showEndBorder && columnId !== "select",
|
|
163
|
+
"border-s": showStartBorder && columnId !== "select"
|
|
164
|
+
}),
|
|
165
|
+
style: {
|
|
166
|
+
...getColumnPinningStyle({ column: cell.column, dir }),
|
|
167
|
+
width: `calc(var(--col-${columnId}-size) * 1px)`
|
|
168
|
+
},
|
|
169
|
+
children: typeof cell.column.columnDef.header === "function" ? /* @__PURE__ */ jsx(
|
|
170
|
+
"div",
|
|
171
|
+
{
|
|
172
|
+
className: cn("size-full px-3 py-1.5", {
|
|
173
|
+
// `--accent` là bậc tint TRẠNG THÁI của gói (Tabs/Switch/Progress dùng đúng nó
|
|
174
|
+
// để nói "đang bật"). `bg-primary/10` là gold pha 10% — 1,05:1, một dấu hiệu giả.
|
|
175
|
+
"bg-accent": isRowSelected
|
|
176
|
+
}),
|
|
177
|
+
children: flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
178
|
+
}
|
|
179
|
+
) : /* @__PURE__ */ jsx(
|
|
180
|
+
DataGridCell,
|
|
181
|
+
{
|
|
182
|
+
cell,
|
|
183
|
+
tableMeta,
|
|
184
|
+
rowIndex: virtualRowIndex,
|
|
185
|
+
columnId,
|
|
186
|
+
rowHeight,
|
|
187
|
+
isFocused: isCellFocused,
|
|
188
|
+
isEditing: isCellEditing,
|
|
189
|
+
isSelected: isCellSelected,
|
|
190
|
+
isSearchMatch,
|
|
191
|
+
isActiveSearchMatch,
|
|
192
|
+
readOnly
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
},
|
|
196
|
+
cell.id
|
|
197
|
+
);
|
|
198
|
+
})
|
|
199
|
+
},
|
|
200
|
+
row.id
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
export {
|
|
204
|
+
DataGridRow
|
|
205
|
+
};
|
|
206
|
+
//# sourceMappingURL=data-grid-row.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/data-grid/data-grid-row.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ColumnPinningState, Row, TableMeta, VisibilityState } from \"@tanstack/react-table\";\nimport type { VirtualItem } from \"@tanstack/react-virtual\";\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn.js\";\nimport { useComposedRefs } from \"./compose-refs.js\";\nimport { DataGridCell } from \"./data-grid-cell.js\";\nimport type { CellPosition, Direction, RowHeightValue } from \"./types.js\";\nimport {\n flexRender,\n getCellKey,\n getColumnBorderVisibility,\n getColumnPinningStyle,\n getRowHeightValue\n} from \"./utils.js\";\n\ninterface DataGridRowProps<TData> extends React.ComponentProps<\"div\"> {\n row: Row<TData>;\n tableMeta: TableMeta<TData>;\n virtualItem: VirtualItem;\n measureElement: (node: Element | null) => void;\n rowMapRef: React.RefObject<Map<number, HTMLDivElement>>;\n rowHeight: RowHeightValue;\n columnVisibility: VisibilityState;\n columnPinning: ColumnPinningState;\n focusedCell: CellPosition | null;\n editingCell: CellPosition | null;\n cellSelectionKeys: Set<string>;\n searchMatchColumns: Set<string> | null;\n activeSearchMatch: CellPosition | null;\n dir: Direction;\n readOnly: boolean;\n stretchColumns: boolean;\n adjustLayout: boolean;\n}\n\nexport const DataGridRow = React.memo(DataGridRowImpl, (prev, next) => {\n const prevRowIndex = prev.virtualItem.index;\n const nextRowIndex = next.virtualItem.index;\n\n // Re-render if row identity changed\n if (prev.row.id !== next.row.id) {\n return false;\n }\n\n // Re-render if row data (original) reference changed\n if (prev.row.original !== next.row.original) {\n return false;\n }\n\n // Re-render if virtual position changed (handles transform updates)\n if (prev.virtualItem.start !== next.virtualItem.start) {\n return false;\n }\n\n // Re-render if focus state changed for this row\n const prevHasFocus = prev.focusedCell?.rowIndex === prevRowIndex;\n const nextHasFocus = next.focusedCell?.rowIndex === nextRowIndex;\n\n if (prevHasFocus !== nextHasFocus) {\n return false;\n }\n\n // Re-render if focused column changed within this row\n if (nextHasFocus && prevHasFocus) {\n if (prev.focusedCell?.columnId !== next.focusedCell?.columnId) {\n return false;\n }\n }\n\n // Re-render if editing state changed for this row\n const prevHasEditing = prev.editingCell?.rowIndex === prevRowIndex;\n const nextHasEditing = next.editingCell?.rowIndex === nextRowIndex;\n\n if (prevHasEditing !== nextHasEditing) {\n return false;\n }\n\n // Re-render if editing column changed within this row\n if (nextHasEditing && prevHasEditing) {\n if (prev.editingCell?.columnId !== next.editingCell?.columnId) {\n return false;\n }\n }\n\n // Re-render if this row's selected cells changed\n // Using stable Set reference that only includes this row's cells\n if (prev.cellSelectionKeys !== next.cellSelectionKeys) {\n return false;\n }\n\n // Re-render if column visibility changed\n if (prev.columnVisibility !== next.columnVisibility) {\n return false;\n }\n\n // Re-render if row height changed\n if (prev.rowHeight !== next.rowHeight) {\n return false;\n }\n\n // Re-render if column pinning state changed\n if (prev.columnPinning !== next.columnPinning) {\n return false;\n }\n\n // Re-render if readOnly changed\n if (prev.readOnly !== next.readOnly) {\n return false;\n }\n\n // Re-render if search match columns changed for this row\n if (prev.searchMatchColumns !== next.searchMatchColumns) {\n return false;\n }\n\n // Re-render if active search match changed for this row\n if (prev.activeSearchMatch?.columnId !== next.activeSearchMatch?.columnId) {\n return false;\n }\n\n // Re-render if direction changed\n if (prev.dir !== next.dir) {\n return false;\n }\n\n // Re-render if adjustLayout state changed\n if (prev.adjustLayout !== next.adjustLayout) {\n return false;\n }\n\n // Re-render if stretchColumns changed\n if (prev.stretchColumns !== next.stretchColumns) {\n return false;\n }\n\n // Skip re-render - props are equal\n return true;\n}) as typeof DataGridRowImpl;\n\nfunction DataGridRowImpl<TData>({\n row,\n tableMeta,\n virtualItem,\n measureElement,\n rowMapRef,\n rowHeight,\n columnVisibility,\n columnPinning,\n focusedCell,\n editingCell,\n cellSelectionKeys,\n searchMatchColumns,\n activeSearchMatch,\n dir,\n readOnly,\n stretchColumns,\n adjustLayout,\n className,\n style,\n ref,\n ...props\n}: DataGridRowProps<TData>) {\n const virtualRowIndex = virtualItem.index;\n\n const onRowChange = React.useCallback(\n (node: HTMLDivElement | null) => {\n if (typeof virtualRowIndex === \"undefined\") return;\n\n if (node) {\n measureElement(node);\n rowMapRef.current?.set(virtualRowIndex, node);\n } else {\n rowMapRef.current?.delete(virtualRowIndex);\n }\n },\n [virtualRowIndex, measureElement, rowMapRef]\n );\n\n const rowRef = useComposedRefs(ref, onRowChange);\n\n const isRowSelected = row.getIsSelected();\n\n // Memoize visible cells to avoid recreating cell array on every render\n // Though TanStack returns new Cell wrappers, memoizing the array helps React's reconciliation\n // biome-ignore lint/correctness/useExhaustiveDependencies: columnVisibility and columnPinning are used for calculating the visible cells\n const visibleCells = React.useMemo(() => row.getVisibleCells(), [row, columnVisibility, columnPinning]);\n\n return (\n <div\n key={row.id}\n role=\"row\"\n aria-rowindex={virtualRowIndex + 2}\n aria-selected={isRowSelected}\n data-index={virtualRowIndex}\n data-slot=\"grid-row\"\n tabIndex={-1}\n {...props}\n ref={rowRef}\n className={cn(\n \"absolute flex w-full border-b [content-visibility:auto]\",\n !adjustLayout && \"will-change-transform\",\n className\n )}\n style={{\n height: `${getRowHeightValue(rowHeight)}px`,\n ...(adjustLayout ? { top: `${virtualItem.start}px` } : { transform: `translateY(${virtualItem.start}px)` }),\n ...style\n }}\n >\n {visibleCells.map((cell, colIndex) => {\n const columnId = cell.column.id;\n\n const isCellFocused = focusedCell?.rowIndex === virtualRowIndex && focusedCell?.columnId === columnId;\n const isCellEditing = editingCell?.rowIndex === virtualRowIndex && editingCell?.columnId === columnId;\n const isCellSelected = cellSelectionKeys?.has(getCellKey(virtualRowIndex, columnId)) ?? false;\n\n const isSearchMatch = searchMatchColumns?.has(columnId) ?? false;\n const isActiveSearchMatch = activeSearchMatch?.columnId === columnId;\n\n const nextCell = visibleCells[colIndex + 1];\n const isLastColumn = colIndex === visibleCells.length - 1;\n const { showEndBorder, showStartBorder } = getColumnBorderVisibility({\n column: cell.column,\n nextColumn: nextCell?.column,\n isLastColumn\n });\n\n return (\n <div\n key={cell.id}\n role=\"gridcell\"\n aria-colindex={colIndex + 1}\n data-highlighted={isCellFocused ? \"\" : undefined}\n data-slot=\"grid-cell\"\n tabIndex={-1}\n className={cn({\n grow: stretchColumns && columnId !== \"select\",\n \"border-e\": showEndBorder && columnId !== \"select\",\n \"border-s\": showStartBorder && columnId !== \"select\"\n })}\n style={{\n ...getColumnPinningStyle({ column: cell.column, dir }),\n width: `calc(var(--col-${columnId}-size) * 1px)`\n }}\n >\n {typeof cell.column.columnDef.header === \"function\" ? (\n <div\n className={cn(\"size-full px-3 py-1.5\", {\n // `--accent` là bậc tint TRẠNG THÁI của gói (Tabs/Switch/Progress dùng đúng nó\n // để nói \"đang bật\"). `bg-primary/10` là gold pha 10% — 1,05:1, một dấu hiệu giả.\n \"bg-accent\": isRowSelected\n })}\n >\n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n </div>\n ) : (\n <DataGridCell\n cell={cell}\n tableMeta={tableMeta}\n rowIndex={virtualRowIndex}\n columnId={columnId}\n rowHeight={rowHeight}\n isFocused={isCellFocused}\n isEditing={isCellEditing}\n isSelected={isCellSelected}\n isSearchMatch={isSearchMatch}\n isActiveSearchMatch={isActiveSearchMatch}\n readOnly={readOnly}\n />\n )}\n </div>\n );\n })}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { SearchState } from './types.js';
|
|
3
|
+
import '@tanstack/react-table';
|
|
4
|
+
|
|
5
|
+
interface DataGridSearchProps extends SearchState {
|
|
6
|
+
}
|
|
7
|
+
declare const DataGridSearch: React.MemoExoticComponent<typeof DataGridSearchImpl>;
|
|
8
|
+
declare function DataGridSearchImpl({ searchMatches, matchIndex, searchOpen, onSearchOpenChange, searchQuery, onSearchQueryChange, onSearch, onNavigateToNextMatch, onNavigateToPrevMatch }: DataGridSearchProps): React.JSX.Element | null;
|
|
9
|
+
|
|
10
|
+
export { DataGridSearch };
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ChevronDown, ChevronUp, X } from "lucide-react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { Button } from "../components/ui/button.js";
|
|
6
|
+
import { Input } from "../components/ui/input.js";
|
|
7
|
+
import { useDataGridMessages } from "./data-grid-locale.js";
|
|
8
|
+
import { useAsRef } from "./use-as-ref.js";
|
|
9
|
+
import { useDebouncedCallback } from "./use-debounced-callback.js";
|
|
10
|
+
function onTriggerPointerDown(event) {
|
|
11
|
+
const target = event.target;
|
|
12
|
+
if (!(target instanceof HTMLElement)) return;
|
|
13
|
+
if (target.hasPointerCapture(event.pointerId)) {
|
|
14
|
+
target.releasePointerCapture(event.pointerId);
|
|
15
|
+
}
|
|
16
|
+
if (event.button === 0 && event.ctrlKey === false && event.pointerType === "mouse" && !(event.target instanceof HTMLInputElement)) {
|
|
17
|
+
event.preventDefault();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const DataGridSearch = React.memo(DataGridSearchImpl, (prev, next) => {
|
|
21
|
+
if (prev.searchOpen !== next.searchOpen) return false;
|
|
22
|
+
if (!next.searchOpen) return true;
|
|
23
|
+
if (prev.matchIndex !== next.matchIndex) return false;
|
|
24
|
+
if (prev.searchMatches.length !== next.searchMatches.length) return false;
|
|
25
|
+
for (let i = 0; i < prev.searchMatches.length; i++) {
|
|
26
|
+
const prevMatch = prev.searchMatches[i];
|
|
27
|
+
const nextMatch = next.searchMatches[i];
|
|
28
|
+
if (!prevMatch || !nextMatch) return false;
|
|
29
|
+
if (prevMatch.rowIndex !== nextMatch.rowIndex || prevMatch.columnId !== nextMatch.columnId) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
});
|
|
35
|
+
function DataGridSearchImpl({
|
|
36
|
+
searchMatches,
|
|
37
|
+
matchIndex,
|
|
38
|
+
searchOpen,
|
|
39
|
+
onSearchOpenChange,
|
|
40
|
+
searchQuery,
|
|
41
|
+
onSearchQueryChange,
|
|
42
|
+
onSearch,
|
|
43
|
+
onNavigateToNextMatch,
|
|
44
|
+
onNavigateToPrevMatch
|
|
45
|
+
}) {
|
|
46
|
+
const msg = useDataGridMessages();
|
|
47
|
+
const propsRef = useAsRef({
|
|
48
|
+
onSearchOpenChange,
|
|
49
|
+
onSearchQueryChange,
|
|
50
|
+
onSearch,
|
|
51
|
+
onNavigateToNextMatch,
|
|
52
|
+
onNavigateToPrevMatch
|
|
53
|
+
});
|
|
54
|
+
const inputRef = React.useRef(null);
|
|
55
|
+
const isComposingRef = React.useRef(false);
|
|
56
|
+
const [hasQuery, setHasQuery] = React.useState(searchQuery.length > 0);
|
|
57
|
+
React.useEffect(() => {
|
|
58
|
+
if (searchOpen) {
|
|
59
|
+
requestAnimationFrame(() => {
|
|
60
|
+
inputRef.current?.focus();
|
|
61
|
+
});
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
isComposingRef.current = false;
|
|
65
|
+
setHasQuery(false);
|
|
66
|
+
}, [searchOpen]);
|
|
67
|
+
React.useEffect(() => {
|
|
68
|
+
if (!searchOpen) return;
|
|
69
|
+
function onEscape(event) {
|
|
70
|
+
if (event.key === "Escape") {
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
propsRef.current.onSearchOpenChange(false);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
document.addEventListener("keydown", onEscape);
|
|
76
|
+
return () => document.removeEventListener("keydown", onEscape);
|
|
77
|
+
}, [searchOpen, propsRef]);
|
|
78
|
+
const debouncedSearch = useDebouncedCallback((query) => {
|
|
79
|
+
propsRef.current.onSearch(query);
|
|
80
|
+
}, 150);
|
|
81
|
+
function onCompositionStart() {
|
|
82
|
+
isComposingRef.current = true;
|
|
83
|
+
}
|
|
84
|
+
function onCompositionEnd(event) {
|
|
85
|
+
isComposingRef.current = false;
|
|
86
|
+
const value = event.currentTarget.value;
|
|
87
|
+
setHasQuery(value.length > 0);
|
|
88
|
+
propsRef.current.onSearchQueryChange(value);
|
|
89
|
+
debouncedSearch(value);
|
|
90
|
+
}
|
|
91
|
+
function onKeyDown(event) {
|
|
92
|
+
event.stopPropagation();
|
|
93
|
+
if (event.key === "Enter") {
|
|
94
|
+
if (event.nativeEvent.isComposing) return;
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
if (event.shiftKey) {
|
|
97
|
+
propsRef.current.onNavigateToPrevMatch();
|
|
98
|
+
} else {
|
|
99
|
+
propsRef.current.onNavigateToNextMatch();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function onChange(event) {
|
|
104
|
+
if (isComposingRef.current) return;
|
|
105
|
+
const value = event.target.value;
|
|
106
|
+
setHasQuery(value.length > 0);
|
|
107
|
+
propsRef.current.onSearchQueryChange(value);
|
|
108
|
+
debouncedSearch(value);
|
|
109
|
+
}
|
|
110
|
+
function onClose() {
|
|
111
|
+
propsRef.current.onSearchOpenChange(false);
|
|
112
|
+
}
|
|
113
|
+
function onPrevMatch() {
|
|
114
|
+
propsRef.current.onNavigateToPrevMatch();
|
|
115
|
+
}
|
|
116
|
+
function onNextMatch() {
|
|
117
|
+
propsRef.current.onNavigateToNextMatch();
|
|
118
|
+
}
|
|
119
|
+
if (!searchOpen) return null;
|
|
120
|
+
return /* @__PURE__ */ jsxs(
|
|
121
|
+
"div",
|
|
122
|
+
{
|
|
123
|
+
role: "search",
|
|
124
|
+
"data-slot": "grid-search",
|
|
125
|
+
className: "fade-in-0 slide-in-from-top-2 absolute end-4 top-4 z-50 flex animate-in flex-col gap-2 rounded-lg border bg-popover p-2 shadow-overlay",
|
|
126
|
+
children: [
|
|
127
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
128
|
+
/* @__PURE__ */ jsx(
|
|
129
|
+
Input,
|
|
130
|
+
{
|
|
131
|
+
autoComplete: "off",
|
|
132
|
+
autoCorrect: "off",
|
|
133
|
+
autoCapitalize: "off",
|
|
134
|
+
spellCheck: false,
|
|
135
|
+
placeholder: msg.search.placeholder,
|
|
136
|
+
"aria-label": msg.search.label,
|
|
137
|
+
className: "h-8 w-64",
|
|
138
|
+
ref: inputRef,
|
|
139
|
+
defaultValue: searchQuery,
|
|
140
|
+
onChange,
|
|
141
|
+
onKeyDown,
|
|
142
|
+
onCompositionStart,
|
|
143
|
+
onCompositionEnd
|
|
144
|
+
}
|
|
145
|
+
),
|
|
146
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
147
|
+
/* @__PURE__ */ jsx(
|
|
148
|
+
Button,
|
|
149
|
+
{
|
|
150
|
+
"aria-label": msg.search.prevMatch,
|
|
151
|
+
variant: "ghost",
|
|
152
|
+
size: "icon",
|
|
153
|
+
className: "size-7",
|
|
154
|
+
onClick: onPrevMatch,
|
|
155
|
+
onPointerDown: onTriggerPointerDown,
|
|
156
|
+
disabled: searchMatches.length === 0,
|
|
157
|
+
children: /* @__PURE__ */ jsx(ChevronUp, { className: "size-3.5" })
|
|
158
|
+
}
|
|
159
|
+
),
|
|
160
|
+
/* @__PURE__ */ jsx(
|
|
161
|
+
Button,
|
|
162
|
+
{
|
|
163
|
+
"aria-label": msg.search.nextMatch,
|
|
164
|
+
variant: "ghost",
|
|
165
|
+
size: "icon",
|
|
166
|
+
className: "size-7",
|
|
167
|
+
onClick: onNextMatch,
|
|
168
|
+
onPointerDown: onTriggerPointerDown,
|
|
169
|
+
disabled: searchMatches.length === 0,
|
|
170
|
+
children: /* @__PURE__ */ jsx(ChevronDown, { className: "size-3.5" })
|
|
171
|
+
}
|
|
172
|
+
),
|
|
173
|
+
/* @__PURE__ */ jsx(Button, { "aria-label": msg.search.close, variant: "ghost", size: "icon", className: "size-7", onClick: onClose, children: /* @__PURE__ */ jsx(X, { className: "size-3.5" }) })
|
|
174
|
+
] })
|
|
175
|
+
] }),
|
|
176
|
+
/* @__PURE__ */ jsx("div", { role: "status", className: "flex items-center gap-1 whitespace-nowrap text-muted-foreground text-xs", children: searchMatches.length > 0 ? /* @__PURE__ */ jsx("span", { children: msg.search.matchPosition(matchIndex + 1, searchMatches.length) }) : hasQuery ? /* @__PURE__ */ jsx("span", { children: msg.search.noResults }) : /* @__PURE__ */ jsx("span", { children: msg.search.typeToSearch }) })
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
export {
|
|
182
|
+
DataGridSearch
|
|
183
|
+
};
|
|
184
|
+
//# sourceMappingURL=data-grid-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/data-grid/data-grid-search.tsx"],"sourcesContent":["\"use client\";\n\nimport { ChevronDown, ChevronUp, X } from \"lucide-react\";\nimport * as React from \"react\";\nimport { Button } from \"../components/ui/button.js\";\nimport { Input } from \"../components/ui/input.js\";\nimport { useDataGridMessages } from \"./data-grid-locale.js\";\nimport type { SearchState } from \"./types.js\";\nimport { useAsRef } from \"./use-as-ref.js\";\nimport { useDebouncedCallback } from \"./use-debounced-callback.js\";\n\nfunction onTriggerPointerDown(event: React.PointerEvent<HTMLButtonElement>) {\n const target = event.target;\n if (!(target instanceof HTMLElement)) return;\n if (target.hasPointerCapture(event.pointerId)) {\n target.releasePointerCapture(event.pointerId);\n }\n\n // Prevent the trigger from stealing focus away from the input\n if (\n event.button === 0 &&\n event.ctrlKey === false &&\n event.pointerType === \"mouse\" &&\n !(event.target instanceof HTMLInputElement)\n ) {\n event.preventDefault();\n }\n}\n\ninterface DataGridSearchProps extends SearchState {}\n\nexport const DataGridSearch = React.memo(DataGridSearchImpl, (prev, next) => {\n if (prev.searchOpen !== next.searchOpen) return false;\n\n if (!next.searchOpen) return true;\n\n // Exclude searchQuery because the input is uncontrolled, and hasQuery state handles the status text\n if (prev.matchIndex !== next.matchIndex) return false;\n\n if (prev.searchMatches.length !== next.searchMatches.length) return false;\n\n for (let i = 0; i < prev.searchMatches.length; i++) {\n const prevMatch = prev.searchMatches[i];\n const nextMatch = next.searchMatches[i];\n\n if (!prevMatch || !nextMatch) return false;\n\n if (prevMatch.rowIndex !== nextMatch.rowIndex || prevMatch.columnId !== nextMatch.columnId) {\n return false;\n }\n }\n\n return true;\n});\n\nfunction DataGridSearchImpl({\n searchMatches,\n matchIndex,\n searchOpen,\n onSearchOpenChange,\n searchQuery,\n onSearchQueryChange,\n onSearch,\n onNavigateToNextMatch,\n onNavigateToPrevMatch\n}: DataGridSearchProps) {\n const msg = useDataGridMessages();\n\n const propsRef = useAsRef({\n onSearchOpenChange,\n onSearchQueryChange,\n onSearch,\n onNavigateToNextMatch,\n onNavigateToPrevMatch\n });\n\n const inputRef = React.useRef<HTMLInputElement>(null);\n const isComposingRef = React.useRef(false);\n const [hasQuery, setHasQuery] = React.useState(searchQuery.length > 0);\n\n React.useEffect(() => {\n if (searchOpen) {\n requestAnimationFrame(() => {\n inputRef.current?.focus();\n });\n return;\n }\n\n isComposingRef.current = false;\n setHasQuery(false);\n }, [searchOpen]);\n\n React.useEffect(() => {\n if (!searchOpen) return;\n\n function onEscape(event: KeyboardEvent) {\n if (event.key === \"Escape\") {\n event.preventDefault();\n propsRef.current.onSearchOpenChange(false);\n }\n }\n\n document.addEventListener(\"keydown\", onEscape);\n return () => document.removeEventListener(\"keydown\", onEscape);\n }, [searchOpen, propsRef]);\n\n const debouncedSearch = useDebouncedCallback((query: string) => {\n propsRef.current.onSearch(query);\n }, 150);\n\n function onCompositionStart() {\n isComposingRef.current = true;\n }\n\n function onCompositionEnd(event: React.CompositionEvent<HTMLInputElement>) {\n isComposingRef.current = false;\n const value = event.currentTarget.value;\n setHasQuery(value.length > 0);\n propsRef.current.onSearchQueryChange(value);\n debouncedSearch(value);\n }\n\n function onKeyDown(event: React.KeyboardEvent) {\n event.stopPropagation();\n\n if (event.key === \"Enter\") {\n if (event.nativeEvent.isComposing) return;\n event.preventDefault();\n if (event.shiftKey) {\n propsRef.current.onNavigateToPrevMatch();\n } else {\n propsRef.current.onNavigateToNextMatch();\n }\n }\n }\n\n function onChange(event: React.ChangeEvent<HTMLInputElement>) {\n if (isComposingRef.current) return;\n const value = event.target.value;\n setHasQuery(value.length > 0);\n propsRef.current.onSearchQueryChange(value);\n debouncedSearch(value);\n }\n\n function onClose() {\n propsRef.current.onSearchOpenChange(false);\n }\n\n function onPrevMatch() {\n propsRef.current.onNavigateToPrevMatch();\n }\n\n function onNextMatch() {\n propsRef.current.onNavigateToNextMatch();\n }\n\n if (!searchOpen) return null;\n\n return (\n <div\n role=\"search\"\n data-slot=\"grid-search\"\n // `shadow-overlay`, KHÔNG `shadow-lg`: bóng của gói đi qua `--elevation-*` nên nó đổi\n // theo sáng/tối; `shadow-lg` của Tailwind là rgba đen cố định, ở dark gần như vô hình.\n className=\"fade-in-0 slide-in-from-top-2 absolute end-4 top-4 z-50 flex animate-in flex-col gap-2 rounded-lg border bg-popover p-2 shadow-overlay\"\n >\n <div className=\"flex items-center gap-2\">\n <Input\n autoComplete=\"off\"\n autoCorrect=\"off\"\n autoCapitalize=\"off\"\n spellCheck={false}\n placeholder={msg.search.placeholder}\n // placeholder biến mất ngay khi người dùng gõ → vẫn phải có nhãn riêng\n aria-label={msg.search.label}\n className=\"h-8 w-64\"\n ref={inputRef}\n defaultValue={searchQuery}\n onChange={onChange}\n onKeyDown={onKeyDown}\n onCompositionStart={onCompositionStart}\n onCompositionEnd={onCompositionEnd}\n />\n <div className=\"flex items-center gap-1\">\n <Button\n aria-label={msg.search.prevMatch}\n variant=\"ghost\"\n size=\"icon\"\n className=\"size-7\"\n onClick={onPrevMatch}\n onPointerDown={onTriggerPointerDown}\n disabled={searchMatches.length === 0}\n >\n <ChevronUp className=\"size-3.5\" />\n </Button>\n <Button\n aria-label={msg.search.nextMatch}\n variant=\"ghost\"\n size=\"icon\"\n className=\"size-7\"\n onClick={onNextMatch}\n onPointerDown={onTriggerPointerDown}\n disabled={searchMatches.length === 0}\n >\n <ChevronDown className=\"size-3.5\" />\n </Button>\n <Button aria-label={msg.search.close} variant=\"ghost\" size=\"icon\" className=\"size-7\" onClick={onClose}>\n <X className=\"size-3.5\" />\n </Button>\n </div>\n </div>\n <div role=\"status\" className=\"flex items-center gap-1 whitespace-nowrap text-muted-foreground text-xs\">\n {searchMatches.length > 0 ? (\n <span>{msg.search.matchPosition(matchIndex + 1, searchMatches.length)}</span>\n ) : hasQuery ? (\n <span>{msg.search.noResults}</span>\n ) : (\n <span>{msg.search.typeToSearch}</span>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ColumnDef } from '@tanstack/react-table';
|
|
2
|
+
|
|
3
|
+
type HitboxSize = "default" | "sm" | "lg";
|
|
4
|
+
interface GetDataGridSelectColumnOptions<TData> extends Omit<Partial<ColumnDef<TData>>, "id" | "header" | "cell"> {
|
|
5
|
+
enableRowMarkers?: boolean;
|
|
6
|
+
readOnly?: boolean;
|
|
7
|
+
hitboxSize?: HitboxSize;
|
|
8
|
+
debug?: boolean;
|
|
9
|
+
}
|
|
10
|
+
declare function getDataGridSelectColumn<TData>({ size, hitboxSize, enableHiding, enableResizing, enableSorting, enableRowMarkers, readOnly, debug, ...props }?: GetDataGridSelectColumnOptions<TData>): ColumnDef<TData>;
|
|
11
|
+
|
|
12
|
+
export { getDataGridSelectColumn };
|