Files
invyone/frontend/lib/registry/components/table/InvTableConfigPanel.tsx
T
DDD1542 57ffbcbbc7 fix: 피벗 ConfigPanel default 값 모두 false (안일한 true → 빈 그리드 회귀 방지)
직전 commit (3ed53a670) 의 메타 토글 4종이 default true 로 잡혀 있었음.
빈 row/column 영역에서 행/열 총계만 활성화되어 사용자가 새 피벗 배치 시
무의미한 0 그리드가 보이는 회귀 발생.

default true → false 변경:
- 필드 선택기 (pivotFieldChooser.enabled)
- 행 총계 (pivotTotals.showRowGrandTotals)
- 열 총계 (pivotTotals.showColumnGrandTotals)
- 행 교대 색 (pivotStyle.alternateRowColors)

PivotView 본체 (line 1048~1085) 가 활성 필드 0 일 때 "필드를 배치하세요"
안내 + FieldChooser 버튼을 자연스럽게 표시함. 신규 피벗 배치 시 사용자가
명시적으로 옵션을 켜야 효과 발현 (솔루션 정의 단계 패턴).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 16:44:54 +09:00

850 lines
29 KiB
TypeScript

"use client";
/**
* InvTableConfigPanel — 통합 "테이블" (id: table) cp 톤 설정 패널
*
* 흐름:
* ① 테이블 연결 — 테이블 선택 + DB 컬럼 자동 로드
* ② 표시 모드 — CPVisualGrid 5칸 (기본/분할/그룹/피벗/카드 시각 미리보기)
* ③ 행 선택 + 높이 — CPSegment
* ④ 모드별 설정 — split 비율 / grouped 컬럼 (조건부)
* ⑤ 컬럼 편집 — dense list (한 줄 = 라벨 + key + 너비 + align + 정렬)
* ▾ 동작 옵션 — 헤더/푸터/체크박스/줄무늬/호버/툴바 (FeatureChipGrid)
*
* Reference: notes/gbpark/2026-04-28-cp-panel-standard.md
*/
import React, { useCallback, useEffect, useMemo, useState } from "react";
import {
Table2,
Columns2,
List,
Grid3x3,
LayoutGrid,
Plus,
AlignLeft,
AlignCenter,
AlignRight,
} from "lucide-react";
import {
CPSection,
CPRow,
CPGroup,
CPText,
CPSelect,
CPSegment,
CPNumber,
CPSwitch,
CPVisualGrid,
FeatureChipGrid,
Hint,
} from "@/components/v2/config-panels/_shared/cp";
import { useDbTables } from "../common/useDbTables";
import { TableConnectSection, AutoLoadButton } from "../common/TableConnectSection";
import {
RowNumberBadge,
RowExpandChevron,
RowDeleteBtn,
} from "../common/row-helpers";
import type { TableConfig, TableColumn } from "./types";
export interface InvTableConfigPanelProps {
config?: TableConfig;
onChange?: (config: TableConfig) => void;
selectedComponent?: { id: string; config?: TableConfig; [k: string]: any };
tables?: any[];
tableColumns?: any[];
screenTableName?: string;
onTableChange?: (tableName: string) => void;
}
export const InvTableConfigPanel: React.FC<InvTableConfigPanelProps> = ({
config,
onChange,
selectedComponent,
tables,
tableColumns,
screenTableName,
onTableChange,
}) => {
const current: TableConfig =
(config as TableConfig) || (selectedComponent?.config as TableConfig) || {};
const patch = useCallback(
(p: Partial<TableConfig>) => onChange?.({ ...current, ...p }),
[current, onChange],
);
const columns: TableColumn[] = current.columns ?? [];
const connectedTable = (current as any).selectedTable || screenTableName;
const { options: tableOptions } = useDbTables({ fallback: tables });
// ── 연결된 테이블의 컬럼 로드 (자동 로드 button 용) ──
const [connectedTableColumns, setConnectedTableColumns] = useState<any[]>([]);
const [loadingConnectedColumns, setLoadingConnectedColumns] = useState(false);
useEffect(() => {
let cancelled = false;
if (!connectedTable) {
setConnectedTableColumns([]);
setLoadingConnectedColumns(false);
return () => {
cancelled = true;
};
}
if (
connectedTable === screenTableName &&
Array.isArray(tableColumns) &&
tableColumns.length > 0
) {
setConnectedTableColumns(tableColumns);
setLoadingConnectedColumns(false);
return () => {
cancelled = true;
};
}
setLoadingConnectedColumns(true);
(async () => {
try {
const { tableTypeApi } = await import("@/lib/api/screen");
const cols = await tableTypeApi.getColumns(connectedTable, true);
if (!cancelled) setConnectedTableColumns(Array.isArray(cols) ? cols : []);
} catch {
if (!cancelled) setConnectedTableColumns([]);
} finally {
if (!cancelled) setLoadingConnectedColumns(false);
}
})();
return () => {
cancelled = true;
};
}, [connectedTable, screenTableName, tableColumns]);
const effectiveTableColumns = useMemo(() => {
if (
connectedTable === screenTableName &&
Array.isArray(tableColumns) &&
tableColumns.length > 0
) {
return tableColumns;
}
return connectedTableColumns;
}, [connectedTable, screenTableName, tableColumns, connectedTableColumns]);
const autoLoadColumns = useCallback(() => {
if (!effectiveTableColumns?.length) return;
const newCols: TableColumn[] = effectiveTableColumns.map((col: any) => ({
key: col.columnName || col.column_name,
label:
col.columnLabel ||
col.column_label ||
col.displayName ||
col.columnName ||
col.column_name,
width: undefined,
align: "left" as const,
sortable: true,
visible: true,
}));
patch({ columns: newCols });
}, [effectiveTableColumns, patch]);
const updateColumn = (idx: number, col: Partial<TableColumn>) => {
const next = columns.map((c, i) => (i === idx ? { ...c, ...col } : c));
patch({ columns: next });
};
const addColumn = () => {
patch({
columns: [
...columns,
{ key: `col${columns.length + 1}`, label: `컬럼 ${columns.length + 1}` },
],
});
};
const removeColumn = (idx: number) => {
patch({ columns: columns.filter((_, i) => i !== idx) });
};
const displayMode = current.displayMode || "table";
return (
<div style={{ fontFamily: "var(--v5-font-sans)", color: "var(--cp-text)", padding: "0 12px" }}>
{/* ── ① 테이블 연결 ─────────────────────────── */}
<TableConnectSection
value={connectedTable || ""}
onChange={(v) => {
onTableChange?.(v);
patch({ selectedTable: v, columns: [] } as any);
}}
options={tableOptions}
desc="DB 테이블 매핑"
>
{connectedTable && (
<Hint>
{loadingConnectedColumns
? "컬럼 정보를 불러오는 중..."
: effectiveTableColumns.length > 0
? `컬럼 ${effectiveTableColumns.length}개 준비됨`
: "불러온 컬럼 정보가 없습니다"}
</Hint>
)}
{connectedTable && effectiveTableColumns.length > 0 && (
<AutoLoadButton
label={`DB 컬럼에서 자동 로드 (${effectiveTableColumns.length}개)`}
onClick={autoLoadColumns}
/>
)}
</TableConnectSection>
{/* ── ② 표시 모드 ─────────────────────────── */}
<CPSection title="② 표시 모드" desc="테이블 형태 변형">
<CPVisualGrid
cols={5}
cardHeight={62}
value={displayMode}
onChange={(v) => patch({ displayMode: v as TableConfig["displayMode"] })}
options={[
{ value: "table", label: "기본", preview: <Table2 size={16} />, desc: "일반 테이블" },
{ value: "split", label: "분할", preview: <Columns2 size={16} />, desc: "좌우 분할" },
{ value: "grouped", label: "그룹", preview: <List size={16} />, desc: "그룹핑" },
{ value: "pivot", label: "피벗", preview: <Grid3x3 size={16} />, desc: "피벗 그리드" },
{ value: "card", label: "카드", preview: <LayoutGrid size={16} />, desc: "카드 리스트" },
]}
/>
</CPSection>
{/* ── ③ 행 선택 + 높이 ─────────────────────────── */}
<CPSection title="③ 행 / 높이">
<CPRow label="행 선택">
<CPSegment
value={current.selectionMode || "single"}
onChange={(v) => patch({ selectionMode: v as TableConfig["selectionMode"] })}
options={[
{ value: "none", label: "없음" },
{ value: "single", label: "단일" },
{ value: "multiple", label: "복수" },
]}
/>
</CPRow>
<CPRow label="행 높이">
<CPSegment
value={current.rowHeight || "normal"}
onChange={(v) => patch({ rowHeight: v as TableConfig["rowHeight"] })}
options={[
{ value: "compact", label: "좁게" },
{ value: "normal", label: "기본" },
{ value: "relaxed", label: "넓게" },
]}
/>
</CPRow>
</CPSection>
{/* ── ④ 모드별 설정 (조건부) ─────────────────────────── */}
{displayMode === "split" && (
<CPSection title="④ 분할 설정" desc="좌측 패널 비율">
<CPRow label="좌측 비율" help="0.1 ~ 0.9 (예: 0.5 = 절반)">
<CPNumber
value={current.splitRatio ?? 0.5}
onChange={(v) => patch({ splitRatio: v ?? 0.5 })}
min={0.1}
max={0.9}
step={0.05}
/>
</CPRow>
</CPSection>
)}
{displayMode === "grouped" && (
<CPSection title="④ 그룹 설정" desc="그룹화 기준 컬럼">
<CPRow label="그룹화 컬럼">
<CPSelect
value={current.groupBy || ""}
onChange={(v) => patch({ groupBy: v || undefined })}
searchable={false}
>
<option value="">...</option>
{columns.map((c) => (
<option key={c.key} value={c.key}>
{c.label || c.key}
</option>
))}
</CPSelect>
</CPRow>
{columns.length === 0 && (
<Hint tone="warn"> .</Hint>
)}
</CPSection>
)}
{displayMode === "pivot" && (
<CPSection title="④ 피벗 설정" desc="본체 분석 UI에서 사용할 표시 옵션">
{/* 피벗 필드 배치는 PivotView 본체의 FieldPanel/FieldChooser가 담당한다. ConfigPanel은 메타 옵션만 관리한다. */}
<CPRow label="차트 표시">
<CPSwitch
value={current.pivotChart?.enabled ?? false}
onChange={(v) =>
patch({
pivotChart: {
type: current.pivotChart?.type ?? "bar",
position: current.pivotChart?.position ?? "bottom",
...current.pivotChart,
enabled: v,
},
})
}
/>
</CPRow>
<CPRow label="필드 선택기">
<CPSwitch
value={current.pivotFieldChooser?.enabled ?? false}
onChange={(v) =>
patch({
pivotFieldChooser: {
...current.pivotFieldChooser,
enabled: v,
},
})
}
/>
</CPRow>
<CPRow label="행 총계">
<CPSwitch
value={current.pivotTotals?.showRowGrandTotals ?? false}
onChange={(v) =>
patch({
pivotTotals: {
...current.pivotTotals,
showRowGrandTotals: v,
},
})
}
/>
</CPRow>
<CPRow label="열 총계">
<CPSwitch
value={current.pivotTotals?.showColumnGrandTotals ?? false}
onChange={(v) =>
patch({
pivotTotals: {
...current.pivotTotals,
showColumnGrandTotals: v,
},
})
}
/>
</CPRow>
<CPRow label="셀 병합">
<CPSwitch
value={current.pivotStyle?.mergeCells ?? false}
onChange={(v) =>
patch({
pivotStyle: {
theme: current.pivotStyle?.theme ?? "default",
headerStyle: current.pivotStyle?.headerStyle ?? "default",
cellPadding: current.pivotStyle?.cellPadding ?? "normal",
borderStyle: current.pivotStyle?.borderStyle ?? "light",
...current.pivotStyle,
mergeCells: v,
},
})
}
/>
</CPRow>
<CPRow label="행 교대 색">
<CPSwitch
value={current.pivotStyle?.alternateRowColors ?? false}
onChange={(v) =>
patch({
pivotStyle: {
theme: current.pivotStyle?.theme ?? "default",
headerStyle: current.pivotStyle?.headerStyle ?? "default",
cellPadding: current.pivotStyle?.cellPadding ?? "normal",
borderStyle: current.pivotStyle?.borderStyle ?? "light",
...current.pivotStyle,
alternateRowColors: v,
},
})
}
/>
</CPRow>
<CPRow label="엑셀 내보내기">
<CPSwitch
value={current.pivotExportConfig?.excel ?? false}
onChange={(v) =>
patch({
pivotExportConfig: {
...current.pivotExportConfig,
excel: v,
},
})
}
/>
</CPRow>
<CPRow label="PDF 내보내기">
<CPSwitch
value={current.pivotExportConfig?.pdf ?? false}
onChange={(v) =>
patch({
pivotExportConfig: {
...current.pivotExportConfig,
pdf: v,
},
})
}
/>
</CPRow>
</CPSection>
)}
{displayMode === "card" && (
<CPSection title="④ 카드 설정" desc="grid · 표시 영역 · 컬럼 매핑">
<CPRow label="한 줄 카드 수">
<CPNumber
value={current.cardsPerRow ?? 3}
onChange={(v) => patch({ cardsPerRow: v ?? 3 })}
min={1}
max={10}
/>
</CPRow>
<CPRow label="간격(px)">
<CPNumber
value={current.cardSpacing ?? 12}
onChange={(v) => patch({ cardSpacing: v ?? 12 })}
min={0}
max={64}
/>
</CPRow>
<CPGroup title="표시 영역" defaultOpen>
<CPRow label="제목">
<CPSwitch
value={current.cardStyle?.showTitle ?? true}
onChange={(v) => patch({ cardStyle: { ...current.cardStyle, showTitle: v } })}
/>
</CPRow>
<CPRow label="부제">
<CPSwitch
value={current.cardStyle?.showSubtitle ?? true}
onChange={(v) => patch({ cardStyle: { ...current.cardStyle, showSubtitle: v } })}
/>
</CPRow>
<CPRow label="설명">
<CPSwitch
value={current.cardStyle?.showDescription ?? true}
onChange={(v) => patch({ cardStyle: { ...current.cardStyle, showDescription: v } })}
/>
</CPRow>
<CPRow label="이미지">
<CPSwitch
value={current.cardStyle?.showImage ?? true}
onChange={(v) => patch({ cardStyle: { ...current.cardStyle, showImage: v } })}
/>
</CPRow>
{(current.cardStyle?.showImage ?? true) && (
<>
<CPRow label="이미지 위치">
<CPSegment
value={current.cardStyle?.imagePosition || "top"}
onChange={(v) => patch({ cardStyle: { ...current.cardStyle, imagePosition: v as any } })}
options={[
{ value: "top", label: "상단" },
{ value: "left", label: "좌측" },
{ value: "right", label: "우측" },
]}
/>
</CPRow>
<CPRow label="이미지 크기">
<CPSegment
value={current.cardStyle?.imageSize || "medium"}
onChange={(v) => patch({ cardStyle: { ...current.cardStyle, imageSize: v as any } })}
options={[
{ value: "small", label: "작게" },
{ value: "medium", label: "보통" },
{ value: "large", label: "크게" },
]}
/>
</CPRow>
</>
)}
</CPGroup>
<CPGroup title="컬럼 매핑" defaultOpen>
{columns.length === 0 ? (
<Hint tone="warn"> .</Hint>
) : (
<>
<CPRow label="제목 컬럼">
<CPSelect
value={current.cardColumnMapping?.titleColumn || ""}
onChange={(v) =>
patch({
cardColumnMapping: {
...current.cardColumnMapping,
titleColumn: v || undefined,
},
})
}
>
<option value="">...</option>
{columns.map((c) => (
<option key={c.key} value={c.key}>
{c.label || c.key}
</option>
))}
</CPSelect>
</CPRow>
<CPRow label="부제 컬럼">
<CPSelect
value={current.cardColumnMapping?.subtitleColumn || ""}
onChange={(v) =>
patch({
cardColumnMapping: {
...current.cardColumnMapping,
subtitleColumn: v || undefined,
},
})
}
>
<option value="">...</option>
{columns.map((c) => (
<option key={c.key} value={c.key}>
{c.label || c.key}
</option>
))}
</CPSelect>
</CPRow>
<CPRow label="설명 컬럼">
<CPSelect
value={current.cardColumnMapping?.descriptionColumn || ""}
onChange={(v) =>
patch({
cardColumnMapping: {
...current.cardColumnMapping,
descriptionColumn: v || undefined,
},
})
}
>
<option value="">...</option>
{columns.map((c) => (
<option key={c.key} value={c.key}>
{c.label || c.key}
</option>
))}
</CPSelect>
</CPRow>
<CPRow label="이미지 컬럼">
<CPSelect
value={current.cardColumnMapping?.imageColumn || ""}
onChange={(v) =>
patch({
cardColumnMapping: {
...current.cardColumnMapping,
imageColumn: v || undefined,
},
})
}
>
<option value="">...</option>
{columns.map((c) => (
<option key={c.key} value={c.key}>
{c.label || c.key}
</option>
))}
</CPSelect>
</CPRow>
</>
)}
</CPGroup>
<CPGroup title="액션 버튼" defaultOpen={false}>
<CPRow label="액션 표시">
<CPSwitch
value={current.cardStyle?.showActions ?? false}
onChange={(v) => patch({ cardStyle: { ...current.cardStyle, showActions: v } })}
/>
</CPRow>
{current.cardStyle?.showActions && (
<>
<CPRow label="보기">
<CPSwitch
value={current.cardStyle?.showViewButton ?? false}
onChange={(v) =>
patch({ cardStyle: { ...current.cardStyle, showViewButton: v } })
}
/>
</CPRow>
<CPRow label="편집">
<CPSwitch
value={current.cardStyle?.showEditButton ?? false}
onChange={(v) =>
patch({ cardStyle: { ...current.cardStyle, showEditButton: v } })
}
/>
</CPRow>
<CPRow label="삭제">
<CPSwitch
value={current.cardStyle?.showDeleteButton ?? false}
onChange={(v) =>
patch({ cardStyle: { ...current.cardStyle, showDeleteButton: v } })
}
/>
</CPRow>
</>
)}
</CPGroup>
</CPSection>
)}
{/* ── ⑤ 컬럼 ─────────────────────────── */}
<CPSection title="⑤ 컬럼" desc={`${columns.length}`}>
<div
style={{
display: "flex",
justifyContent: "flex-end",
marginBottom: 5,
}}
>
<button
type="button"
onClick={addColumn}
style={{
padding: "4px 10px",
fontSize: 10.5,
background: "var(--cp-bg-subtle)",
border: "1px solid var(--cp-border)",
borderRadius: 4,
cursor: "pointer",
color: "var(--cp-text)",
fontFamily: "var(--v5-font-sans)",
display: "inline-flex",
alignItems: "center",
gap: 4,
}}
>
<Plus size={10} />
</button>
</div>
{columns.length === 0 ? (
<Hint>
{connectedTable
? loadingConnectedColumns
? "컬럼 정보를 불러오는 중입니다."
: effectiveTableColumns.length > 0
? "위 [자동 로드] 버튼으로 한 번에 가져올 수 있습니다."
: "연결된 테이블에서 컬럼 정보를 찾지 못했습니다."
: "테이블을 연결하면 자동 로드할 수 있습니다."}
</Hint>
) : (
<div
style={{
border: "1px solid var(--cp-border-subtle)",
borderRadius: 5,
overflow: "hidden",
background: "var(--cp-bg-subtle)",
}}
>
{columns.map((col, idx) => (
<ColumnEditRow
key={`${col.key}-${idx}`}
index={idx}
col={col}
isLast={idx === columns.length - 1}
onChange={(p) => updateColumn(idx, p)}
onRemove={() => removeColumn(idx)}
/>
))}
</div>
)}
</CPSection>
{/* ── ▾ 동작 옵션 ─────────────────────────── */}
<CPGroup title="동작 옵션" defaultOpen={false}>
<FeatureChipGrid
items={[
{
key: "showHeader",
label: "헤더",
default: true,
desc: "컬럼 라벨이 있는 상단 헤더 행을 표시합니다.\n끄면 데이터 행만 보여요.",
},
{
key: "showFooter",
label: "푸터",
default: true,
desc: "테이블 하단에 페이지네이션 / 합계 등을 표시합니다.\n페이지네이션이 비활성이면 자리만 차지해요.",
},
{
key: "showCheckbox",
label: "체크박스",
desc: "각 행 좌측에 선택 체크박스 컬럼이 추가됩니다.\n[행 선택] = 복수 일 때 함께 켜는 게 일반적이에요.",
},
{
key: "striped",
label: "줄무늬",
default: true,
desc: "짝수 행에 옅은 음영을 주어 행 구분이 쉬워집니다.\n행이 많은 표 (50행+) 에 권장.",
},
{
key: "hoverable",
label: "호버 효과",
default: true,
desc: "행 위에 마우스를 올리면 배경이 밝아져 강조됩니다.\n인터랙티브 테이블 (행 클릭/선택) 에 유용.",
},
{
key: "showToolbar",
label: "툴바",
default: true,
desc: "테이블 상단에 검색 / 새로고침 / 엑셀 등 도구 모음을 표시합니다.\n별도 검색 컴포넌트가 있으면 OFF 권장.",
},
{
key: "bordered",
label: "테두리",
desc: "셀 사이에 보더 선을 추가합니다.\n많은 컬럼을 명확히 구분할 때 유용.",
},
]}
source={current as any}
onToggle={(k, v) => patch({ [k]: v } as Partial<TableConfig>)}
/>
</CPGroup>
</div>
);
};
InvTableConfigPanel.displayName = "InvTableConfigPanel";
// ───────────────────────────────────────────────────────
// ColumnEditRow — 컬럼 한 줄 편집 (dense)
// ───────────────────────────────────────────────────────
function ColumnEditRow({
index,
col,
isLast,
onChange,
onRemove,
}: {
index: number;
col: TableColumn;
isLast: boolean;
onChange: (p: Partial<TableColumn>) => void;
onRemove: () => void;
}) {
const [hover, setHover] = useState(false);
const [expanded, setExpanded] = useState(false);
return (
<div
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
style={{
borderBottom: isLast ? "none" : "1px solid var(--cp-border-subtle)",
background: hover ? "var(--cp-surface-hover, var(--cp-surface))" : "transparent",
transition: "background .12s ease",
fontFamily: "var(--v5-font-sans)",
}}
>
{/* 한 줄: # + 라벨 + key + 펼침 화살표 + 삭제 */}
<div
style={{
display: "grid",
gridTemplateColumns: "16px 1fr 1fr 18px 22px",
alignItems: "center",
columnGap: 6,
padding: "5px 8px",
minHeight: 28,
}}
>
<RowNumberBadge n={index + 1} />
<input
type="text"
value={col.label}
onChange={(e) => onChange({ label: e.target.value })}
placeholder="라벨"
style={inputStyle()}
/>
<input
type="text"
value={col.key}
onChange={(e) => onChange({ key: e.target.value })}
placeholder="컬럼 key"
style={inputStyle({ mono: true })}
/>
<RowExpandChevron
expanded={expanded}
onToggle={() => setExpanded((x) => !x)}
/>
<RowDeleteBtn onClick={onRemove} visible={hover} />
</div>
{/* 펼친 옵션: 너비 / 정렬 / 정렬 가능 */}
{expanded && (
<div
style={{
padding: "0 8px 8px 30px",
display: "flex",
flexDirection: "column",
gap: 6,
}}
>
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: 6,
alignItems: "center",
}}
>
<CPRow label="너비 (px)">
<CPNumber
value={col.width ?? undefined}
onChange={(v) => onChange({ width: v })}
placeholder="auto"
min={0}
/>
</CPRow>
<CPRow label="정렬">
<CPSegment
value={col.align || "left"}
onChange={(v) => onChange({ align: v as TableColumn["align"] })}
options={[
{ value: "left", label: <AlignLeft size={11} /> },
{ value: "center", label: <AlignCenter size={11} /> },
{ value: "right", label: <AlignRight size={11} /> },
]}
/>
</CPRow>
</div>
<CPRow label="정렬 가능" help="헤더 클릭 시 sort 토글">
<CPSwitch
value={col.sortable ?? true}
onChange={(v) => onChange({ sortable: v })}
/>
</CPRow>
</div>
)}
</div>
);
}
function inputStyle({ mono = false }: { mono?: boolean } = {}): React.CSSProperties {
return {
height: 22,
padding: "0 6px",
fontSize: 11,
fontFamily: mono ? "var(--v5-font-mono)" : "var(--v5-font-sans)",
background: "var(--cp-surface)",
border: "1px solid var(--cp-border)",
borderRadius: 3,
color: "var(--cp-text)",
outline: "none",
minWidth: 0,
};
}
export default InvTableConfigPanel;