@_tc/template-core 0.1.13 → 0.1.15
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/.skills/tc-component-usage-skills/SKILL.md +31 -9
- package/.skills/tc-component-usage-skills/reference/component-api.md +63 -42
- package/.skills/tc-component-usage-skills/reference/examples.md +34 -24
- package/.skills/tc-component-usage-skills/reference/patterns.md +52 -17
- package/.skills/tc-component-usage-skills/reference/template-core-frontend.md +104 -0
- package/.skills/tc-generator/SKILL.md +11 -3
- package/.skills/tc-generator/reference/example.md +1 -1
- package/.skills/tc-generator/reference/model-schema.md +4 -2
- package/.skills/tc-generator/reference/project-template/config/config.default.js +4 -0
- package/.skills/tc-generator/reference/project-template/index.js +1 -1
- package/.skills/tc-generator/reference/project-template/model/product/project/default.js +1 -0
- package/.skills/tc-generator/reference/project-template/package.json +16 -1
- package/.skills/tc-generator/reference/runtime-extensions.md +190 -0
- package/README.md +217 -2
- package/cjs/app/extend/db.js +20 -1
- package/cjs/bundler/utils.js +1 -1
- package/esm/app/extend/db.js +138 -3
- package/esm/bundler/utils.js +2 -1
- package/fe/frontend/apps/dash/Dashboard.js +3 -1
- package/fe/frontend/apps/dash/types.d.ts +8 -0
- package/fe/frontend/src/components/LanguageSwitch/LanguageSwitch.d.ts +14 -0
- package/fe/frontend/src/components/LanguageSwitch/LanguageSwitch.js +32 -0
- package/fe/frontend/src/components/LanguageSwitch/index.d.ts +4 -0
- package/fe/frontend/src/components/LanguageSwitch/index.js +3 -0
- package/fe/frontend/src/components/ThemeSwitch/ThemeSwitch.d.ts +15 -0
- package/fe/frontend/src/components/ThemeSwitch/ThemeSwitch.js +59 -0
- package/fe/frontend/src/components/ThemeSwitch/index.d.ts +4 -0
- package/fe/frontend/src/components/ThemeSwitch/index.js +3 -0
- package/fe/frontend/src/components/index.d.ts +11 -0
- package/fe/frontend/src/components/index.js +10 -0
- package/fe/frontend/src/index.d.ts +1 -0
- package/fe/frontend/src/index.js +1 -0
- package/fe/frontend/src/language/en-US.d.ts +13 -0
- package/fe/frontend/src/language/en-US.js +13 -0
- package/fe/frontend/src/language/index.d.ts +7 -0
- package/fe/frontend/src/language/index.js +7 -0
- package/fe/frontend/src/language/zh-CN.d.ts +13 -0
- package/fe/frontend/src/language/zh-CN.js +13 -0
- package/fe/packages/ui/react/components/Button/{SumbitButton.d.ts → SubmitButton.d.ts} +5 -5
- package/fe/packages/ui/react/components/Button/{SumbitButton.js → SubmitButton.js} +3 -3
- package/fe/packages/ui/react/components/Button/index.d.ts +1 -1
- package/fe/packages/ui/react/components/Button/index.js +1 -1
- package/fe/packages/ui/react/components/Modal/ModalManager.d.ts +3 -3
- package/fe/packages/ui/react/components/TableSearch/index.d.ts +1 -0
- package/package.json +1 -1
- package/types/app/extend/db.d.ts +54 -1
- package/types/config/config.default.d.ts +30 -0
- package/types/index.d.ts +1 -0
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tc-component-usage-skills
|
|
3
|
-
description: Use when building or modifying React UI in
|
|
3
|
+
description: Use when building or modifying React UI in TemplateCore projects. Covers how to choose, compose, and correctly use components from `@tc/ui-react` / `@_tc/template-core/fe/rc`, plus TemplateCore frontend helpers from `@_tc/template-core/fe` such as `AsyncSelect`, `LanguageSwitch`, and `ThemeSwitch`. Includes patterns for forms, tables, modals, drawers, menus, Dashboard toolbar UI, and TC visual style.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# TC Component Usage
|
|
7
7
|
|
|
8
|
-
Use this skill when writing React components that consume `@tc/ui-react`.
|
|
8
|
+
Use this skill when writing React components that consume `@tc/ui-react`, `@_tc/template-core/fe/rc`, or TemplateCore frontend helpers from `@_tc/template-core/fe`.
|
|
9
9
|
|
|
10
10
|
## Quick Decision Flow
|
|
11
11
|
|
|
12
12
|
1. **Displaying data?** → `DataTable` or `Card`
|
|
13
|
-
2. **Collecting input?** → `Form` / `SchemaForm` (built on `rc-field-form`) with `Input`, `Select`, `DatePicker`, `Switch`, `Textarea`, `InputNumber`, `Checkbox`, `Radio`, `TreeSelect`, `Upload`
|
|
13
|
+
2. **Collecting input?** → `Form` / `SchemaForm` (built on `rc-field-form`) with `Input`, `Select`, `DatePicker`, `Switch`, `Textarea`, `InputNumber`, `Checkbox`, `Radio`, `TreeSelect`, `Upload`; TemplateCore model-driven pages can also use framework field type `asyncSelect`
|
|
14
14
|
3. **Page layout?** → `Layout`
|
|
15
|
-
4. **Overlays?** → `Modal` / `
|
|
15
|
+
4. **Overlays?** → `Modal` / `modal.confirm()` / `Drawer` / `Popup` / `Tooltip` / `ImagePreview`
|
|
16
16
|
5. **Navigation?** → `Menu` / `Breadcrumb` / `Tabs` / `Pagination`
|
|
17
17
|
6. **Feedback?** → `message.success()` / `Notification` / `Loading` / `Skeleton` / `ConfirmDialog`
|
|
18
18
|
7. **Search & filter?** → `TableSearch` / `Search`
|
|
19
|
+
8. **Language/theme toolbar?** → `LanguageSwitch` / `ThemeSwitch` from `@_tc/template-core/fe`
|
|
20
|
+
9. **Remote select options?** → `AsyncSelect` from `@_tc/template-core/fe`
|
|
19
21
|
|
|
20
22
|
## Core Patterns
|
|
21
23
|
|
|
@@ -36,15 +38,15 @@ Every input-like component supports both modes. Check for `value !== undefined`
|
|
|
36
38
|
`Form` / `FormItem` / `SchemaForm` wrap `rc-field-form`. `FormItem` auto-clones children to inject `value`/`onChange`. Prefer `SchemaForm` for data-driven forms:
|
|
37
39
|
|
|
38
40
|
```tsx
|
|
39
|
-
const form = useForm<T>();
|
|
41
|
+
const [form] = useForm<T>();
|
|
40
42
|
<SchemaForm form={form} schemas={[
|
|
41
43
|
{ key: 'name', label: 'Name', type: 'input', required: true },
|
|
42
44
|
{ key: 'status', label: 'Status', type: 'select', fieldProps: { options: [...] } },
|
|
43
45
|
]} onFinish={(vals) => ...} />
|
|
44
46
|
```
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
UI-library built-in `type` values: `input`, `inputNumber`, `select`, `textarea`, `switch`, `date`, `checkbox`.
|
|
49
|
+
TemplateCore adds framework type `asyncSelect` via `@tc/scalability/SchemaForm/frameData`; custom app types are registered via `@tc/scalability/SchemaForm/data`.
|
|
48
50
|
|
|
49
51
|
### Modal (Declarative + Imperative)
|
|
50
52
|
|
|
@@ -55,7 +57,7 @@ Custom types are registered via `@tc/scalability/SchemaForm/data`.
|
|
|
55
57
|
</Modal>
|
|
56
58
|
|
|
57
59
|
// Imperative (async ok supported)
|
|
58
|
-
|
|
60
|
+
modal.confirm({
|
|
59
61
|
title: 'Confirm',
|
|
60
62
|
content: 'Are you sure?',
|
|
61
63
|
onOk: async () => { await doSomething(); },
|
|
@@ -94,6 +96,7 @@ Read the reference files (progressive disclosure):
|
|
|
94
96
|
- `reference/component-api.md` — Full props tables for every component
|
|
95
97
|
- `reference/patterns.md` — Deeper patterns: schema forms, table columns, action buttons, horizontal forms
|
|
96
98
|
- `reference/examples.md` — Copy-paste examples for common scenarios
|
|
99
|
+
- `reference/template-core-frontend.md` — TemplateCore frontend helper components and Dashboard toolbar composition
|
|
97
100
|
|
|
98
101
|
## Validation
|
|
99
102
|
|
|
@@ -106,5 +109,24 @@ pnpm type-check
|
|
|
106
109
|
## Component Import Convention
|
|
107
110
|
|
|
108
111
|
```tsx
|
|
109
|
-
import { Button, Form, FormItem, Input, DataTable, Modal } from '@tc/ui
|
|
112
|
+
import { Button, Form, FormItem, Input, DataTable, Modal, modal, useForm } from '@tc/ui-react'
|
|
113
|
+
import { AsyncSelect, LanguageSwitch, ThemeSwitch } from '@_tc/template-core/fe'
|
|
110
114
|
```
|
|
115
|
+
|
|
116
|
+
When consuming the published TemplateCore package, `@_tc/template-core/fe/rc` re-exports the UI component library:
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
import { Button, DataTable, TableSearch } from '@_tc/template-core/fe/rc'
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Style entry:
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
import '@tc/ui-react/index.css'
|
|
126
|
+
// or, in published TemplateCore frontend entries:
|
|
127
|
+
import '@_tc/template-core/fe/tailwind_ui.css'
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Visual Style
|
|
131
|
+
|
|
132
|
+
For `packages/ui/react/components` or TemplateCore business UI, keep the default product feel dark, restrained, dense, and tool-like: stable small type, subtle borders, consistent focus rings, controlled saturated accents, and complete default/hover/focus/active/disabled/loading/error states.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Component API Reference
|
|
2
2
|
|
|
3
|
-
Complete props for every `@tc/ui
|
|
3
|
+
Complete props for every `@tc/ui-react` component.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -24,6 +24,16 @@ interface ButtonProps {
|
|
|
24
24
|
|
|
25
25
|
`variant='link'` renders `<a>` tag. `SubmitButton` / `AsynchronousButton` auto-manage loading state on async onClick.
|
|
26
26
|
|
|
27
|
+
### SubmitButton / AsynchronousButton
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
interface SubmitButtonProps extends Omit<ButtonElementProps, 'onClick'> {
|
|
31
|
+
onClick?: (e: MouseEvent<HTMLButtonElement>) => Promise<void | boolean> | void
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Use `SubmitButton` or `AsynchronousButton` for async actions that should show loading and ignore repeated clicks while running. The old misspelled `SumbitButton` name should not be used in new code.
|
|
36
|
+
|
|
27
37
|
---
|
|
28
38
|
|
|
29
39
|
## Input
|
|
@@ -47,20 +57,21 @@ Note: `onChange` callback receives `(stringValue, event)` — two arguments, not
|
|
|
47
57
|
|
|
48
58
|
```typescript
|
|
49
59
|
interface InputNumberProps {
|
|
50
|
-
value?: number
|
|
60
|
+
value?: number
|
|
51
61
|
defaultValue?: number
|
|
52
|
-
onChange?: (value: number |
|
|
62
|
+
onChange?: (value: number | undefined, event: ChangeEvent<HTMLInputElement>) => void
|
|
53
63
|
min?: number
|
|
54
64
|
max?: number
|
|
55
65
|
precision?: number // decimal places
|
|
56
66
|
step?: number // default: 1
|
|
67
|
+
allowClear?: boolean | { clearIcon: ReactNode }
|
|
57
68
|
allowStep?: boolean // show stepper buttons
|
|
58
69
|
disabled?: boolean
|
|
59
70
|
className?: string
|
|
60
71
|
}
|
|
61
72
|
```
|
|
62
73
|
|
|
63
|
-
Invalid/empty input becomes `
|
|
74
|
+
Invalid/empty input becomes `undefined`. Raw during focus, formatted on blur.
|
|
64
75
|
|
|
65
76
|
---
|
|
66
77
|
|
|
@@ -145,23 +156,22 @@ interface SwitchProps {
|
|
|
145
156
|
|
|
146
157
|
```typescript
|
|
147
158
|
interface RadioGroupProps {
|
|
148
|
-
value?: string
|
|
149
|
-
defaultValue?: string
|
|
150
|
-
onChange?: (value: string
|
|
159
|
+
value?: string
|
|
160
|
+
defaultValue?: string
|
|
161
|
+
onChange?: (value: string) => void
|
|
151
162
|
direction?: 'horizontal' | 'vertical'
|
|
152
163
|
className?: string
|
|
153
164
|
children?: ReactNode
|
|
154
165
|
}
|
|
155
166
|
|
|
156
|
-
interface RadioProps {
|
|
157
|
-
|
|
167
|
+
interface RadioProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'children'> {
|
|
168
|
+
label?: ReactNode
|
|
158
169
|
disabled?: boolean
|
|
159
170
|
className?: string
|
|
160
|
-
children?: ReactNode
|
|
161
171
|
}
|
|
162
172
|
```
|
|
163
173
|
|
|
164
|
-
|
|
174
|
+
Use `<RadioGroup>` with `<Radio value="..." label="..." />`. Radio auto-detects parent context; group selection only handles string values.
|
|
165
175
|
|
|
166
176
|
---
|
|
167
177
|
|
|
@@ -304,7 +314,7 @@ interface SchemaFormProps<T> {
|
|
|
304
314
|
}
|
|
305
315
|
```
|
|
306
316
|
|
|
307
|
-
**Important:** In `horizontal` layout, `footerButtons` are NOT auto-rendered.
|
|
317
|
+
**Important:** In `horizontal` layout, `footerButtons` are NOT auto-rendered. UI-library built-in types: `input`, `inputNumber`, `select`, `textarea`, `switch`, `date`, `checkbox`. TemplateCore also registers framework type `asyncSelect`. App types are extensible via `@tc/scalability/SchemaForm/data`.
|
|
308
318
|
|
|
309
319
|
---
|
|
310
320
|
|
|
@@ -324,6 +334,18 @@ interface DataTableProps<T> {
|
|
|
324
334
|
|
|
325
335
|
Internal pagination (pageSize: 10) when no external pagination prop. Action columns are auto-sticky right. Loading shows skeleton overlay. Empty state shows no-result image.
|
|
326
336
|
|
|
337
|
+
### RowSelection
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
interface RowSelection<T> {
|
|
341
|
+
selectedRowKeys?: Array<string | number>
|
|
342
|
+
defaultSelectedRowKeys?: Array<string | number>
|
|
343
|
+
onChange?: (selectedRowKeys: Array<string | number>, selectedRows: T[], isAll: boolean) => void
|
|
344
|
+
isAll?: boolean
|
|
345
|
+
beforeCheckboxNode?: ReactNode
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
327
349
|
### TableColumnDef
|
|
328
350
|
|
|
329
351
|
```typescript
|
|
@@ -335,17 +357,16 @@ Internal pagination (pageSize: 10) when no external pagination prop. Action colu
|
|
|
335
357
|
key: string
|
|
336
358
|
title: string
|
|
337
359
|
dataIndex: string
|
|
338
|
-
width?:
|
|
339
|
-
minWidth?: number
|
|
340
|
-
maxWidth?: number
|
|
360
|
+
width?: string
|
|
341
361
|
fixed?: 'left' | 'right'
|
|
342
|
-
|
|
343
|
-
|
|
362
|
+
isAction?: boolean
|
|
363
|
+
actions?: DropdownItem[] | ((value, record, index) => DropdownItem[])
|
|
364
|
+
actionOverflow?: 'auto' | 'dropdown' | 'inline'
|
|
344
365
|
render?: (value, record, index) => ReactNode
|
|
345
366
|
}
|
|
346
367
|
```
|
|
347
368
|
|
|
348
|
-
Action columns render via `ActionBtn`
|
|
369
|
+
Action columns are detected by `key: 'action'` or `isAction: true`, and render via `ActionBtn` with `'auto'` | `'dropdown'` | `'inline'` overflow modes.
|
|
349
370
|
|
|
350
371
|
---
|
|
351
372
|
|
|
@@ -393,13 +414,13 @@ interface ModalProps {
|
|
|
393
414
|
}
|
|
394
415
|
```
|
|
395
416
|
|
|
396
|
-
###
|
|
417
|
+
### Modal Function API (imperative)
|
|
397
418
|
|
|
398
419
|
```typescript
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
420
|
+
modal.open(config: ModalConfig): string // returns id
|
|
421
|
+
modal.confirm(config: ConfirmConfig): string // async onOk supported
|
|
422
|
+
modal.close(id: string): void
|
|
423
|
+
modal.closeAll(): void
|
|
403
424
|
```
|
|
404
425
|
|
|
405
426
|
---
|
|
@@ -457,23 +478,22 @@ Wraps `Modal` with width: 450 and auto async-supporting ok button.
|
|
|
457
478
|
|
|
458
479
|
```typescript
|
|
459
480
|
interface PopupProps {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
onVisibleChange?: (visible: boolean) => void
|
|
466
|
-
trigger?: 'hover' | 'click'
|
|
467
|
-
mouseEnterDelay?: number
|
|
468
|
-
mouseLeaveDelay?: number
|
|
481
|
+
open: boolean
|
|
482
|
+
anchorRef: RefObject<HTMLElement | null>
|
|
483
|
+
children: ReactNode // popup panel content
|
|
484
|
+
placement?: DropdownPlacement // default: 'bottom-end'
|
|
485
|
+
offset?: number // default: 8
|
|
469
486
|
keepMounted?: boolean
|
|
470
|
-
|
|
487
|
+
closeOnClickOutside?: boolean
|
|
488
|
+
closeOnEscape?: boolean
|
|
489
|
+
matchAnchorWidth?: boolean
|
|
490
|
+
onOpenChange?: (open: boolean) => void
|
|
471
491
|
className?: string
|
|
472
|
-
|
|
492
|
+
container?: HTMLElement
|
|
473
493
|
}
|
|
474
494
|
```
|
|
475
495
|
|
|
476
|
-
|
|
496
|
+
Low-level positioned popup. For click-triggered menus, prefer `Dropdown`; for hints, prefer `Tooltip`.
|
|
477
497
|
|
|
478
498
|
---
|
|
479
499
|
|
|
@@ -487,14 +507,15 @@ interface DropdownItem {
|
|
|
487
507
|
danger?: boolean
|
|
488
508
|
onClick?: () => void
|
|
489
509
|
// Or custom render:
|
|
490
|
-
render?: () => ReactNode
|
|
510
|
+
render?: (item, context: { onClick: () => void; close: () => void }) => ReactNode
|
|
491
511
|
}
|
|
492
512
|
|
|
493
513
|
interface DropdownProps {
|
|
494
514
|
items: DropdownItem[]
|
|
495
|
-
children:
|
|
496
|
-
placement?:
|
|
497
|
-
|
|
515
|
+
children: ReactNode
|
|
516
|
+
placement?: DropdownPlacement
|
|
517
|
+
className?: string
|
|
518
|
+
getPopupContainer?: () => HTMLElement
|
|
498
519
|
}
|
|
499
520
|
```
|
|
500
521
|
|
|
@@ -506,9 +527,9 @@ interface DropdownProps {
|
|
|
506
527
|
interface TooltipProps {
|
|
507
528
|
content: ReactNode
|
|
508
529
|
children: ReactElement
|
|
509
|
-
placement?:
|
|
530
|
+
placement?: 'top' | 'bottom' | 'left' | 'right'
|
|
510
531
|
visible?: boolean
|
|
511
|
-
|
|
532
|
+
className?: string
|
|
512
533
|
}
|
|
513
534
|
```
|
|
514
535
|
|
|
@@ -618,7 +639,7 @@ interface PaginationProps {
|
|
|
618
639
|
current: number
|
|
619
640
|
pageSize: number
|
|
620
641
|
total: number
|
|
621
|
-
onChange: (page: number
|
|
642
|
+
onChange: (page: number) => void
|
|
622
643
|
onPageSizeChange?: (pageSize: number) => void
|
|
623
644
|
pageSizeOptions?: number[] // default: [10, 20, 50, 100]
|
|
624
645
|
className?: string
|
|
@@ -8,8 +8,8 @@ Copy-paste starting points for common TC UI scenarios.
|
|
|
8
8
|
|
|
9
9
|
```tsx
|
|
10
10
|
import { useState, useEffect, useCallback } from 'react'
|
|
11
|
-
import { DataTable, TableSearch, Button, Modal, SchemaForm, ConfirmDialog, message } from '@tc/ui
|
|
12
|
-
import type { TableColumnDef, TableSearchSchema } from '@tc/ui
|
|
11
|
+
import { DataTable, TableSearch, Button, Modal, SchemaForm, ConfirmDialog, message, useForm } from '@tc/ui-react'
|
|
12
|
+
import type { TableColumnDef, TableSearchSchema } from '@tc/ui-react'
|
|
13
13
|
|
|
14
14
|
interface Product {
|
|
15
15
|
id: number
|
|
@@ -36,7 +36,7 @@ export function ProductList() {
|
|
|
36
36
|
const [modalOpen, setModalOpen] = useState(false)
|
|
37
37
|
const [editingId, setEditingId] = useState<number | null>(null)
|
|
38
38
|
const [deleteId, setDeleteId] = useState<number | null>(null)
|
|
39
|
-
const [form] =
|
|
39
|
+
const [form] = useForm<ProductForm>()
|
|
40
40
|
|
|
41
41
|
const searchSchemas: TableSearchSchema<ProductSearch>[] = [
|
|
42
42
|
{ key: 'name', label: 'Name', type: 'input', isPermanent: true },
|
|
@@ -45,7 +45,7 @@ export function ProductList() {
|
|
|
45
45
|
]
|
|
46
46
|
|
|
47
47
|
const columns: TableColumnDef<Product>[] = [
|
|
48
|
-
{ key: 'id', title: 'ID', dataIndex: 'id', width:
|
|
48
|
+
{ key: 'id', title: 'ID', dataIndex: 'id', width: '80px' },
|
|
49
49
|
{ key: 'name', title: 'Name', dataIndex: 'name' },
|
|
50
50
|
{ key: 'price', title: 'Price', dataIndex: 'price',
|
|
51
51
|
render: (value) => `¥${value}` },
|
|
@@ -56,10 +56,10 @@ export function ProductList() {
|
|
|
56
56
|
</span>
|
|
57
57
|
) },
|
|
58
58
|
{ key: 'createdAt', title: 'Created', dataIndex: 'createdAt' },
|
|
59
|
-
{ key: 'action', title: 'Actions',
|
|
60
|
-
actions: [
|
|
61
|
-
{ key: 'edit', label: 'Edit', onClick: (
|
|
62
|
-
{ key: 'delete', label: 'Delete', danger: true, onClick: (
|
|
59
|
+
{ key: 'action', title: 'Actions',
|
|
60
|
+
actions: (_value, record) => [
|
|
61
|
+
{ key: 'edit', label: 'Edit', onClick: () => openEdit(record) },
|
|
62
|
+
{ key: 'delete', label: 'Delete', danger: true, onClick: () => setDeleteId(record.id) },
|
|
63
63
|
] },
|
|
64
64
|
]
|
|
65
65
|
|
|
@@ -164,7 +164,7 @@ export function ProductList() {
|
|
|
164
164
|
## Example 2: Detail Drawer
|
|
165
165
|
|
|
166
166
|
```tsx
|
|
167
|
-
import { Drawer, Button, Loading, Skeleton } from '@tc/ui
|
|
167
|
+
import { Drawer, Button, Loading, Skeleton } from '@tc/ui-react'
|
|
168
168
|
|
|
169
169
|
export function UserDetailDrawer({ userId, open, onClose }: {
|
|
170
170
|
userId: number | null
|
|
@@ -207,7 +207,7 @@ export function UserDetailDrawer({ userId, open, onClose }: {
|
|
|
207
207
|
## Example 3: App Shell with Sidebar Menu
|
|
208
208
|
|
|
209
209
|
```tsx
|
|
210
|
-
import { Layout, Menu, Breadcrumb } from '@tc/ui
|
|
210
|
+
import { Layout, Menu, Breadcrumb } from '@tc/ui-react'
|
|
211
211
|
import { useNavigate, useLocation } from 'react-router-dom'
|
|
212
212
|
|
|
213
213
|
export function AppShell({ children }: { children: ReactNode }) {
|
|
@@ -253,7 +253,7 @@ export function AppShell({ children }: { children: ReactNode }) {
|
|
|
253
253
|
## Example 4: Image Upload Form
|
|
254
254
|
|
|
255
255
|
```tsx
|
|
256
|
-
import { SchemaForm, ImageUpload } from '@tc/ui
|
|
256
|
+
import { SchemaForm, ImageUpload, message, useForm } from '@tc/ui-react'
|
|
257
257
|
|
|
258
258
|
interface BannerForm {
|
|
259
259
|
title: string
|
|
@@ -261,7 +261,7 @@ interface BannerForm {
|
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
function BannerEditor() {
|
|
264
|
-
const [form] =
|
|
264
|
+
const [form] = useForm<BannerForm>()
|
|
265
265
|
|
|
266
266
|
return (
|
|
267
267
|
<SchemaForm<BannerForm>
|
|
@@ -269,7 +269,7 @@ function BannerEditor() {
|
|
|
269
269
|
schemas={[
|
|
270
270
|
{ key: 'title', label: 'Title', type: 'input', required: true },
|
|
271
271
|
{ key: 'images', label: 'Banner Images', type: 'input',
|
|
272
|
-
render: (
|
|
272
|
+
render: (value, onChange) => (
|
|
273
273
|
<ImageUpload
|
|
274
274
|
value={value}
|
|
275
275
|
onChange={onChange}
|
|
@@ -295,7 +295,7 @@ function BannerEditor() {
|
|
|
295
295
|
## Example 5: TreeSelect for Permissions
|
|
296
296
|
|
|
297
297
|
```tsx
|
|
298
|
-
import { TreeSelect } from '@tc/ui
|
|
298
|
+
import { TreeSelect } from '@tc/ui-react'
|
|
299
299
|
|
|
300
300
|
function PermissionEditor() {
|
|
301
301
|
const [checked, setChecked] = useState<(string | number)[]>([])
|
|
@@ -326,7 +326,7 @@ function PermissionEditor() {
|
|
|
326
326
|
## Example 6: DataTable with Row Selection (Batch Actions)
|
|
327
327
|
|
|
328
328
|
```tsx
|
|
329
|
-
import { DataTable, Button, message } from '@tc/ui
|
|
329
|
+
import { DataTable, Button, message } from '@tc/ui-react'
|
|
330
330
|
|
|
331
331
|
function BatchUserList() {
|
|
332
332
|
const [selectedKeys, setSelectedKeys] = useState<(string | number)[]>([])
|
|
@@ -368,7 +368,7 @@ function BatchUserList() {
|
|
|
368
368
|
## Example 7: DatePicker Usage
|
|
369
369
|
|
|
370
370
|
```tsx
|
|
371
|
-
import { DatePicker } from '@tc/ui
|
|
371
|
+
import { DatePicker } from '@tc/ui-react'
|
|
372
372
|
|
|
373
373
|
// Single date
|
|
374
374
|
<DatePicker
|
|
@@ -398,7 +398,8 @@ import { DatePicker } from '@tc/ui/react'
|
|
|
398
398
|
## Example 8: Tooltip & Popup
|
|
399
399
|
|
|
400
400
|
```tsx
|
|
401
|
-
import { Tooltip, Popup, Dropdown, Button } from '@tc/ui
|
|
401
|
+
import { Tooltip, Popup, Dropdown, Button } from '@tc/ui-react'
|
|
402
|
+
import { useRef, useState } from 'react'
|
|
402
403
|
|
|
403
404
|
// Simple tooltip
|
|
404
405
|
<Tooltip content="Click to save changes">
|
|
@@ -415,11 +416,20 @@ import { Tooltip, Popup, Dropdown, Button } from '@tc/ui/react'
|
|
|
415
416
|
</Dropdown>
|
|
416
417
|
|
|
417
418
|
// Custom popup
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
419
|
+
function UserPopup({ user }: { user: User }) {
|
|
420
|
+
const [open, setOpen] = useState(false)
|
|
421
|
+
const anchorRef = useRef<HTMLButtonElement>(null)
|
|
422
|
+
|
|
423
|
+
return (
|
|
424
|
+
<>
|
|
425
|
+
<button ref={anchorRef} type="button" onClick={() => setOpen((v) => !v)}
|
|
426
|
+
className="cursor-pointer underline">
|
|
427
|
+
{user.name}
|
|
428
|
+
</button>
|
|
429
|
+
<Popup open={open} anchorRef={anchorRef} onOpenChange={setOpen}>
|
|
430
|
+
<UserCard user={user} />
|
|
431
|
+
</Popup>
|
|
432
|
+
</>
|
|
433
|
+
)
|
|
434
|
+
}
|
|
425
435
|
```
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
# Component Patterns
|
|
2
2
|
|
|
3
|
-
Recurring patterns and best practices for `@tc/ui
|
|
3
|
+
Recurring patterns and best practices for `@tc/ui-react`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Visual Style Pattern
|
|
8
|
+
|
|
9
|
+
For TemplateCore component work, aim for a dark, restrained, collaboration-tool feel:
|
|
10
|
+
|
|
11
|
+
- Use deep backgrounds, low-contrast panels, subtle borders, and compact text.
|
|
12
|
+
- Reserve saturated color for primary actions, selection, status, and collaboration identity.
|
|
13
|
+
- Keep page shells and panels layered; avoid decorative gradients or oversized marketing-style sections for admin UI.
|
|
14
|
+
- Every reusable component should cover default, hover, focus, active, disabled, loading, and error states where applicable.
|
|
15
|
+
- Prefer stable dimensions for controls, table cells, icon buttons, and fixed-format UI so state changes do not shift layout.
|
|
16
|
+
|
|
17
|
+
When editing this repository, `packages/ui/react/components/VIDEO_UI_STYLE_GUIDE.md` is the fuller visual reference.
|
|
4
18
|
|
|
5
19
|
---
|
|
6
20
|
|
|
@@ -35,13 +49,33 @@ const [form] = useForm<UserForm>()
|
|
|
35
49
|
|
|
36
50
|
### Registering Custom SchemaForm Types
|
|
37
51
|
|
|
52
|
+
TemplateCore already registers framework field type `asyncSelect`:
|
|
53
|
+
|
|
38
54
|
```tsx
|
|
39
|
-
|
|
55
|
+
<SchemaForm
|
|
56
|
+
schemas={[
|
|
57
|
+
{
|
|
58
|
+
key: 'ownerId',
|
|
59
|
+
label: 'Owner',
|
|
60
|
+
type: 'asyncSelect',
|
|
61
|
+
fieldProps: {
|
|
62
|
+
fetchConfig: { url: '/user/options', method: 'get' },
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
]}
|
|
66
|
+
/>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The options endpoint should return the normal TemplateCore response wrapper, usually via `this.success(ctx, options)`.
|
|
40
70
|
|
|
41
|
-
|
|
71
|
+
```tsx
|
|
72
|
+
// In the app-side module aliased as @tc/scalability/SchemaForm/data:
|
|
73
|
+
const consumerComponentsMap = {
|
|
42
74
|
richText: RichTextEditor,
|
|
43
75
|
userSelector: UserSelect,
|
|
44
|
-
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default consumerComponentsMap
|
|
45
79
|
// Now { type: 'richText', ... } works in schemas
|
|
46
80
|
```
|
|
47
81
|
|
|
@@ -61,16 +95,16 @@ When you need custom logic inside a form field:
|
|
|
61
95
|
|
|
62
96
|
`FormItem` auto-clones children to inject `value`/`onChange`. It handles Input's two-arg `onChange(value, event)` signature correctly.
|
|
63
97
|
|
|
64
|
-
### SubmitButton for Async Forms
|
|
98
|
+
### SubmitButton / AsynchronousButton for Async Forms
|
|
65
99
|
|
|
66
100
|
```tsx
|
|
67
|
-
<
|
|
101
|
+
<AsynchronousButton
|
|
68
102
|
onClick={async () => {
|
|
69
103
|
await submitToApi(form.getFieldsValue())
|
|
70
104
|
}}
|
|
71
105
|
>
|
|
72
106
|
Save
|
|
73
|
-
</
|
|
107
|
+
</AsynchronousButton>
|
|
74
108
|
```
|
|
75
109
|
|
|
76
110
|
Auto-shows loading spinner during async execution.
|
|
@@ -83,14 +117,14 @@ Auto-shows loading spinner during async execution.
|
|
|
83
117
|
|
|
84
118
|
```tsx
|
|
85
119
|
const columns: TableColumnDef<User>[] = [
|
|
86
|
-
{ key: 'id', title: 'ID', dataIndex: 'id', width:
|
|
120
|
+
{ key: 'id', title: 'ID', dataIndex: 'id', width: '80px' },
|
|
87
121
|
{ key: 'name', title: 'Name', dataIndex: 'name' },
|
|
88
122
|
{ key: 'status', title: 'Status', dataIndex: 'status',
|
|
89
123
|
render: (value) => <Badge status={value} /> },
|
|
90
|
-
{ key: 'action', title: 'Actions',
|
|
91
|
-
actions: [
|
|
92
|
-
{ key: 'edit', label: 'Edit', onClick: (
|
|
93
|
-
{ key: 'delete', label: 'Delete', danger: true, onClick: (
|
|
124
|
+
{ key: 'action', title: 'Actions',
|
|
125
|
+
actions: (_value, record) => [
|
|
126
|
+
{ key: 'edit', label: 'Edit', onClick: () => openEdit(record) },
|
|
127
|
+
{ key: 'delete', label: 'Delete', danger: true, onClick: () => onDelete(record) },
|
|
94
128
|
] },
|
|
95
129
|
]
|
|
96
130
|
```
|
|
@@ -182,7 +216,7 @@ const [form] = useForm()
|
|
|
182
216
|
footer={
|
|
183
217
|
<div className="flex justify-end gap-2">
|
|
184
218
|
<Button onClick={() => setOpen(false)}>Cancel</Button>
|
|
185
|
-
<
|
|
219
|
+
<AsynchronousButton onClick={() => form.submit()}>Save</AsynchronousButton>
|
|
186
220
|
</div>
|
|
187
221
|
}
|
|
188
222
|
>
|
|
@@ -203,10 +237,10 @@ const [form] = useForm()
|
|
|
203
237
|
/>
|
|
204
238
|
```
|
|
205
239
|
|
|
206
|
-
### Imperative Confirm (
|
|
240
|
+
### Imperative Confirm (modal)
|
|
207
241
|
|
|
208
242
|
```tsx
|
|
209
|
-
|
|
243
|
+
modal.confirm({
|
|
210
244
|
title: 'Delete',
|
|
211
245
|
content: 'Are you sure?',
|
|
212
246
|
onOk: async () => { await deleteUser(id); },
|
|
@@ -277,7 +311,7 @@ In `mode='top'`, SubMenus always use popup dropdowns.
|
|
|
277
311
|
### Toast Messages
|
|
278
312
|
|
|
279
313
|
```tsx
|
|
280
|
-
import { message } from '@tc/ui
|
|
314
|
+
import { message } from '@tc/ui-react'
|
|
281
315
|
|
|
282
316
|
message.success('User created successfully')
|
|
283
317
|
message.error('Failed to save')
|
|
@@ -288,7 +322,7 @@ message.info('New update available')
|
|
|
288
322
|
### Notifications (Corner)
|
|
289
323
|
|
|
290
324
|
```tsx
|
|
291
|
-
import { Notification } from '@tc/ui
|
|
325
|
+
import { Notification } from '@tc/ui-react'
|
|
292
326
|
|
|
293
327
|
// As component
|
|
294
328
|
<Notification type="error" title="Upload Failed" message="Network error. Retry?" />
|
|
@@ -351,6 +385,7 @@ This means both patterns work identically:
|
|
|
351
385
|
|
|
352
386
|
## CSS Conventions
|
|
353
387
|
|
|
388
|
+
- Import styles before using components: `@tc/ui-react/index.css` for raw UI usage, or `@_tc/template-core/fe/tailwind_ui.css` for published TemplateCore frontend entries.
|
|
354
389
|
- All CSS classes are prefixed `tc-ui-<component-name>`
|
|
355
390
|
- Tailwind CSS used for styling
|
|
356
391
|
- Theme via CSS custom properties: `hsl(var(--theme-primary))`, `hsl(var(--theme-background))`, etc.
|