@_tc/template-core 0.1.4 → 0.1.5

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.
@@ -0,0 +1,425 @@
1
+ # Complete Examples
2
+
3
+ Copy-paste starting points for common TC UI scenarios.
4
+
5
+ ---
6
+
7
+ ## Example 1: CRUD List Page
8
+
9
+ ```tsx
10
+ import { useState, useEffect, useCallback } from 'react'
11
+ import { DataTable, TableSearch, Button, Modal, SchemaForm, ConfirmDialog, message } from '@tc/ui/react'
12
+ import type { TableColumnDef, TableSearchSchema } from '@tc/ui/react'
13
+
14
+ interface Product {
15
+ id: number
16
+ name: string
17
+ price: number
18
+ status: 'active' | 'inactive'
19
+ createdAt: string
20
+ }
21
+
22
+ interface ProductSearch {
23
+ name?: string
24
+ status?: string
25
+ }
26
+
27
+ interface ProductForm {
28
+ name: string
29
+ price: number
30
+ status: 'active' | 'inactive'
31
+ }
32
+
33
+ export function ProductList() {
34
+ const [data, setData] = useState<Product[]>([])
35
+ const [loading, setLoading] = useState(false)
36
+ const [modalOpen, setModalOpen] = useState(false)
37
+ const [editingId, setEditingId] = useState<number | null>(null)
38
+ const [deleteId, setDeleteId] = useState<number | null>(null)
39
+ const [form] = Form.useForm<ProductForm>()
40
+
41
+ const searchSchemas: TableSearchSchema<ProductSearch>[] = [
42
+ { key: 'name', label: 'Name', type: 'input', isPermanent: true },
43
+ { key: 'status', label: 'Status', type: 'select',
44
+ fieldProps: { options: [{ label: 'Active', value: 'active' }, { label: 'Inactive', value: 'inactive' }] } },
45
+ ]
46
+
47
+ const columns: TableColumnDef<Product>[] = [
48
+ { key: 'id', title: 'ID', dataIndex: 'id', width: 80 },
49
+ { key: 'name', title: 'Name', dataIndex: 'name' },
50
+ { key: 'price', title: 'Price', dataIndex: 'price',
51
+ render: (value) => `¥${value}` },
52
+ { key: 'status', title: 'Status', dataIndex: 'status',
53
+ render: (value) => (
54
+ <span className={value === 'active' ? 'text-green-600' : 'text-gray-400'}>
55
+ {value}
56
+ </span>
57
+ ) },
58
+ { key: 'createdAt', title: 'Created', dataIndex: 'createdAt' },
59
+ { key: 'action', title: 'Actions', type: 'action',
60
+ actions: [
61
+ { key: 'edit', label: 'Edit', onClick: (record) => openEdit(record) },
62
+ { key: 'delete', label: 'Delete', danger: true, onClick: (record) => setDeleteId(record.id) },
63
+ ] },
64
+ ]
65
+
66
+ const fetchData = useCallback(async (search?: ProductSearch) => {
67
+ setLoading(true)
68
+ try {
69
+ const res = await api.getProducts(search)
70
+ setData(res)
71
+ } finally {
72
+ setLoading(false)
73
+ }
74
+ }, [])
75
+
76
+ const openCreate = () => {
77
+ setEditingId(null)
78
+ form.resetFields()
79
+ setModalOpen(true)
80
+ }
81
+
82
+ const openEdit = (record: Product) => {
83
+ setEditingId(record.id)
84
+ form.setFieldsValue(record)
85
+ setModalOpen(true)
86
+ }
87
+
88
+ const handleSubmit = async (values: ProductForm) => {
89
+ if (editingId !== null) {
90
+ await api.updateProduct(editingId, values)
91
+ message.success('Updated')
92
+ } else {
93
+ await api.createProduct(values)
94
+ message.success('Created')
95
+ }
96
+ setModalOpen(false)
97
+ fetchData()
98
+ }
99
+
100
+ const handleDelete = async () => {
101
+ if (deleteId === null) return
102
+ await api.deleteProduct(deleteId)
103
+ message.success('Deleted')
104
+ setDeleteId(null)
105
+ fetchData()
106
+ }
107
+
108
+ useEffect(() => { fetchData() }, [fetchData])
109
+
110
+ return (
111
+ <div>
112
+ <TableSearch<ProductSearch>
113
+ schemas={searchSchemas}
114
+ onSearch={fetchData}
115
+ onReset={() => fetchData()}
116
+ renderActionBtnArea={() => (
117
+ <Button variant="primary" onClick={openCreate}>Create Product</Button>
118
+ )}
119
+ />
120
+ <DataTable<Product>
121
+ columns={columns}
122
+ data={data}
123
+ rowKey="id"
124
+ loading={loading}
125
+ />
126
+
127
+ <Modal open={modalOpen} onClose={() => setModalOpen(false)}
128
+ title={editingId !== null ? 'Edit Product' : 'Create Product'}
129
+ footer={
130
+ <div className="flex justify-end gap-2">
131
+ <Button onClick={() => setModalOpen(false)}>Cancel</Button>
132
+ <Button variant="primary" onClick={() => form.submit()}>Save</Button>
133
+ </div>
134
+ }
135
+ >
136
+ <SchemaForm<ProductForm>
137
+ form={form}
138
+ schemas={[
139
+ { key: 'name', label: 'Name', type: 'input', required: true },
140
+ { key: 'price', label: 'Price', type: 'inputNumber',
141
+ fieldProps: { min: 0, precision: 2, allowStep: true } },
142
+ { key: 'status', label: 'Status', type: 'select',
143
+ fieldProps: { options: [{ label: 'Active', value: 'active' }, { label: 'Inactive', value: 'inactive' }] } },
144
+ ]}
145
+ footerButtons={false}
146
+ onFinish={handleSubmit}
147
+ />
148
+ </Modal>
149
+
150
+ <ConfirmDialog
151
+ open={deleteId !== null}
152
+ title="Delete Product"
153
+ content="This action cannot be undone."
154
+ onOk={handleDelete}
155
+ onCancel={() => setDeleteId(null)}
156
+ />
157
+ </div>
158
+ )
159
+ }
160
+ ```
161
+
162
+ ---
163
+
164
+ ## Example 2: Detail Drawer
165
+
166
+ ```tsx
167
+ import { Drawer, Button, Loading, Skeleton } from '@tc/ui/react'
168
+
169
+ export function UserDetailDrawer({ userId, open, onClose }: {
170
+ userId: number | null
171
+ open: boolean
172
+ onClose: () => void
173
+ }) {
174
+ const [user, setUser] = useState<User | null>(null)
175
+ const [loading, setLoading] = useState(false)
176
+
177
+ useEffect(() => {
178
+ if (userId && open) {
179
+ setLoading(true)
180
+ api.getUser(userId).then(setUser).finally(() => setLoading(false))
181
+ }
182
+ }, [userId, open])
183
+
184
+ return (
185
+ <Drawer open={open} onClose={onClose}
186
+ title="User Details" placement="right" width={520}
187
+ >
188
+ {loading ? (
189
+ <Skeleton mode="componentsloading" rows={6} showTitle />
190
+ ) : user ? (
191
+ <div className="space-y-4">
192
+ <Field label="Name" value={user.name} />
193
+ <Field label="Email" value={user.email} />
194
+ <Field label="Role" value={user.role} />
195
+ <Field label="Status" value={user.active ? 'Active' : 'Inactive'} />
196
+ </div>
197
+ ) : (
198
+ <p className="text-gray-400">No data</p>
199
+ )}
200
+ </Drawer>
201
+ )
202
+ }
203
+ ```
204
+
205
+ ---
206
+
207
+ ## Example 3: App Shell with Sidebar Menu
208
+
209
+ ```tsx
210
+ import { Layout, Menu, Breadcrumb } from '@tc/ui/react'
211
+ import { useNavigate, useLocation } from 'react-router-dom'
212
+
213
+ export function AppShell({ children }: { children: ReactNode }) {
214
+ const navigate = useNavigate()
215
+ const { pathname } = useLocation()
216
+
217
+ return (
218
+ <Layout
219
+ sidebar={
220
+ <div className="flex flex-col h-full">
221
+ <div className="h-14 flex items-center px-4 font-bold text-lg">
222
+ My App
223
+ </div>
224
+ <Menu
225
+ mode="left"
226
+ selectedKey={pathname}
227
+ onSelect={(key) => navigate(key)}
228
+ items={[
229
+ { key: '/dashboard', label: 'Dashboard', icon: <DashboardIcon /> },
230
+ { key: '/products', label: 'Products', icon: <ProductIcon /> },
231
+ { key: '/users', label: 'Users', icon: <UserIcon />,
232
+ children: [
233
+ { key: '/users/list', label: 'All Users' },
234
+ { key: '/users/roles', label: 'Roles' },
235
+ ] },
236
+ { key: '/settings', label: 'Settings', icon: <SettingsIcon /> },
237
+ ]}
238
+ />
239
+ </div>
240
+ }
241
+ >
242
+ <div className="p-6">
243
+ <SimpleBreadcrumb />
244
+ {children}
245
+ </div>
246
+ </Layout>
247
+ )
248
+ }
249
+ ```
250
+
251
+ ---
252
+
253
+ ## Example 4: Image Upload Form
254
+
255
+ ```tsx
256
+ import { SchemaForm, ImageUpload } from '@tc/ui/react'
257
+
258
+ interface BannerForm {
259
+ title: string
260
+ images: string[]
261
+ }
262
+
263
+ function BannerEditor() {
264
+ const [form] = Form.useForm<BannerForm>()
265
+
266
+ return (
267
+ <SchemaForm<BannerForm>
268
+ form={form}
269
+ schemas={[
270
+ { key: 'title', label: 'Title', type: 'input', required: true },
271
+ { key: 'images', label: 'Banner Images', type: 'input',
272
+ render: ({ value, onChange }) => (
273
+ <ImageUpload
274
+ value={value}
275
+ onChange={onChange}
276
+ maxCount={5}
277
+ accept=".jpg,.png,.webp"
278
+ beforeChange={(file) => {
279
+ if (file.size > 5 * 1024 * 1024) {
280
+ message.warning('Max 5MB per image')
281
+ return false
282
+ }
283
+ }}
284
+ />
285
+ ) },
286
+ ]}
287
+ onFinish={handleSubmit}
288
+ />
289
+ )
290
+ }
291
+ ```
292
+
293
+ ---
294
+
295
+ ## Example 5: TreeSelect for Permissions
296
+
297
+ ```tsx
298
+ import { TreeSelect } from '@tc/ui/react'
299
+
300
+ function PermissionEditor() {
301
+ const [checked, setChecked] = useState<(string | number)[]>([])
302
+ const treeData = [
303
+ { id: '1', name: 'System', children: [
304
+ { id: '1-1', name: 'User Management' },
305
+ { id: '1-2', name: 'Role Management' },
306
+ { id: '1-3', name: 'Settings' },
307
+ ] },
308
+ { id: '2', name: 'Content', children: [
309
+ { id: '2-1', name: 'Articles' },
310
+ { id: '2-2', name: 'Comments' },
311
+ ] },
312
+ ]
313
+
314
+ return (
315
+ <TreeSelect
316
+ data={treeData}
317
+ value={checked}
318
+ onChange={({ checked: newChecked }) => setChecked(newChecked)}
319
+ />
320
+ )
321
+ }
322
+ ```
323
+
324
+ ---
325
+
326
+ ## Example 6: DataTable with Row Selection (Batch Actions)
327
+
328
+ ```tsx
329
+ import { DataTable, Button, message } from '@tc/ui/react'
330
+
331
+ function BatchUserList() {
332
+ const [selectedKeys, setSelectedKeys] = useState<(string | number)[]>([])
333
+ const [users, setUsers] = useState<User[]>([])
334
+
335
+ const handleBatchDelete = async () => {
336
+ await api.batchDeleteUsers(selectedKeys as number[])
337
+ message.success(`Deleted ${selectedKeys.length} users`)
338
+ setSelectedKeys([])
339
+ fetchUsers()
340
+ }
341
+
342
+ return (
343
+ <div>
344
+ {selectedKeys.length > 0 && (
345
+ <div className="mb-4 flex gap-2 items-center">
346
+ <span>{selectedKeys.length} selected</span>
347
+ <Button state="danger" onClick={handleBatchDelete}>
348
+ Batch Delete
349
+ </Button>
350
+ </div>
351
+ )}
352
+ <DataTable<User>
353
+ columns={columns}
354
+ data={users}
355
+ rowKey="id"
356
+ rowSelection={{
357
+ selectedRowKeys: selectedKeys,
358
+ onChange: (keys) => setSelectedKeys(keys),
359
+ }}
360
+ />
361
+ </div>
362
+ )
363
+ }
364
+ ```
365
+
366
+ ---
367
+
368
+ ## Example 7: DatePicker Usage
369
+
370
+ ```tsx
371
+ import { DatePicker } from '@tc/ui/react'
372
+
373
+ // Single date
374
+ <DatePicker
375
+ value={startDate}
376
+ onChange={(v) => setStartDate(v)}
377
+ />
378
+
379
+ // Range with time
380
+ <DatePicker
381
+ mode="range"
382
+ showTime
383
+ value={dateRange}
384
+ onChange={(v) => setDateRange(v)}
385
+ quickRanges={[
386
+ { label: 'This Month', range: [monthStart, new Date()] },
387
+ ]}
388
+ />
389
+
390
+ // In SchemaForm
391
+ { key: 'birthday', label: 'Birthday', type: 'date' }
392
+ { key: 'period', label: 'Period', type: 'date',
393
+ fieldProps: { mode: 'range', showTime: true } }
394
+ ```
395
+
396
+ ---
397
+
398
+ ## Example 8: Tooltip & Popup
399
+
400
+ ```tsx
401
+ import { Tooltip, Popup, Dropdown, Button } from '@tc/ui/react'
402
+
403
+ // Simple tooltip
404
+ <Tooltip content="Click to save changes">
405
+ <Button><SaveIcon /></Button>
406
+ </Tooltip>
407
+
408
+ // Dropdown menu
409
+ <Dropdown items={[
410
+ { key: 'edit', label: 'Edit', onClick: handleEdit },
411
+ { key: 'share', label: 'Share', onClick: handleShare },
412
+ { key: 'delete', label: 'Delete', danger: true, onClick: handleDelete },
413
+ ]}>
414
+ <Button>Actions <ChevronDown /></Button>
415
+ </Dropdown>
416
+
417
+ // Custom popup
418
+ <Popup
419
+ content={<UserCard user={user} />}
420
+ trigger="click"
421
+ onVisibleChange={(v) => console.log('popup:', v)}
422
+ >
423
+ <span className="cursor-pointer underline">{user.name}</span>
424
+ </Popup>
425
+ ```