react-public-components 1.1.15 → 1.1.17
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 +66 -4
- package/dist/index.cjs +245 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +15 -0
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +57 -2
- package/dist/index.d.ts +57 -2
- package/dist/index.js +244 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
封装 react 常用的组件,欢迎更多同学提出大众化普遍存在的业务场景。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
本仓库包含四个独立的 React 组件:
|
|
6
6
|
|
|
7
7
|
1. **CollapseBox** — 可折叠的内容容器组件
|
|
8
8
|
2. **Splitter** — 可拖拽调整、可折叠的分屏面板组件
|
|
9
9
|
3. **DisabledBox** — 禁用态包装容器组件(带锁图标及拦截事件)
|
|
10
|
+
4. **Masonry** — 瀑布流布局组件(一比一复刻 Ant Design V6 规范)
|
|
10
11
|
|
|
11
12
|
---
|
|
12
13
|
|
|
@@ -198,7 +199,65 @@ export default function Demo() {
|
|
|
198
199
|
|
|
199
200
|
---
|
|
200
201
|
|
|
201
|
-
##
|
|
202
|
+
## 四、Masonry 瀑布流组件
|
|
203
|
+
|
|
204
|
+
### 简介
|
|
205
|
+
|
|
206
|
+
`Masonry` 是一个符合 Ant Design V6 规范的瀑布流布局组件。支持固定或响应式列数配置(`columns`)、水平垂直间距配置(`gutter`)、持续监听子项尺寸变化(`fresh`)、自定义语义化结构样式/类名(`styles`/`classNames`)以及列排序回调(`onLayoutChange`)。
|
|
207
|
+
|
|
208
|
+
### 引入方式
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
import { Masonry } from 'react-public-components';
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Masonry Props
|
|
215
|
+
|
|
216
|
+
| 参数 | 说明 | 类型 | 默认值 |
|
|
217
|
+
| ---------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------- |
|
|
218
|
+
| `classNames` | 自定义内部语义化结构 class,支持对象或函数 | `Record<'root' \| 'item', string> \| ((info: { props }) => Record<'root' \| 'item', string>)` | `-` |
|
|
219
|
+
| `columns` | 列数,固定数字或响应式配置 | `number \| { xs?: number; sm?: number; md?: number; lg?: number; xl?: number; xxl?: number }` | `3` |
|
|
220
|
+
| `fresh` | 是否持续监听子项尺寸变化 | `boolean` | `false` |
|
|
221
|
+
| `gutter` | 间距,固定数字、响应式配置或水平垂直间距配置 | `Gap \| [Gap, Gap]` | `0` |
|
|
222
|
+
| `items` | 瀑布流数据项 | `MasonryItem[]` | `-` |
|
|
223
|
+
| `itemRender` | 自定义数据项渲染函数 | `(item: MasonryItem) => ReactNode` | `-` |
|
|
224
|
+
| `styles` | 语义化结构 style,支持对象和函数形式 | `Record<'root' \| 'item', CSSProperties> \| ((info: { props }) => Record<'root' \| 'item', CSSProperties>)` | `-` |
|
|
225
|
+
| `onLayoutChange` | 列排序计算完成回调 | `(layout: { key: React.Key; column: number }[]) => void` | `-` |
|
|
226
|
+
|
|
227
|
+
### MasonryItem 属性
|
|
228
|
+
|
|
229
|
+
| 参数 | 说明 | 类型 | 默认值 |
|
|
230
|
+
| ---------- | ------------------------------------------------ | ------------------ | ------ |
|
|
231
|
+
| `children` | 自定义展示内容,相对 `itemRender` 具有更高优先级 | `ReactNode` | `-` |
|
|
232
|
+
| `column` | 自定义指定放置列(0-indexed) | `number` | `-` |
|
|
233
|
+
| `data` | 自定义存储数据 | `T` | `-` |
|
|
234
|
+
| `height` | 高度(手指定时优先使用) | `number` | `-` |
|
|
235
|
+
| `key` | 唯一标识 | `string \| number` | `-` |
|
|
236
|
+
|
|
237
|
+
### 基础用法
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
import { Masonry } from 'react-public-components';
|
|
241
|
+
|
|
242
|
+
export default function Demo() {
|
|
243
|
+
return (
|
|
244
|
+
<Masonry
|
|
245
|
+
columns={{ xs: 1, sm: 2, md: 3, lg: 4 }}
|
|
246
|
+
gutter={[16, 16]}
|
|
247
|
+
fresh={true}
|
|
248
|
+
items={[
|
|
249
|
+
{ key: '1', height: 120, children: <div>卡片 1</div> },
|
|
250
|
+
{ key: '2', height: 180, children: <div>卡片 2</div> },
|
|
251
|
+
{ key: '3', height: 150, children: <div>卡片 3</div> },
|
|
252
|
+
]}
|
|
253
|
+
/>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## 五、目录结构
|
|
202
261
|
|
|
203
262
|
```
|
|
204
263
|
.
|
|
@@ -207,14 +266,17 @@ export default function Demo() {
|
|
|
207
266
|
│ └── index.less
|
|
208
267
|
├── Splitter/
|
|
209
268
|
│ └── index.tsx
|
|
210
|
-
|
|
269
|
+
├── DisabledBox/
|
|
270
|
+
│ ├── index.tsx
|
|
271
|
+
│ └── index.less
|
|
272
|
+
└── Masonry/
|
|
211
273
|
├── index.tsx
|
|
212
274
|
└── index.less
|
|
213
275
|
```
|
|
214
276
|
|
|
215
277
|
---
|
|
216
278
|
|
|
217
|
-
##
|
|
279
|
+
## 六、发布到 npm
|
|
218
280
|
|
|
219
281
|
### 1. 前置准备
|
|
220
282
|
|
package/dist/index.cjs
CHANGED
|
@@ -32,6 +32,7 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
CollapseBox: () => CollapseBox_default,
|
|
34
34
|
DisabledBox: () => DisabledBox_default,
|
|
35
|
+
Masonry: () => Masonry_default,
|
|
35
36
|
Splitter: () => Splitter_default
|
|
36
37
|
});
|
|
37
38
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -630,7 +631,8 @@ var CustomSplitter = (props) => {
|
|
|
630
631
|
boxSizing: "border-box",
|
|
631
632
|
display: isHidden ? "none" : "block",
|
|
632
633
|
height: "100%",
|
|
633
|
-
|
|
634
|
+
overflowX: "hidden",
|
|
635
|
+
overflowY: "auto",
|
|
634
636
|
padding: ((_e = sizes[index]) != null ? _e : 0) < 32 ? 0 : 16,
|
|
635
637
|
width: "100%"
|
|
636
638
|
},
|
|
@@ -864,10 +866,252 @@ var DisabledBox = (props) => {
|
|
|
864
866
|
);
|
|
865
867
|
};
|
|
866
868
|
var DisabledBox_default = DisabledBox;
|
|
869
|
+
|
|
870
|
+
// Masonry/index.tsx
|
|
871
|
+
var import_react3 = __toESM(require("react"), 1);
|
|
872
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
873
|
+
var BREAKPOINT_ORDER = ["xs", "sm", "md", "lg", "xl", "xxl"];
|
|
874
|
+
function getBreakpoint(width) {
|
|
875
|
+
if (width >= 1600) return "xxl";
|
|
876
|
+
if (width >= 1200) return "xl";
|
|
877
|
+
if (width >= 992) return "lg";
|
|
878
|
+
if (width >= 768) return "md";
|
|
879
|
+
if (width >= 576) return "sm";
|
|
880
|
+
return "xs";
|
|
881
|
+
}
|
|
882
|
+
function resolveResponsiveValue(value, currentBp, defaultValue) {
|
|
883
|
+
if (value === void 0 || value === null) return defaultValue;
|
|
884
|
+
if (typeof value === "object") {
|
|
885
|
+
const obj = value;
|
|
886
|
+
if (obj[currentBp] !== void 0) return obj[currentBp];
|
|
887
|
+
const currentIdx = BREAKPOINT_ORDER.indexOf(currentBp);
|
|
888
|
+
for (let i = currentIdx - 1; i >= 0; i--) {
|
|
889
|
+
const bp = BREAKPOINT_ORDER[i];
|
|
890
|
+
if (obj[bp] !== void 0) return obj[bp];
|
|
891
|
+
}
|
|
892
|
+
for (let i = currentIdx + 1; i < BREAKPOINT_ORDER.length; i++) {
|
|
893
|
+
const bp = BREAKPOINT_ORDER[i];
|
|
894
|
+
if (obj[bp] !== void 0) return obj[bp];
|
|
895
|
+
}
|
|
896
|
+
return defaultValue;
|
|
897
|
+
}
|
|
898
|
+
return value;
|
|
899
|
+
}
|
|
900
|
+
function resolveGutter(gutter, currentBp) {
|
|
901
|
+
if (!gutter) return [0, 0];
|
|
902
|
+
if (Array.isArray(gutter)) {
|
|
903
|
+
const h = resolveResponsiveValue(gutter[0], currentBp, 0);
|
|
904
|
+
const v = resolveResponsiveValue(gutter[1], currentBp, 0);
|
|
905
|
+
return [h, v];
|
|
906
|
+
}
|
|
907
|
+
const g = resolveResponsiveValue(gutter, currentBp, 0);
|
|
908
|
+
return [g, g];
|
|
909
|
+
}
|
|
910
|
+
var Masonry = (props) => {
|
|
911
|
+
const {
|
|
912
|
+
classNames,
|
|
913
|
+
columns = 3,
|
|
914
|
+
fresh = false,
|
|
915
|
+
gutter = 0,
|
|
916
|
+
items,
|
|
917
|
+
itemRender,
|
|
918
|
+
styles,
|
|
919
|
+
onLayoutChange,
|
|
920
|
+
className,
|
|
921
|
+
style,
|
|
922
|
+
children
|
|
923
|
+
} = props;
|
|
924
|
+
const rootRef = (0, import_react3.useRef)(null);
|
|
925
|
+
const [containerWidth, setContainerWidth] = (0, import_react3.useState)(0);
|
|
926
|
+
const [measuredHeights, setMeasuredHeights] = (0, import_react3.useState)({});
|
|
927
|
+
const itemRefs = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
|
|
928
|
+
const normalizedItems = (0, import_react3.useMemo)(() => {
|
|
929
|
+
if (items) return items;
|
|
930
|
+
if (!children) return [];
|
|
931
|
+
return import_react3.default.Children.toArray(children).map((child, index) => {
|
|
932
|
+
var _a;
|
|
933
|
+
if (import_react3.default.isValidElement(child)) {
|
|
934
|
+
const key = (_a = child.key) != null ? _a : `masonry-item-${index}`;
|
|
935
|
+
return {
|
|
936
|
+
key,
|
|
937
|
+
children: child
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
return {
|
|
941
|
+
key: `masonry-item-${index}`,
|
|
942
|
+
children: child
|
|
943
|
+
};
|
|
944
|
+
});
|
|
945
|
+
}, [items, children]);
|
|
946
|
+
const currentBreakpoint = (0, import_react3.useMemo)(() => getBreakpoint(containerWidth), [containerWidth]);
|
|
947
|
+
const resolvedColumns = (0, import_react3.useMemo)(() => {
|
|
948
|
+
const count = resolveResponsiveValue(columns, currentBreakpoint, 3);
|
|
949
|
+
return Math.max(1, Math.floor(count));
|
|
950
|
+
}, [columns, currentBreakpoint]);
|
|
951
|
+
const [horizontalGap, verticalGap] = (0, import_react3.useMemo)(
|
|
952
|
+
() => resolveGutter(gutter, currentBreakpoint),
|
|
953
|
+
[gutter, currentBreakpoint]
|
|
954
|
+
);
|
|
955
|
+
(0, import_react3.useEffect)(() => {
|
|
956
|
+
if (!rootRef.current) return;
|
|
957
|
+
const observer = new ResizeObserver((entries) => {
|
|
958
|
+
for (const entry of entries) {
|
|
959
|
+
const width = entry.contentRect.width;
|
|
960
|
+
if (width > 0) {
|
|
961
|
+
setContainerWidth(width);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
observer.observe(rootRef.current);
|
|
966
|
+
return () => observer.disconnect();
|
|
967
|
+
}, []);
|
|
968
|
+
const measureItems = (0, import_react3.useCallback)(() => {
|
|
969
|
+
let hasChange = false;
|
|
970
|
+
const newHeights = {};
|
|
971
|
+
itemRefs.current.forEach((el, key) => {
|
|
972
|
+
if (el) {
|
|
973
|
+
const strKey = String(key);
|
|
974
|
+
const h = el.offsetHeight;
|
|
975
|
+
if (h > 0) {
|
|
976
|
+
newHeights[strKey] = h;
|
|
977
|
+
if (measuredHeights[strKey] !== h) {
|
|
978
|
+
hasChange = true;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
});
|
|
983
|
+
if (hasChange) {
|
|
984
|
+
setMeasuredHeights((prev) => ({ ...prev, ...newHeights }));
|
|
985
|
+
}
|
|
986
|
+
}, [measuredHeights]);
|
|
987
|
+
(0, import_react3.useLayoutEffect)(() => {
|
|
988
|
+
const handle = requestAnimationFrame(() => {
|
|
989
|
+
measureItems();
|
|
990
|
+
});
|
|
991
|
+
return () => cancelAnimationFrame(handle);
|
|
992
|
+
}, [measureItems]);
|
|
993
|
+
(0, import_react3.useEffect)(() => {
|
|
994
|
+
if (!fresh) return;
|
|
995
|
+
const observer = new ResizeObserver(() => {
|
|
996
|
+
measureItems();
|
|
997
|
+
});
|
|
998
|
+
itemRefs.current.forEach((el) => {
|
|
999
|
+
if (el) observer.observe(el);
|
|
1000
|
+
});
|
|
1001
|
+
return () => observer.disconnect();
|
|
1002
|
+
}, [fresh, measureItems, normalizedItems]);
|
|
1003
|
+
const columnWidth = (0, import_react3.useMemo)(() => {
|
|
1004
|
+
if (containerWidth <= 0) return 0;
|
|
1005
|
+
const totalGap = (resolvedColumns - 1) * horizontalGap;
|
|
1006
|
+
return Math.max(0, (containerWidth - totalGap) / resolvedColumns);
|
|
1007
|
+
}, [containerWidth, resolvedColumns, horizontalGap]);
|
|
1008
|
+
const { layoutItems, containerHeight, layoutChangeData } = (0, import_react3.useMemo)(() => {
|
|
1009
|
+
const colHeights = new Array(resolvedColumns).fill(0);
|
|
1010
|
+
const changeData = [];
|
|
1011
|
+
const computedList = normalizedItems.map((item) => {
|
|
1012
|
+
var _a, _b;
|
|
1013
|
+
const h = (_b = (_a = item.height) != null ? _a : measuredHeights[String(item.key)]) != null ? _b : 0;
|
|
1014
|
+
let targetCol = 0;
|
|
1015
|
+
if (typeof item.column === "number" && item.column >= 0 && item.column < resolvedColumns) {
|
|
1016
|
+
targetCol = Math.floor(item.column);
|
|
1017
|
+
} else {
|
|
1018
|
+
let minH = colHeights[0];
|
|
1019
|
+
for (let i = 1; i < resolvedColumns; i++) {
|
|
1020
|
+
if (colHeights[i] < minH) {
|
|
1021
|
+
minH = colHeights[i];
|
|
1022
|
+
targetCol = i;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
const left = targetCol * (columnWidth + horizontalGap);
|
|
1027
|
+
const top = colHeights[targetCol];
|
|
1028
|
+
colHeights[targetCol] += h + (h > 0 ? verticalGap : 0);
|
|
1029
|
+
changeData.push({ key: item.key, column: targetCol });
|
|
1030
|
+
return {
|
|
1031
|
+
item,
|
|
1032
|
+
left,
|
|
1033
|
+
top,
|
|
1034
|
+
width: columnWidth,
|
|
1035
|
+
height: h,
|
|
1036
|
+
column: targetCol
|
|
1037
|
+
};
|
|
1038
|
+
});
|
|
1039
|
+
const totalH = Math.max(0, ...colHeights);
|
|
1040
|
+
return {
|
|
1041
|
+
layoutItems: computedList,
|
|
1042
|
+
containerHeight: totalH,
|
|
1043
|
+
layoutChangeData: changeData
|
|
1044
|
+
};
|
|
1045
|
+
}, [normalizedItems, resolvedColumns, horizontalGap, verticalGap, columnWidth, measuredHeights]);
|
|
1046
|
+
const prevLayoutRef = (0, import_react3.useRef)("");
|
|
1047
|
+
(0, import_react3.useEffect)(() => {
|
|
1048
|
+
const layoutKey = JSON.stringify(layoutChangeData);
|
|
1049
|
+
if (layoutKey !== prevLayoutRef.current) {
|
|
1050
|
+
prevLayoutRef.current = layoutKey;
|
|
1051
|
+
onLayoutChange == null ? void 0 : onLayoutChange(layoutChangeData);
|
|
1052
|
+
}
|
|
1053
|
+
}, [layoutChangeData, onLayoutChange]);
|
|
1054
|
+
const semanticClassNames = (0, import_react3.useMemo)(() => {
|
|
1055
|
+
var _a;
|
|
1056
|
+
if (typeof classNames === "function") {
|
|
1057
|
+
return (_a = classNames({ props })) != null ? _a : {};
|
|
1058
|
+
}
|
|
1059
|
+
return classNames != null ? classNames : {};
|
|
1060
|
+
}, [classNames, props]);
|
|
1061
|
+
const semanticStyles = (0, import_react3.useMemo)(() => {
|
|
1062
|
+
var _a;
|
|
1063
|
+
if (typeof styles === "function") {
|
|
1064
|
+
return (_a = styles({ props })) != null ? _a : {};
|
|
1065
|
+
}
|
|
1066
|
+
return styles != null ? styles : {};
|
|
1067
|
+
}, [styles, props]);
|
|
1068
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1069
|
+
"div",
|
|
1070
|
+
{
|
|
1071
|
+
ref: rootRef,
|
|
1072
|
+
className: ["masonry_root", semanticClassNames.root, className].filter(Boolean).join(" "),
|
|
1073
|
+
style: {
|
|
1074
|
+
minHeight: containerHeight > 0 ? containerHeight : void 0,
|
|
1075
|
+
height: containerHeight > 0 ? containerHeight : void 0,
|
|
1076
|
+
...semanticStyles.root,
|
|
1077
|
+
...style
|
|
1078
|
+
},
|
|
1079
|
+
children: layoutItems.map(({ item, left, top, width, height }) => {
|
|
1080
|
+
var _a;
|
|
1081
|
+
const content = (_a = item.children) != null ? _a : itemRender == null ? void 0 : itemRender(item);
|
|
1082
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1083
|
+
"div",
|
|
1084
|
+
{
|
|
1085
|
+
ref: (node) => {
|
|
1086
|
+
if (node) {
|
|
1087
|
+
itemRefs.current.set(item.key, node);
|
|
1088
|
+
} else {
|
|
1089
|
+
itemRefs.current.delete(item.key);
|
|
1090
|
+
}
|
|
1091
|
+
},
|
|
1092
|
+
className: ["masonry_item", semanticClassNames.item].filter(Boolean).join(" "),
|
|
1093
|
+
style: {
|
|
1094
|
+
width: containerWidth > 0 ? width : "100%",
|
|
1095
|
+
height: height > 0 ? height : void 0,
|
|
1096
|
+
transform: containerWidth > 0 ? `translate3d(${left}px, ${top}px, 0)` : void 0,
|
|
1097
|
+
opacity: containerWidth > 0 ? 1 : 0,
|
|
1098
|
+
...semanticStyles.item
|
|
1099
|
+
},
|
|
1100
|
+
onLoad: measureItems,
|
|
1101
|
+
children: content
|
|
1102
|
+
},
|
|
1103
|
+
item.key
|
|
1104
|
+
);
|
|
1105
|
+
})
|
|
1106
|
+
}
|
|
1107
|
+
);
|
|
1108
|
+
};
|
|
1109
|
+
var Masonry_default = Masonry;
|
|
867
1110
|
// Annotate the CommonJS export names for ESM import in node:
|
|
868
1111
|
0 && (module.exports = {
|
|
869
1112
|
CollapseBox,
|
|
870
1113
|
DisabledBox,
|
|
1114
|
+
Masonry,
|
|
871
1115
|
Splitter
|
|
872
1116
|
});
|
|
873
1117
|
//# sourceMappingURL=index.cjs.map
|