@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wzyjs/components",
3
- "version": "0.2.41",
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-syntax-highlighter": "^15.5.0"
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": "22795bdb9c670d3e37e9a6e83b544f916bab3c16",
42
+ "gitHead": "3f1b77e82ed5c52cb04b6a0c7f816c96095c969f",
39
43
  "publishConfig": {
40
44
  "access": "public"
41
45
  }
@@ -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
+ }
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React, { useEffect, CSSProperties } from 'react'
4
4
  import { Spin } from 'antd'
5
- import { useBoolean } from '@wzyjs/hooks'
5
+ import { useBoolean } from 'ahooks'
6
6
 
7
7
  export interface IframeProProps {
8
8
  url: string;
@@ -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'