@thefreshop/tb 1.1.3 → 1.1.4

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 ADDED
@@ -0,0 +1,273 @@
1
+ # @thefreshop/tb — tb framework
2
+
3
+ ## 1) 패키지 이름 + 한 줄 소개
4
+ Ant Design 기반의 React UI 프레임워크로, 탭형 어드민 프레임과 CRUD 테이블/검색/페이지 스캐폴드를 제공합니다.
5
+
6
+ ## 2) Why / What it solves
7
+ - 탭 기반 어드민 프레임(Top/Nav/Bottom)과 라우팅을 빠르게 구성.
8
+ - 검색/테이블/모달 CRUD 흐름을 재사용 가능한 컴포넌트로 표준화.
9
+ - Ant Design을 기반으로 일관된 관리자 UI 구축.
10
+
11
+ ## 3) Features
12
+ - [x] 탭 기반 프레임 레이아웃 + 동적 라우팅
13
+ - [x] 세션 기반 탭 상태 저장(`sessionStorage`)
14
+ - [x] 검색 박스 + 테이블 + 로딩 레이아웃 스캐폴드
15
+ - [x] Ant Design Table 기반 CRUD 래퍼 + 모달 생성/수정
16
+ - [x] 한국어 로케일 기본 제공(dayjs/moment/antd)
17
+
18
+ ## 4) Installation
19
+ ```bash
20
+ npm i @thefreshop/tb
21
+ ```
22
+
23
+ ```bash
24
+ yarn add @thefreshop/tb
25
+ ```
26
+
27
+ ```bash
28
+ pnpm add @thefreshop/tb
29
+ ```
30
+
31
+ ### Peer Dependencies
32
+ 이 패키지는 아래 피어 의존성을 필요로 합니다:
33
+ ```bash
34
+ npm i react react-dom react-router antd @ant-design/icons dayjs moment react-error-boundary @monaco-editor/react
35
+ ```
36
+
37
+ ## 5) Quick Start
38
+ ```tsx
39
+ import React from "react";
40
+ import { TbProvider, TbpProvider, TbFrame } from "@thefreshop/tb";
41
+ import type { navType, topType, tbframeType } from "@thefreshop/tb";
42
+
43
+ const setting: tbframeType = { islogin: false, startTabKey: "dashboard" };
44
+ const top: topType = { title: "TB Admin", topMenuSetting: [{ key: "main", title: "Main" }] };
45
+ const nav: navType = {
46
+ menuSet: [
47
+ {
48
+ parentkey: "main",
49
+ menuSetting: [
50
+ { tab: { key: "dashboard", title: "Dashboard", page: <div>Home</div> }, hasPage: true },
51
+ ],
52
+ },
53
+ ],
54
+ };
55
+
56
+ export default function App() {
57
+ return (
58
+ <TbProvider basename="/">
59
+ <TbpProvider>
60
+ <TbFrame setting={setting} top={top} nav={nav} />
61
+ </TbpProvider>
62
+ </TbProvider>
63
+ );
64
+ }
65
+ ```
66
+
67
+ ## 6) Usage
68
+ ### 기본 사용
69
+ ```tsx
70
+ import { Tbpage } from "@thefreshop/tb";
71
+
72
+ const columns = [
73
+ {
74
+ tableProps: { dataIndex: "name", title: "Name" },
75
+ search: true,
76
+ create: true,
77
+ modify: true,
78
+ },
79
+ ];
80
+
81
+ export default function Users() {
82
+ return (
83
+ <Tbpage
84
+ title="Users"
85
+ decs="User list"
86
+ searchOption={[{ key: "base", items: [{ type: "input", key: "name", title: "Name" }] }]}
87
+ tableProps={{ rowKey: "id", columns, dataSource: [] }}
88
+ />
89
+ );
90
+ }
91
+ ```
92
+
93
+ ### 옵션/설정(핵심 props 요약)
94
+ #### TbFrame
95
+ | 이름 | 타입 | 기본값 | 설명 |
96
+ | --- | --- | --- | --- |
97
+ | `setting` | `tbframeType` | - | 로그인/시작 탭 등 기본 프레임 설정 |
98
+ | `top` | `topType` | - | 상단 영역 설정 |
99
+ | `nav` | `navType` | - | 네비게이션/탭 정의 |
100
+ | `bottom` | `bottomType` | `undefined` | 하단 영역 |
101
+ | `top_banner` | `React.ReactNode` | `undefined` | 상단 배너 영역 |
102
+ | `hashmode` | `boolean` | `false` | 해시 라우팅 사용 여부 |
103
+ | `frameStyle` | `FrameStyleType` | `undefined` | 스타일 오버라이드 |
104
+
105
+ #### Tbpage
106
+ | 이름 | 타입 | 기본값 | 설명 |
107
+ | --- | --- | --- | --- |
108
+ | `tableProps` | `any` | - | AntBaseTable에 전달되는 props |
109
+ | `searchOption` | `any` | `undefined` | SearchBox 옵션 |
110
+ | `searchDisabled` | `boolean` | `false` | 검색 비활성화 |
111
+ | `onSubmit` | `(searchdata?, rowdata?, isReset?) => void` | `undefined` | 검색 제출/리셋 콜백 |
112
+ | `isLoading` | `boolean` | `undefined` | 로딩 오버레이 |
113
+ | `title`/`decs` | `string` | `undefined` | 타이틀/설명 |
114
+ | `PageStyle` | `PageStyleType` | `undefined` | 스타일 오버라이드 |
115
+
116
+ > 더 많은 props 및 타입은 `docs/API.md`를 참고하세요.
117
+
118
+ ### 반환값/에러
119
+ | 항목 | 반환 | 에러/주의사항 |
120
+ | --- | --- | --- |
121
+ | `useTbState()` | `ProviderType` | `TbProvider` 밖에서 호출 시 Error throw |
122
+
123
+ ## 7) API Reference
124
+ API가 많은 관계로 `docs/API.md`로 분리했습니다.
125
+ - 핵심 export: `TbProvider`, `useTbState`, `tbContext`, `TbFrame`, `TbpProvider`, `ContentLayout`, `Tbpage`, `TbpageSm`, `Tblist`, `TbpageMulti`, `AntBaseTable`, `AntBaseModalCreate`, `AuthTable` 및 타입들
126
+
127
+ ## 8) TypeScript
128
+ - 타입 엔트리: `package.json`의 `types: ./dist/cjs/index.d.ts`
129
+ - 공개 타입: `export type * from "./types"`로 노출
130
+
131
+ ```ts
132
+ import type { tbframeType, navType, antBasePropsType } from "@thefreshop/tb";
133
+ ```
134
+
135
+ ## 9) Compatibility
136
+ - **Runtime**: 브라우저 기반 React 앱 (DOM lib 사용)
137
+ - **React**: `react@^19.1.1` (peer)
138
+ - **Router**: `react-router@^7.10.0` (peer)
139
+ - **UI**: `antd@^6.0.0` (peer)
140
+ - **CJS/ESM**: `main`(CJS), `module`(ESM) 빌드 제공
141
+ - **Node 엔진**: Not found in repository
142
+
143
+ ## 10) Bundling / Tree-shaking
144
+ - `exports`/`sideEffects` 필드가 없어 트리 셰이킹 동작은 번들러 설정에 의존합니다.
145
+ - Rollup 빌드에서 CSS Modules를 런타임 inject 하므로 일부 부수효과(side effects)가 있을 수 있습니다.
146
+
147
+ ## 11) Examples
148
+ ### Basic (프레임 구성)
149
+ ```tsx
150
+ import { TbProvider, TbpProvider, TbFrame } from "@thefreshop/tb";
151
+ import type { navType, topType, tbframeType } from "@thefreshop/tb";
152
+
153
+ const setting: tbframeType = { islogin: false };
154
+ const top: topType = { title: "Admin", topMenuSetting: [{ key: "main", title: "Main" }] };
155
+ const nav: navType = {
156
+ menuSet: [
157
+ {
158
+ parentkey: "main",
159
+ menuSetting: [{ tab: { key: "home", title: "Home", page: <div>Home</div> }, hasPage: true }],
160
+ },
161
+ ],
162
+ };
163
+
164
+ export default function App() {
165
+ return (
166
+ <TbProvider>
167
+ <TbpProvider>
168
+ <TbFrame setting={setting} top={top} nav={nav} />
169
+ </TbpProvider>
170
+ </TbProvider>
171
+ );
172
+ }
173
+ ```
174
+
175
+ ### Advanced (검색 + 테이블)
176
+ ```tsx
177
+ import { Tbpage } from "@thefreshop/tb";
178
+
179
+ const columns = [
180
+ { tableProps: { dataIndex: "name", title: "Name" }, search: true, create: true, modify: true },
181
+ ];
182
+
183
+ export default function Users() {
184
+ return (
185
+ <Tbpage
186
+ title="Users"
187
+ searchOption={[{ key: "base", items: [{ type: "input", key: "name", title: "Name" }] }]}
188
+ tableProps={{ rowKey: "id", columns, dataSource: [] }}
189
+ onSubmit={(query, data) => {
190
+ console.log(query, data);
191
+ }}
192
+ />
193
+ );
194
+ }
195
+ ```
196
+
197
+ ### Real-world (멀티 패널)
198
+ ```tsx
199
+ import { TbpageMulti } from "@thefreshop/tb";
200
+
201
+ export default function Dashboard() {
202
+ return (
203
+ <TbpageMulti
204
+ title="Dashboard"
205
+ arrtableProps={[
206
+ { type: "table", title: "Left", tableprops: { rowKey: "id", columns: [], dataSource: [] } },
207
+ { type: "module", title: "Right", module: <div>Custom Module</div> },
208
+ ]}
209
+ arrflex={[2, 1]}
210
+ direction="row"
211
+ />
212
+ );
213
+ }
214
+ ```
215
+
216
+ ## 12) Troubleshooting
217
+ 1. **peerDependencies 충돌**: React/antd/react-router 버전이 맞지 않으면 런타임 오류가 발생합니다.
218
+ 2. **CSS Modules 미적용**: 소비 앱 번들러에서 CSS Modules가 비활성화되면 레이아웃이 깨집니다.
219
+ 3. **SSR 환경 오류**: `sessionStorage` 사용으로 SSR 시 `window` 관련 에러가 날 수 있습니다.
220
+ 4. **CJS/ESM 혼용 오류**: 번들러가 `module`/`main` 선택을 잘못하면 중복 React 경고가 발생할 수 있습니다.
221
+ 5. **타입 인식 실패**: `types` 경로(`dist/cjs/index.d.ts`)가 올바르게 배포되었는지 확인하세요.
222
+ 6. **Ant Design 스타일 누락**: 소비 앱에서 antd 스타일을 로드하지 않으면 컴포넌트가 깨집니다.
223
+ 7. **모달 폼 초기값 미반영**: `AntBaseModalCreate`에서 `initialValues` 사용 시 `open` 타이밍을 확인하세요.
224
+
225
+ ## 13) FAQ
226
+ 1. **React 18에서도 동작하나요?** Not found in repository (peerDependencies는 React 19 기준입니다).
227
+ 2. **React Router v6 사용 가능?** Not found in repository (peerDependencies는 v7 기준입니다).
228
+ 3. **라우팅 구조는 어떻게 만들나요?** `nav.menuSet`과 `nav.globalTabs` 기반으로 라우트가 자동 생성됩니다.
229
+ 4. **해시 라우팅을 지원하나요?** `TbFrame`의 `hashmode`로 지원합니다.
230
+ 5. **탭 상태는 어디에 저장되나요?** `sessionStorage`의 `tbFrameState` 키에 저장됩니다.
231
+ 6. **로케일 설정은 어디서 하나요?** `TbpProvider`가 `antd` `ConfigProvider`와 `ko_KR` 로케일을 설정합니다.
232
+ 7. **테스트 예제나 Storybook이 있나요?** Not found in repository.
233
+ 8. **변경 이력(Changelog)이 있나요?** No changelog found.
234
+
235
+ ## 14) Contributing
236
+ ### 로컬 개발
237
+ ```bash
238
+ yarn install
239
+ ```
240
+
241
+ ```bash
242
+ yarn dev
243
+ ```
244
+
245
+ ```bash
246
+ yarn build
247
+ ```
248
+
249
+ - 테스트/린트 스크립트: Not found in repository
250
+ - 배포 스크립트: Not found in repository
251
+
252
+ ## 15) Security
253
+ - 공급망 보안: peerDependencies 버전을 고정/검증하세요.
254
+ - 토큰/시크릿 관리: 레포에 환경변수 파일이 없으므로 소비 앱에서 안전하게 관리하세요.
255
+ - 취약점 신고: `package.json`의 `bugs.url` 참고.
256
+
257
+ ## 16) License
258
+ - `package.json`: MIT
259
+ - LICENSE 파일: No license file found
260
+
261
+ ## 17) Quick Commands
262
+ | 목적 | 명령 | 비고 |
263
+ | --- | --- | --- |
264
+ | 개발(와치) | ```bash
265
+ yarn dev
266
+ ``` | Rollup watch 빌드 |
267
+ | 빌드 | ```bash
268
+ yarn build
269
+ ``` | `dist/` 출력 |
270
+ | 테스트 | Not found in repository | - |
271
+ | 린트 | Not found in repository | - |
272
+ | 타입체크 | Not found in repository | - |
273
+ | 퍼블리시 | Not found in repository | - |
package/dist/cjs/index.js CHANGED
@@ -403,11 +403,11 @@ var css_248z$7 = ".main-module_mainFrame__Etjvl{height:100%;width:100%}.main-mod
403
403
  var styles$5 = {"mainFrame":"main-module_mainFrame__Etjvl","mainFrameFullscreen":"main-module_mainFrameFullscreen__oEwV8"};
404
404
  styleInject(css_248z$7);
405
405
 
406
- const Main = () => {
406
+ const Main = ({ style }) => {
407
407
  const [sp] = reactRouter.useSearchParams();
408
408
  const isFs = sp.get("fs") === "1" || sp.get("fullscreen") === "1";
409
409
  // console.log("Main render, isFs:", isFs);
410
- return (React.createElement("div", { className: isFs ? styles$5.mainFrameFullscreen : styles$5.mainFrame },
410
+ return (React.createElement("div", { className: isFs ? styles$5.mainFrameFullscreen : styles$5.mainFrame, style: style },
411
411
  React.createElement(reactRouter.Outlet, null)));
412
412
  };
413
413
 
@@ -636,7 +636,7 @@ const MainLayout = ({ errMsg, setting, top_banner, nav, user, top, bottom, frame
636
636
  React.createElement(Nav, { nav: nav, islogin: setting.islogin ?? false, frameStyle: frameStyle }),
637
637
  React.createElement("div", { className: styles$9.contentsFrame, style: frameStyle?.contentsFrame },
638
638
  React.createElement(Tabs, { nav: nav, frameStyle: frameStyle }),
639
- React.createElement(Main, null))),
639
+ React.createElement(Main, { style: frameStyle?.contentsMainFrame }))),
640
640
  React.createElement("div", { className: styles$9.bottomFrame, style: frameStyle?.bottomFrame }, bottom && React.createElement(Bottom, { bottom: bottom, frameStyle: frameStyle }))))));
641
641
  };
642
642
 
@@ -705,7 +705,7 @@ const FORM_CREATE = "등록";
705
705
 
706
706
  * @todo
707
707
  */
708
- const TableTopButtonGroup = ({ isSelect, onRefresh, isCounter, autoRefreshTime, addProps, modifyProps, deleteProps, removeProps, resetProps, customProps, }) => {
708
+ const TableTopButtonGroup = ({ isSelect, onRefresh, isCounter, autoRefreshTime, addProps, modifyProps, deleteProps, removeProps, resetProps, customProps, isInSearch = false, onInSearch, }) => {
709
709
  if (!addProps && !modifyProps && !deleteProps && !removeProps && !resetProps && !customProps && !onRefresh) {
710
710
  return null;
711
711
  }
@@ -752,7 +752,8 @@ const TableTopButtonGroup = ({ isSelect, onRefresh, isCounter, autoRefreshTime,
752
752
  deleteProps ? (React.createElement(antd.Button, { danger: true, disabled: !isSelect || deleteProps.disable, onClick: deleteProps.onFun }, deleteProps.title ? deleteProps.title : TABLE_DELETE)) : null,
753
753
  resetProps ? (React.createElement(antd.Button, { disabled: !isSelect || resetProps.disable, onClick: resetProps.onFun }, resetProps.title ? resetProps.title : TABLE_RESET)) : null,
754
754
  removeProps ? (React.createElement(antd.Button, { danger: true, type: "primary", disabled: !isSelect || removeProps.disable, onClick: removeProps.onFun }, removeProps.title ? removeProps.title : TABLE_REMOVE)) : null,
755
- customProps?.rightRender && customProps?.rightRender()))));
755
+ customProps?.rightRender && customProps?.rightRender(),
756
+ isInSearch && onInSearch ? React.createElement(InSearchForm, { onInSearch: onInSearch }) : null))));
756
757
  };
757
758
  const TableBottomButtonGroup = ({ isSelect, topProps, bottomProps, upProps, downProps, }) => {
758
759
  return (React.createElement(antd.Row, { justify: "space-between" },
@@ -766,6 +767,22 @@ const TableBottomButtonGroup = ({ isSelect, topProps, bottomProps, upProps, down
766
767
  downProps ? (React.createElement(antd.Button, { disabled: !isSelect || downProps.disable, onClick: downProps.onFun }, TABLE_DOWN)) : null,
767
768
  bottomProps ? (React.createElement(antd.Button, { disabled: !isSelect || bottomProps.disable, onClick: bottomProps.onFun }, TABLE_BOTTOM)) : null))));
768
769
  };
770
+ const InSearchForm = ({ onInSearch }) => {
771
+ const [inputSearch, setInputSearch] = React.useState("");
772
+ const SearchClick = () => {
773
+ onInSearch(inputSearch);
774
+ };
775
+ const activeEnter = (e) => {
776
+ if (e.key === "Enter") {
777
+ SearchClick();
778
+ }
779
+ };
780
+ return (React.createElement(antd.Space.Compact, null,
781
+ React.createElement(antd.Input, { style: { width: "150px" }, onChange: (e) => {
782
+ setInputSearch(e.target.value);
783
+ }, value: inputSearch, onKeyDown: (e) => activeEnter(e) }),
784
+ React.createElement(antd.Button, { onClick: SearchClick }, "\uAC80\uC0C9")));
785
+ };
769
786
 
770
787
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
771
788
 
@@ -23343,11 +23360,24 @@ const AntBaseModalCreate = ({ modalProps, formProps, props }) => {
23343
23360
 
23344
23361
  const { Title: Title$1 } = antd.Typography;
23345
23362
  const { confirm: confirm$1 } = antd.Modal;
23346
- const AntBaseTable = ({ bordered, className, rowKey, rowName, dataSource, initData, customColumn, customValue, columns, expandable, size, onDoubleClick, onClick, selectProps, addProps, modifyProps, deleteProps, removeProps, resetProps, customProps, topProps, bottomProps, upProps, downProps, pageProps, scroll, onRefresh, isCounter, autoRefreshTime, createModalWidth = 1000, onCreateClose, }, ref) => {
23363
+ const AntBaseTable = ({ bordered, className, rowKey, rowName, dataSource, initData, customColumn, customValue, columns, expandable, size, onDoubleClick, onClick, selectProps, addProps, modifyProps, deleteProps, removeProps, resetProps, customProps, topProps, bottomProps, upProps, downProps, pageProps, scroll, onRefresh, isCounter, autoRefreshTime, createModalWidth = 1000, onCreateClose, isInSearch = false, }, ref) => {
23347
23364
  const [selectedRowKeys, setSelectedRowKeys] = React.useState([]);
23348
23365
  const [selectedRow, setSelectedRow] = React.useState();
23349
23366
  const [popupCreate, setPopupCreate] = React.useState(false);
23350
23367
  const [popupModify, setPopupModify] = React.useState(false);
23368
+ const [viewData, setViewData] = React.useState(dataSource);
23369
+ const onInSearch = (value) => {
23370
+ const search = value.trim().toLowerCase();
23371
+ if (!search) {
23372
+ setViewData(dataSource);
23373
+ return;
23374
+ }
23375
+ setViewData(dataSource.filter((item) => Object.values(item).some((itemValue) => {
23376
+ if (itemValue == null)
23377
+ return false;
23378
+ return String(itemValue).toLowerCase().includes(search);
23379
+ })));
23380
+ };
23351
23381
  React.useEffect(() => {
23352
23382
  setSelectedRow(dataSource?.find((m) => {
23353
23383
  if (m) {
@@ -23440,7 +23470,7 @@ const AntBaseTable = ({ bordered, className, rowKey, rowName, dataSource, initDa
23440
23470
  height: "100%",
23441
23471
  padding: "20px",
23442
23472
  } },
23443
- React.createElement(TableTopButtonGroup, { isSelect: isSelect, isCounter: isCounter, onRefresh: onRefresh, autoRefreshTime: autoRefreshTime, addProps: addProps && {
23473
+ React.createElement(TableTopButtonGroup, { isInSearch: isInSearch, onInSearch: onInSearch, isSelect: isSelect, isCounter: isCounter, onRefresh: onRefresh, autoRefreshTime: autoRefreshTime, addProps: addProps && {
23444
23474
  ...addProps,
23445
23475
  onFun: () => {
23446
23476
  if (addProps.customModal) {
@@ -23621,7 +23651,7 @@ const AntBaseTable = ({ bordered, className, rowKey, rowName, dataSource, initDa
23621
23651
  return {
23622
23652
  onClick: () => { }, // click header row
23623
23653
  };
23624
- }, dataSource: dataSource?.map((i) => i),
23654
+ }, dataSource: viewData?.map((i) => i),
23625
23655
  // columns={tableColumns(
23626
23656
  // columns.concat(
23627
23657
  // deleteCol({