react-public-components 1.1.16 → 1.1.18

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/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import React__default, { ReactNode, CSSProperties } from 'react';
2
+ import React__default, { ReactNode, CSSProperties, Key, HTMLAttributes } from 'react';
3
3
 
4
4
  type CollapseBoxDirection = 'horizontal' | 'vertical';
5
5
  type CollapseBoxButtonPosition = 'left' | 'right' | 'top' | 'bottom';
@@ -109,4 +109,81 @@ interface DisabledBoxProps {
109
109
  }
110
110
  declare const DisabledBox: (props: DisabledBoxProps) => React.JSX.Element;
111
111
 
112
- export { CollapsibleBox as CollapseBox, type CollapseBoxButtonPosition, type CollapseBoxDirection, type CollapseBoxProps, DisabledBox, type DisabledBoxProps, CustomSplitter as Splitter, type SplitterCollapsible, type SplitterOrientation, type SplitterPanelProps, type SplitterProps, type SplitterSize };
112
+ /** 响应式断点名称 */
113
+ type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
114
+ /** 间距配置项 */
115
+ type Gap = undefined | number | Partial<Record<Breakpoint, number>>;
116
+ /** 瀑布流单项结构 */
117
+ interface MasonryItem<T = any> {
118
+ /** 自定义展示内容,相对 itemRender 具有更高优先级 */
119
+ children?: ReactNode;
120
+ /** 自定义所在列(0-indexed) */
121
+ column?: number;
122
+ /** 自定义存储数据 */
123
+ data?: T;
124
+ /** 高度(手指定时优先使用,否则通过测量动态计算) */
125
+ height?: number;
126
+ /** 唯一标识 */
127
+ key: Key;
128
+ }
129
+ /** 语义化 DOM 节点 */
130
+ type MasonrySemanticDOM = 'root' | 'item';
131
+ type MasonryClassNames = Partial<Record<MasonrySemanticDOM, string>> | ((info: {
132
+ props: MasonryProps<any>;
133
+ }) => Partial<Record<MasonrySemanticDOM, string>>);
134
+ type MasonryStyles = Partial<Record<MasonrySemanticDOM, CSSProperties>> | ((info: {
135
+ props: MasonryProps<any>;
136
+ }) => Partial<Record<MasonrySemanticDOM, CSSProperties>>);
137
+ /** Masonry 组件属性 */
138
+ interface MasonryProps<T = any> {
139
+ /** 用于自定义组件内部各语义化结构的 class,支持对象或函数 */
140
+ classNames?: MasonryClassNames;
141
+ /** 列数,可以是固定值或响应式配置,默认 3 */
142
+ columns?: number | Partial<Record<Breakpoint, number>>;
143
+ /** 是否持续监听子项尺寸变化,默认 false */
144
+ fresh?: boolean;
145
+ /** 间距,可以是固定值、响应式配置或水平垂直间距配置 [horizontalGap, verticalGap],默认 0 */
146
+ gutter?: Gap | [Gap, Gap];
147
+ /** 瀑布流项列表 */
148
+ items?: MasonryItem<T>[];
149
+ /** 自定义项渲染 */
150
+ itemRender?: (item: MasonryItem<T>) => ReactNode;
151
+ /** 语义化结构 style,支持对象和函数形式 */
152
+ styles?: MasonryStyles;
153
+ /** 列排序回调 */
154
+ onLayoutChange?: (layout: {
155
+ key: Key;
156
+ column: number;
157
+ }[]) => void;
158
+ /** 根节点自定义类名 */
159
+ className?: string;
160
+ /** 根节点自定义 CSS 样式 */
161
+ style?: CSSProperties;
162
+ /** JSX 子节点支持 */
163
+ children?: ReactNode;
164
+ }
165
+ declare const Masonry: <T = unknown>(props: MasonryProps<T>) => React__default.JSX.Element;
166
+
167
+ type BorderBeamColorStop = {
168
+ color: string;
169
+ percent: number;
170
+ };
171
+ interface BorderBeamProps extends Omit<HTMLAttributes<HTMLDivElement>, 'color'> {
172
+ /** 装饰内容 */
173
+ children?: ReactNode;
174
+ /** 流光颜色配置,支持单色字符串或渐变停靠点数组 */
175
+ color?: string | BorderBeamColorStop[];
176
+ /** 流光数量,默认为 1 */
177
+ count?: number;
178
+ /** 流光完成一圈动画的时间,单位秒,默认为 6 */
179
+ duration?: number;
180
+ /** 流光线宽,数字类型按像素处理,默认为 1px */
181
+ lineWidth?: number | string;
182
+ /** 流光层相对容器边缘的外扩距离,遇到裁剪容器时可设为 0 */
183
+ outset?: number | string;
184
+ /** 流光可见段的尺寸,数字类型按像素处理,默认为 100 */
185
+ size?: number | string;
186
+ }
187
+ declare const BorderBeam: (props: BorderBeamProps) => React.JSX.Element;
188
+
189
+ export { BorderBeam, type BorderBeamColorStop, type BorderBeamProps, type Breakpoint, CollapsibleBox as CollapseBox, type CollapseBoxButtonPosition, type CollapseBoxDirection, type CollapseBoxProps, DisabledBox, type DisabledBoxProps, type Gap, Masonry, type MasonryClassNames, type MasonryItem, type MasonryProps, type MasonrySemanticDOM, type MasonryStyles, CustomSplitter as Splitter, type SplitterCollapsible, type SplitterOrientation, type SplitterPanelProps, type SplitterProps, type SplitterSize };
package/dist/index.js CHANGED
@@ -827,9 +827,350 @@ var DisabledBox = (props) => {
827
827
  );
828
828
  };
829
829
  var DisabledBox_default = DisabledBox;
830
+
831
+ // Masonry/index.tsx
832
+ import React2, { useCallback as useCallback2, useEffect as useEffect2, useLayoutEffect, useMemo as useMemo2, useRef as useRef2, useState as useState3 } from "react";
833
+ import { jsx as jsx5 } from "react/jsx-runtime";
834
+ var BREAKPOINT_ORDER = ["xs", "sm", "md", "lg", "xl", "xxl"];
835
+ function getBreakpoint(width) {
836
+ if (width >= 1600) return "xxl";
837
+ if (width >= 1200) return "xl";
838
+ if (width >= 992) return "lg";
839
+ if (width >= 768) return "md";
840
+ if (width >= 576) return "sm";
841
+ return "xs";
842
+ }
843
+ function resolveResponsiveValue(value, currentBp, defaultValue) {
844
+ if (value === void 0 || value === null) return defaultValue;
845
+ if (typeof value === "object") {
846
+ const obj = value;
847
+ if (obj[currentBp] !== void 0) return obj[currentBp];
848
+ const currentIdx = BREAKPOINT_ORDER.indexOf(currentBp);
849
+ for (let i = currentIdx - 1; i >= 0; i--) {
850
+ const bp = BREAKPOINT_ORDER[i];
851
+ if (obj[bp] !== void 0) return obj[bp];
852
+ }
853
+ for (let i = currentIdx + 1; i < BREAKPOINT_ORDER.length; i++) {
854
+ const bp = BREAKPOINT_ORDER[i];
855
+ if (obj[bp] !== void 0) return obj[bp];
856
+ }
857
+ return defaultValue;
858
+ }
859
+ return value;
860
+ }
861
+ function resolveGutter(gutter, currentBp) {
862
+ if (!gutter) return [0, 0];
863
+ if (Array.isArray(gutter)) {
864
+ const h = resolveResponsiveValue(gutter[0], currentBp, 0);
865
+ const v = resolveResponsiveValue(gutter[1], currentBp, 0);
866
+ return [h, v];
867
+ }
868
+ const g = resolveResponsiveValue(gutter, currentBp, 0);
869
+ return [g, g];
870
+ }
871
+ var Masonry = (props) => {
872
+ const {
873
+ classNames,
874
+ columns = 3,
875
+ fresh = false,
876
+ gutter = 0,
877
+ items,
878
+ itemRender,
879
+ styles,
880
+ onLayoutChange,
881
+ className,
882
+ style,
883
+ children
884
+ } = props;
885
+ const rootRef = useRef2(null);
886
+ const [containerWidth, setContainerWidth] = useState3(0);
887
+ const [measuredHeights, setMeasuredHeights] = useState3({});
888
+ const itemRefs = useRef2(/* @__PURE__ */ new Map());
889
+ const normalizedItems = useMemo2(() => {
890
+ if (items) return items;
891
+ if (!children) return [];
892
+ return React2.Children.toArray(children).map((child, index) => {
893
+ var _a;
894
+ if (React2.isValidElement(child)) {
895
+ const key = (_a = child.key) != null ? _a : `masonry-item-${index}`;
896
+ return {
897
+ key,
898
+ children: child
899
+ };
900
+ }
901
+ return {
902
+ key: `masonry-item-${index}`,
903
+ children: child
904
+ };
905
+ });
906
+ }, [items, children]);
907
+ const currentBreakpoint = useMemo2(() => getBreakpoint(containerWidth), [containerWidth]);
908
+ const resolvedColumns = useMemo2(() => {
909
+ const count = resolveResponsiveValue(columns, currentBreakpoint, 3);
910
+ return Math.max(1, Math.floor(count));
911
+ }, [columns, currentBreakpoint]);
912
+ const [horizontalGap, verticalGap] = useMemo2(
913
+ () => resolveGutter(gutter, currentBreakpoint),
914
+ [gutter, currentBreakpoint]
915
+ );
916
+ useEffect2(() => {
917
+ if (!rootRef.current) return;
918
+ const observer = new ResizeObserver((entries) => {
919
+ for (const entry of entries) {
920
+ const width = entry.contentRect.width;
921
+ if (width > 0) {
922
+ setContainerWidth(width);
923
+ }
924
+ }
925
+ });
926
+ observer.observe(rootRef.current);
927
+ return () => observer.disconnect();
928
+ }, []);
929
+ const measureItems = useCallback2(() => {
930
+ let hasChange = false;
931
+ const newHeights = {};
932
+ itemRefs.current.forEach((el, key) => {
933
+ if (el) {
934
+ const strKey = String(key);
935
+ const h = el.offsetHeight;
936
+ if (h > 0) {
937
+ newHeights[strKey] = h;
938
+ if (measuredHeights[strKey] !== h) {
939
+ hasChange = true;
940
+ }
941
+ }
942
+ }
943
+ });
944
+ if (hasChange) {
945
+ setMeasuredHeights((prev) => ({ ...prev, ...newHeights }));
946
+ }
947
+ }, [measuredHeights]);
948
+ useLayoutEffect(() => {
949
+ const handle = requestAnimationFrame(() => {
950
+ measureItems();
951
+ });
952
+ return () => cancelAnimationFrame(handle);
953
+ }, [measureItems]);
954
+ useEffect2(() => {
955
+ if (!fresh) return;
956
+ const observer = new ResizeObserver(() => {
957
+ measureItems();
958
+ });
959
+ itemRefs.current.forEach((el) => {
960
+ if (el) observer.observe(el);
961
+ });
962
+ return () => observer.disconnect();
963
+ }, [fresh, measureItems, normalizedItems]);
964
+ const columnWidth = useMemo2(() => {
965
+ if (containerWidth <= 0) return 0;
966
+ const totalGap = (resolvedColumns - 1) * horizontalGap;
967
+ return Math.max(0, (containerWidth - totalGap) / resolvedColumns);
968
+ }, [containerWidth, resolvedColumns, horizontalGap]);
969
+ const { layoutItems, containerHeight, layoutChangeData } = useMemo2(() => {
970
+ const colHeights = new Array(resolvedColumns).fill(0);
971
+ const changeData = [];
972
+ const computedList = normalizedItems.map((item) => {
973
+ var _a, _b;
974
+ const h = (_b = (_a = item.height) != null ? _a : measuredHeights[String(item.key)]) != null ? _b : 0;
975
+ let targetCol = 0;
976
+ if (typeof item.column === "number" && item.column >= 0 && item.column < resolvedColumns) {
977
+ targetCol = Math.floor(item.column);
978
+ } else {
979
+ let minH = colHeights[0];
980
+ for (let i = 1; i < resolvedColumns; i++) {
981
+ if (colHeights[i] < minH) {
982
+ minH = colHeights[i];
983
+ targetCol = i;
984
+ }
985
+ }
986
+ }
987
+ const left = targetCol * (columnWidth + horizontalGap);
988
+ const top = colHeights[targetCol];
989
+ colHeights[targetCol] += h + (h > 0 ? verticalGap : 0);
990
+ changeData.push({ key: item.key, column: targetCol });
991
+ return {
992
+ item,
993
+ left,
994
+ top,
995
+ width: columnWidth,
996
+ height: h,
997
+ column: targetCol
998
+ };
999
+ });
1000
+ const totalH = Math.max(0, ...colHeights);
1001
+ return {
1002
+ layoutItems: computedList,
1003
+ containerHeight: totalH,
1004
+ layoutChangeData: changeData
1005
+ };
1006
+ }, [normalizedItems, resolvedColumns, horizontalGap, verticalGap, columnWidth, measuredHeights]);
1007
+ const prevLayoutRef = useRef2("");
1008
+ useEffect2(() => {
1009
+ const layoutKey = JSON.stringify(layoutChangeData);
1010
+ if (layoutKey !== prevLayoutRef.current) {
1011
+ prevLayoutRef.current = layoutKey;
1012
+ onLayoutChange == null ? void 0 : onLayoutChange(layoutChangeData);
1013
+ }
1014
+ }, [layoutChangeData, onLayoutChange]);
1015
+ const semanticClassNames = useMemo2(() => {
1016
+ var _a;
1017
+ if (typeof classNames === "function") {
1018
+ return (_a = classNames({ props })) != null ? _a : {};
1019
+ }
1020
+ return classNames != null ? classNames : {};
1021
+ }, [classNames, props]);
1022
+ const semanticStyles = useMemo2(() => {
1023
+ var _a;
1024
+ if (typeof styles === "function") {
1025
+ return (_a = styles({ props })) != null ? _a : {};
1026
+ }
1027
+ return styles != null ? styles : {};
1028
+ }, [styles, props]);
1029
+ return /* @__PURE__ */ jsx5(
1030
+ "div",
1031
+ {
1032
+ ref: rootRef,
1033
+ className: ["masonry_root", semanticClassNames.root, className].filter(Boolean).join(" "),
1034
+ style: {
1035
+ minHeight: containerHeight > 0 ? containerHeight : void 0,
1036
+ height: containerHeight > 0 ? containerHeight : void 0,
1037
+ ...semanticStyles.root,
1038
+ ...style
1039
+ },
1040
+ children: layoutItems.map(({ item, left, top, width, height }) => {
1041
+ var _a;
1042
+ const content = (_a = item.children) != null ? _a : itemRender == null ? void 0 : itemRender(item);
1043
+ return /* @__PURE__ */ jsx5(
1044
+ "div",
1045
+ {
1046
+ ref: (node) => {
1047
+ if (node) {
1048
+ itemRefs.current.set(item.key, node);
1049
+ } else {
1050
+ itemRefs.current.delete(item.key);
1051
+ }
1052
+ },
1053
+ className: ["masonry_item", semanticClassNames.item].filter(Boolean).join(" "),
1054
+ style: {
1055
+ width: containerWidth > 0 ? width : "100%",
1056
+ height: height > 0 ? height : void 0,
1057
+ transform: containerWidth > 0 ? `translate3d(${left}px, ${top}px, 0)` : void 0,
1058
+ opacity: containerWidth > 0 ? 1 : 0,
1059
+ ...semanticStyles.item
1060
+ },
1061
+ onLoad: measureItems,
1062
+ children: content
1063
+ },
1064
+ item.key
1065
+ );
1066
+ })
1067
+ }
1068
+ );
1069
+ };
1070
+ var Masonry_default = Masonry;
1071
+
1072
+ // BorderBeam/index.tsx
1073
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1074
+ var formatSizeUnit = (value, defaultValue) => {
1075
+ if (value === void 0 || value === null) {
1076
+ return defaultValue;
1077
+ }
1078
+ if (typeof value === "number") {
1079
+ return `${value}px`;
1080
+ }
1081
+ return value;
1082
+ };
1083
+ var buildGradientString = (color) => {
1084
+ if (!color) {
1085
+ return "linear-gradient(to left, #1677ff 0%, transparent 100%)";
1086
+ }
1087
+ if (typeof color === "string") {
1088
+ return `linear-gradient(to left, ${color} 0%, transparent 100%)`;
1089
+ }
1090
+ if (Array.isArray(color)) {
1091
+ if (color.length === 0) {
1092
+ return "linear-gradient(to left, #1677ff 0%, transparent 100%)";
1093
+ }
1094
+ const sorted = [...color].sort((a, b) => a.percent - b.percent);
1095
+ const mappedStops = sorted.map((stop) => {
1096
+ const mappedPercent = Math.min(Math.max(stop.percent / 100 * 80, 0), 80);
1097
+ return `${stop.color} ${mappedPercent.toFixed(2)}%`;
1098
+ });
1099
+ return `linear-gradient(to left, ${mappedStops.join(", ")}, transparent 100%)`;
1100
+ }
1101
+ return "linear-gradient(to left, #1677ff 0%, transparent 100%)";
1102
+ };
1103
+ var BorderBeam = (props) => {
1104
+ const {
1105
+ children,
1106
+ color,
1107
+ count = 1,
1108
+ duration = 6,
1109
+ lineWidth = "1px",
1110
+ outset = "0px",
1111
+ size = 100,
1112
+ className,
1113
+ style,
1114
+ ...restProps
1115
+ } = props;
1116
+ const formattedLineWidth = formatSizeUnit(lineWidth, "1px");
1117
+ const formattedOutset = formatSizeUnit(outset, "0px");
1118
+ const formattedSize = formatSizeUnit(size, "100px");
1119
+ const gradientCss = buildGradientString(color);
1120
+ const maskStyle = {
1121
+ "--border-beam-line-width": formattedLineWidth,
1122
+ "--border-beam-outset": formattedOutset,
1123
+ "--border-beam-size": formattedSize,
1124
+ "--border-beam-duration": `${duration}s`,
1125
+ "--border-beam-gradient": gradientCss
1126
+ };
1127
+ const beamCount = Math.max(1, count);
1128
+ const beams = Array.from({ length: beamCount }).map((_, index) => {
1129
+ const delaySeconds = -(duration * index / beamCount);
1130
+ return /* @__PURE__ */ jsx6(
1131
+ "div",
1132
+ {
1133
+ className: "react-public-border-beam-item",
1134
+ style: {
1135
+ animationDelay: `${delaySeconds}s`
1136
+ }
1137
+ },
1138
+ index
1139
+ );
1140
+ });
1141
+ if (children !== void 0) {
1142
+ return /* @__PURE__ */ jsxs4(
1143
+ "div",
1144
+ {
1145
+ className: `react-public-border-beam-container ${className || ""}`.trim(),
1146
+ style,
1147
+ ...restProps,
1148
+ children: [
1149
+ children,
1150
+ /* @__PURE__ */ jsx6("div", { className: "react-public-border-beam-mask", style: maskStyle, children: beams })
1151
+ ]
1152
+ }
1153
+ );
1154
+ }
1155
+ return /* @__PURE__ */ jsx6(
1156
+ "div",
1157
+ {
1158
+ className: `react-public-border-beam-mask ${className || ""}`.trim(),
1159
+ style: {
1160
+ ...maskStyle,
1161
+ ...style
1162
+ },
1163
+ ...restProps,
1164
+ children: beams
1165
+ }
1166
+ );
1167
+ };
1168
+ var BorderBeam_default = BorderBeam;
830
1169
  export {
1170
+ BorderBeam_default as BorderBeam,
831
1171
  CollapseBox_default as CollapseBox,
832
1172
  DisabledBox_default as DisabledBox,
1173
+ Masonry_default as Masonry,
833
1174
  Splitter_default as Splitter
834
1175
  };
835
1176
  //# sourceMappingURL=index.js.map