@wzyjs/components 0.2.41 → 0.2.42
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/package.json +7 -3
- package/src/DragSort/index.tsx +1 -1
- package/src/GroupLayout/index.tsx +45 -0
- package/src/IframePro/index.tsx +1 -1
- package/src/Markdown/index.tsx +23 -0
- package/src/SectorButton/index.tsx +247 -0
- package/src/TextInput/index.tsx +61 -0
- package/src/index.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wzyjs/components",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.42",
|
|
4
4
|
"description": "description",
|
|
5
5
|
"author": "wzy",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"ali-oss": "^6.21.0",
|
|
12
12
|
"echarts": "^5.4.3",
|
|
13
13
|
"echarts-for-react": "^3.0.2",
|
|
14
|
+
"github-markdown-css": "^5.8.1",
|
|
14
15
|
"handlebars": "^4.7.8",
|
|
15
16
|
"html2canvas": "^1.4.1",
|
|
16
17
|
"js-beautify": "^1.14.7",
|
|
@@ -19,7 +20,10 @@
|
|
|
19
20
|
"react": "^19.0.0",
|
|
20
21
|
"react-beautiful-dnd": "^13.1.1",
|
|
21
22
|
"react-json-view": "^1.21.3",
|
|
22
|
-
"react-
|
|
23
|
+
"react-markdown": "^10.1.0",
|
|
24
|
+
"react-syntax-highlighter": "^15.5.0",
|
|
25
|
+
"rehype-starry-night": "^2.2.0",
|
|
26
|
+
"remark-gfm": "^4.0.1"
|
|
23
27
|
},
|
|
24
28
|
"peerDependencies": {
|
|
25
29
|
"@wzyjs/antd": "^0.2.37",
|
|
@@ -35,7 +39,7 @@
|
|
|
35
39
|
"@types/react-beautiful-dnd": "^13.1.8",
|
|
36
40
|
"@types/react-syntax-highlighter": "^15.5.5"
|
|
37
41
|
},
|
|
38
|
-
"gitHead": "
|
|
42
|
+
"gitHead": "3f1b77e82ed5c52cb04b6a0c7f816c96095c969f",
|
|
39
43
|
"publishConfig": {
|
|
40
44
|
"access": "public"
|
|
41
45
|
}
|
package/src/DragSort/index.tsx
CHANGED
|
@@ -49,7 +49,7 @@ export const DragSort = <T extends { id: string }>(props: DragSortProps<T>) => {
|
|
|
49
49
|
<div ref={provided.innerRef} {...provided.droppableProps}>
|
|
50
50
|
<Space direction={direction} wrap style={{ width: '100%' }}>
|
|
51
51
|
{list?.map((item, index) => (
|
|
52
|
-
<Draggable key={item.id} draggableId={item.id.toString()} index={index}>
|
|
52
|
+
<Draggable key={item.id} disableInteractiveElementBlocking draggableId={item.id.toString()} index={index}>
|
|
53
53
|
{provided => (
|
|
54
54
|
<div ref={provided.innerRef} {...provided.draggableProps} >
|
|
55
55
|
{children(item, provided)}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
import GridLayout, { type Layout } from 'react-grid-layout'
|
|
4
|
+
import 'react-grid-layout/css/styles.css'
|
|
5
|
+
|
|
6
|
+
export { type Layout as GridLayoutItem } from 'react-grid-layout'
|
|
7
|
+
|
|
8
|
+
interface GridLayoutProps<I> {
|
|
9
|
+
layout: I[]
|
|
10
|
+
style?: CSSProperties
|
|
11
|
+
onChange?: (layout: I[]) => void
|
|
12
|
+
renderItem?: (item: I) => ReactNode
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const GroupLayout = <I extends Layout>(props: GridLayoutProps<I>) => {
|
|
16
|
+
const { style, layout, onChange, renderItem = item => item.i } = props
|
|
17
|
+
|
|
18
|
+
const onLayoutChange = (items: Layout[]) => {
|
|
19
|
+
onChange?.(items.map(item => ({
|
|
20
|
+
...layout.find(i => i.i === item.i),
|
|
21
|
+
...item,
|
|
22
|
+
}) as I))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<GridLayout
|
|
27
|
+
layout={layout}
|
|
28
|
+
cols={24}
|
|
29
|
+
rowHeight={50}
|
|
30
|
+
width={1200}
|
|
31
|
+
useCSSTransforms={false}
|
|
32
|
+
containerPadding={[0, 0]}
|
|
33
|
+
style={{ width: '100%', height: '100%', userSelect: 'none', ...style }}
|
|
34
|
+
draggableHandle='.drag-handle'
|
|
35
|
+
resizeHandles={['se']}
|
|
36
|
+
onLayoutChange={onLayoutChange}
|
|
37
|
+
>
|
|
38
|
+
{layout.map(item => (
|
|
39
|
+
<div key={item.i}>
|
|
40
|
+
{renderItem(item)}
|
|
41
|
+
</div>
|
|
42
|
+
))}
|
|
43
|
+
</GridLayout>
|
|
44
|
+
)
|
|
45
|
+
}
|
package/src/IframePro/index.tsx
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { MarkdownHooks } from 'react-markdown'
|
|
4
|
+
|
|
5
|
+
import remarkGfm from 'remark-gfm'
|
|
6
|
+
import rehypeStarryNight from 'rehype-starry-night'
|
|
7
|
+
|
|
8
|
+
import 'github-markdown-css/github-markdown-light.css'
|
|
9
|
+
|
|
10
|
+
interface MarkdownProps {
|
|
11
|
+
content: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const Markdown = (props: MarkdownProps) => {
|
|
15
|
+
const { content } = props
|
|
16
|
+
return (
|
|
17
|
+
<div className='markdown-body' style={{ backgroundColor: 'transparent' }}>
|
|
18
|
+
<MarkdownHooks rehypePlugins={[rehypeStarryNight, remarkGfm]}>
|
|
19
|
+
{content}
|
|
20
|
+
</MarkdownHooks>
|
|
21
|
+
</div>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { useState } from 'react'
|
|
4
|
+
|
|
5
|
+
export interface SectorButtonSector<T> {
|
|
6
|
+
id: T;
|
|
7
|
+
label?: string;
|
|
8
|
+
color?: string;
|
|
9
|
+
hoverColor?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SectorButtonProps<T> {
|
|
13
|
+
sectors: SectorButtonSector<T>[];
|
|
14
|
+
size?: number;
|
|
15
|
+
className?: string;
|
|
16
|
+
onClick?: (id: T) => void;
|
|
17
|
+
borderRadius?: number;
|
|
18
|
+
showPlusSign?: boolean;
|
|
19
|
+
plusSignColor?: string;
|
|
20
|
+
plusSignSize?: number;
|
|
21
|
+
shape?: 'circle' | 'rounded-rect';
|
|
22
|
+
rectWidth?: number;
|
|
23
|
+
rectHeight?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const SectorButton = <T = string>(props: SectorButtonProps<T>) => {
|
|
27
|
+
const {
|
|
28
|
+
shape = 'rounded-rect',
|
|
29
|
+
size = 40,
|
|
30
|
+
rectWidth = size,
|
|
31
|
+
rectHeight = size,
|
|
32
|
+
sectors,
|
|
33
|
+
className,
|
|
34
|
+
borderRadius = 12,
|
|
35
|
+
showPlusSign = true,
|
|
36
|
+
plusSignColor = '#bbb',
|
|
37
|
+
plusSignSize = 0.3,
|
|
38
|
+
onClick,
|
|
39
|
+
} = props
|
|
40
|
+
|
|
41
|
+
const [hoveredSector, setHoveredSector] = useState<T | null>(null)
|
|
42
|
+
|
|
43
|
+
const anglePerSector = 360 / sectors.length
|
|
44
|
+
const radius = size / 2
|
|
45
|
+
|
|
46
|
+
// 添加额外边距确保虚线显示完整
|
|
47
|
+
const padding = 2
|
|
48
|
+
const svgWidth = shape === 'circle' ? size + padding * 2 : rectWidth + padding * 2
|
|
49
|
+
const svgHeight = shape === 'circle' ? size + padding * 2 : rectHeight + padding * 2
|
|
50
|
+
|
|
51
|
+
const handleSectorClick = (id: T) => {
|
|
52
|
+
onClick?.(id)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 先渲染非悬停的扇形,然后渲染悬停的扇形,确保悬停的扇形在最顶层
|
|
56
|
+
const nonHoveredSectors = sectors.filter(sector => sector.id !== hoveredSector)
|
|
57
|
+
const hoveredSector1 = sectors.find(sector => sector.id === hoveredSector)
|
|
58
|
+
const orderedSectors = [...nonHoveredSectors]
|
|
59
|
+
if (hoveredSector1) {
|
|
60
|
+
orderedSectors.push(hoveredSector1)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 生成圆角矩形或扇形的路径
|
|
64
|
+
const generatePath = (sector: SectorButtonSector<T>, originalIndex: number) => {
|
|
65
|
+
const centerX = svgWidth / 2
|
|
66
|
+
const centerY = svgHeight / 2
|
|
67
|
+
|
|
68
|
+
if (shape === 'circle') {
|
|
69
|
+
// 原来的扇形路径生成逻辑
|
|
70
|
+
const startAngle = originalIndex * anglePerSector
|
|
71
|
+
const endAngle = (originalIndex + 1) * anglePerSector
|
|
72
|
+
|
|
73
|
+
const startRad = (startAngle - 90) * Math.PI / 180
|
|
74
|
+
const endRad = (endAngle - 90) * Math.PI / 180
|
|
75
|
+
|
|
76
|
+
return [
|
|
77
|
+
`M ${centerX} ${centerY}`,
|
|
78
|
+
`L ${centerX + radius * Math.cos(startRad)} ${centerY + radius * Math.sin(startRad)}`,
|
|
79
|
+
`A ${radius} ${radius} 0 ${anglePerSector > 180 ? 1 : 0} 1 ${centerX + radius * Math.cos(endRad)} ${centerY + radius * Math.sin(endRad)}`,
|
|
80
|
+
`Z`,
|
|
81
|
+
].join(' ')
|
|
82
|
+
} else {
|
|
83
|
+
// 圆角矩形的分块逻辑
|
|
84
|
+
const rectX = padding
|
|
85
|
+
const rectY = padding
|
|
86
|
+
const rectW = rectWidth
|
|
87
|
+
const rectH = rectHeight
|
|
88
|
+
|
|
89
|
+
// 当只有2个扇形时,使用左右布局
|
|
90
|
+
if (sectors.length === 2) {
|
|
91
|
+
const secWidth = rectW / 2
|
|
92
|
+
const secHeight = rectH
|
|
93
|
+
const x = rectX + originalIndex * secWidth
|
|
94
|
+
const y = rectY
|
|
95
|
+
|
|
96
|
+
// 使用圆角矩形绘制单个部分
|
|
97
|
+
const br = borderRadius > 0 ? Math.min(borderRadius, secWidth / 4, secHeight / 4) : 0
|
|
98
|
+
|
|
99
|
+
// 确定是左侧还是右侧
|
|
100
|
+
const isLeft = originalIndex === 0
|
|
101
|
+
const isRight = originalIndex === 1
|
|
102
|
+
|
|
103
|
+
// 构建路径 - 根据位置决定哪些角有圆角
|
|
104
|
+
return [
|
|
105
|
+
`M ${x + (isLeft ? br : 0)} ${y}`,
|
|
106
|
+
|
|
107
|
+
// 上边
|
|
108
|
+
`H ${x + secWidth - (isRight ? br : 0)}`,
|
|
109
|
+
isRight ? `A ${br} ${br} 0 0 1 ${x + secWidth} ${y + br}` : '',
|
|
110
|
+
|
|
111
|
+
// 右边
|
|
112
|
+
`V ${y + secHeight - (isRight ? br : 0)}`,
|
|
113
|
+
isRight ? `A ${br} ${br} 0 0 1 ${x + secWidth - br} ${y + secHeight}` : '',
|
|
114
|
+
|
|
115
|
+
// 下边
|
|
116
|
+
`H ${x + (isLeft ? br : 0)}`,
|
|
117
|
+
isLeft ? `A ${br} ${br} 0 0 1 ${x} ${y + secHeight - br}` : '',
|
|
118
|
+
|
|
119
|
+
// 左边
|
|
120
|
+
`V ${y + (isLeft ? br : 0)}`,
|
|
121
|
+
isLeft ? `A ${br} ${br} 0 0 1 ${x + br} ${y}` : '',
|
|
122
|
+
|
|
123
|
+
'Z',
|
|
124
|
+
].filter(Boolean).join(' ')
|
|
125
|
+
} else {
|
|
126
|
+
// 原有的上下布局逻辑
|
|
127
|
+
const secWidth = rectW / (sectors.length % 2 === 0 ? sectors.length / 2 : Math.ceil(sectors.length / 2))
|
|
128
|
+
const secHeight = rectH / 2
|
|
129
|
+
|
|
130
|
+
// 确定当前扇区在矩形中的位置
|
|
131
|
+
const isTopRow = originalIndex < Math.ceil(sectors.length / 2)
|
|
132
|
+
const rowIndex = isTopRow ? originalIndex : originalIndex - Math.ceil(sectors.length / 2)
|
|
133
|
+
|
|
134
|
+
const x = rectX + rowIndex * secWidth
|
|
135
|
+
const y = isTopRow ? rectY : rectY + secHeight
|
|
136
|
+
|
|
137
|
+
// 使用圆角矩形绘制单个部分
|
|
138
|
+
const br = borderRadius > 0 ? Math.min(borderRadius, secWidth / 4, secHeight / 4) : 0
|
|
139
|
+
|
|
140
|
+
// 确定哪些边需要圆角
|
|
141
|
+
const isLeftEdge = (isTopRow && rowIndex === 0) || (!isTopRow && rowIndex === 0)
|
|
142
|
+
const isRightEdge = (isTopRow && rowIndex === Math.ceil(sectors.length / 2) - 1) ||
|
|
143
|
+
(!isTopRow && rowIndex === (sectors.length - Math.ceil(sectors.length / 2)) - 1)
|
|
144
|
+
const isTopEdge = isTopRow
|
|
145
|
+
const isBottomEdge = !isTopRow
|
|
146
|
+
|
|
147
|
+
// 构建路径 - 根据位置决定哪些角有圆角
|
|
148
|
+
return [
|
|
149
|
+
`M ${x + (isLeftEdge && isTopEdge ? br : 0)} ${y}`,
|
|
150
|
+
|
|
151
|
+
// 上边
|
|
152
|
+
`H ${x + secWidth - (isRightEdge && isTopEdge ? br : 0)}`,
|
|
153
|
+
isRightEdge && isTopEdge ? `A ${br} ${br} 0 0 1 ${x + secWidth} ${y + br}` : '',
|
|
154
|
+
|
|
155
|
+
// 右边
|
|
156
|
+
`V ${y + secHeight - (isRightEdge && isBottomEdge ? br : 0)}`,
|
|
157
|
+
isRightEdge && isBottomEdge ? `A ${br} ${br} 0 0 1 ${x + secWidth - br} ${y + secHeight}` : '',
|
|
158
|
+
|
|
159
|
+
// 下边
|
|
160
|
+
`H ${x + (isLeftEdge && isBottomEdge ? br : 0)}`,
|
|
161
|
+
isLeftEdge && isBottomEdge ? `A ${br} ${br} 0 0 1 ${x} ${y + secHeight - br}` : '',
|
|
162
|
+
|
|
163
|
+
// 左边
|
|
164
|
+
`V ${y + (isLeftEdge && isTopEdge ? br : 0)}`,
|
|
165
|
+
isLeftEdge && isTopEdge ? `A ${br} ${br} 0 0 1 ${x + br} ${y}` : '',
|
|
166
|
+
|
|
167
|
+
'Z',
|
|
168
|
+
].filter(Boolean).join(' ')
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<div
|
|
175
|
+
className={`relative ${className}`}
|
|
176
|
+
style={{
|
|
177
|
+
width: shape === 'circle' ? size : rectWidth,
|
|
178
|
+
height: shape === 'circle' ? size : rectHeight,
|
|
179
|
+
borderRadius,
|
|
180
|
+
backgroundColor: 'transparent',
|
|
181
|
+
}}
|
|
182
|
+
>
|
|
183
|
+
<svg
|
|
184
|
+
width={svgWidth}
|
|
185
|
+
height={svgHeight}
|
|
186
|
+
viewBox={`0 0 ${svgWidth} ${svgHeight}`}
|
|
187
|
+
style={{
|
|
188
|
+
position: 'absolute',
|
|
189
|
+
top: -padding,
|
|
190
|
+
left: -padding,
|
|
191
|
+
}}
|
|
192
|
+
>
|
|
193
|
+
<g>
|
|
194
|
+
{orderedSectors.map(sector => {
|
|
195
|
+
const originalIndex = sectors.findIndex(s => s.id === sector.id)
|
|
196
|
+
const pathData = generatePath(sector, originalIndex)
|
|
197
|
+
|
|
198
|
+
const isHovered = hoveredSector === sector.id
|
|
199
|
+
const fillColor = (isHovered ? sector.hoverColor : sector.color) || 'white'
|
|
200
|
+
const strokeColor = isHovered ? '#90cdf4' : '#e2e8f0'
|
|
201
|
+
const strokeWidth = isHovered ? 1.5 : 1
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<g key={sector.id as string}>
|
|
205
|
+
<path
|
|
206
|
+
d={pathData}
|
|
207
|
+
fill={fillColor}
|
|
208
|
+
stroke={strokeColor}
|
|
209
|
+
strokeWidth={strokeWidth}
|
|
210
|
+
strokeDasharray='4,1'
|
|
211
|
+
className='transition-colors duration-200 cursor-pointer'
|
|
212
|
+
onMouseEnter={() => setHoveredSector(sector.id)}
|
|
213
|
+
onMouseLeave={() => setHoveredSector(null)}
|
|
214
|
+
onClick={() => handleSectorClick(sector.id)}
|
|
215
|
+
/>
|
|
216
|
+
</g>
|
|
217
|
+
)
|
|
218
|
+
})}
|
|
219
|
+
|
|
220
|
+
{/* 在中间添加加号 */}
|
|
221
|
+
{showPlusSign && (
|
|
222
|
+
<g>
|
|
223
|
+
<line
|
|
224
|
+
x1={svgWidth / 2 - (plusSignSize * (shape === 'circle' ? size : Math.min(rectWidth, rectHeight)) / 2)}
|
|
225
|
+
y1={svgHeight / 2}
|
|
226
|
+
x2={svgWidth / 2 + (plusSignSize * (shape === 'circle' ? size : Math.min(rectWidth, rectHeight)) / 2)}
|
|
227
|
+
y2={svgHeight / 2}
|
|
228
|
+
stroke={plusSignColor}
|
|
229
|
+
strokeWidth={2}
|
|
230
|
+
strokeLinecap='round'
|
|
231
|
+
/>
|
|
232
|
+
<line
|
|
233
|
+
x1={svgWidth / 2}
|
|
234
|
+
y1={svgHeight / 2 - (plusSignSize * (shape === 'circle' ? size : Math.min(rectWidth, rectHeight)) / 2)}
|
|
235
|
+
x2={svgWidth / 2}
|
|
236
|
+
y2={svgHeight / 2 + (plusSignSize * (shape === 'circle' ? size : Math.min(rectWidth, rectHeight)) / 2)}
|
|
237
|
+
stroke={plusSignColor}
|
|
238
|
+
strokeWidth={2}
|
|
239
|
+
strokeLinecap='round'
|
|
240
|
+
/>
|
|
241
|
+
</g>
|
|
242
|
+
)}
|
|
243
|
+
</g>
|
|
244
|
+
</svg>
|
|
245
|
+
</div>
|
|
246
|
+
)
|
|
247
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { useState } from 'react'
|
|
4
|
+
|
|
5
|
+
import { Input } from 'app/src/components'
|
|
6
|
+
import { useBoolean } from 'app/src/hooks'
|
|
7
|
+
|
|
8
|
+
interface TextInputProps {
|
|
9
|
+
value: string
|
|
10
|
+
onChange?: (value: string) => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const TextInput = (props: TextInputProps) => {
|
|
14
|
+
const { value, onChange } = props
|
|
15
|
+
|
|
16
|
+
const [text, setText] = useState(value)
|
|
17
|
+
|
|
18
|
+
const [isEditing, { setTrue, setFalse }] = useBoolean(false)
|
|
19
|
+
|
|
20
|
+
const onSubmit = () => {
|
|
21
|
+
if (!text.trim()) {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (text === value) {
|
|
26
|
+
setFalse()
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
onChange?.(text.trim())
|
|
31
|
+
setFalse()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const onBlur = () => {
|
|
35
|
+
onSubmit()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const onKeyDown = (e: React.KeyboardEvent) => {
|
|
39
|
+
if (e.key === 'Enter') {
|
|
40
|
+
onSubmit()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div onDoubleClick={setTrue}>
|
|
46
|
+
{!isEditing ? (
|
|
47
|
+
<span>{text}</span>
|
|
48
|
+
) : (
|
|
49
|
+
<Input
|
|
50
|
+
value={text}
|
|
51
|
+
onChange={ev => setText(ev.target.value)}
|
|
52
|
+
onBlur={onBlur}
|
|
53
|
+
onKeyDown={onKeyDown}
|
|
54
|
+
autoFocus
|
|
55
|
+
size='small'
|
|
56
|
+
style={{ width: 200 }}
|
|
57
|
+
/>
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
60
|
+
)
|
|
61
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,10 @@ export * from './DownloadLink'
|
|
|
10
10
|
export * from './Fold'
|
|
11
11
|
export * from './DragSort'
|
|
12
12
|
export * from './DateSwitcher'
|
|
13
|
+
export * from './GroupLayout'
|
|
14
|
+
export * from './TextInput'
|
|
15
|
+
export * from './SectorButton'
|
|
16
|
+
export * from './Markdown'
|
|
13
17
|
|
|
14
18
|
// export * from './JsonView'
|
|
15
19
|
// export * from './Crud'
|