diff --git a/components/space/Item.tsx b/components/space/Item.tsx
new file mode 100644
index 000000000..43018e72e
--- /dev/null
+++ b/components/space/Item.tsx
@@ -0,0 +1,57 @@
+import * as React from "react";
+import { SpaceContext } from "./index";
+
+export interface ItemProps {
+ className: string;
+ children: React.ReactNode;
+ index: number;
+ direction?: "horizontal" | "vertical";
+ marginDirection: "marginLeft" | "marginRight";
+ split?: string | React.ReactNode;
+ wrap?: boolean;
+}
+
+export default function Item({
+ className,
+ direction,
+ index,
+ marginDirection,
+ children,
+ split,
+ wrap,
+}: ItemProps) {
+ const { horizontalSize, verticalSize, latestIndex, supportFlexGap } =
+ React.useContext(SpaceContext);
+
+ let style: React.CSSProperties = {};
+
+ if (!supportFlexGap) {
+ if (direction === "vertical") {
+ if (index < latestIndex) {
+ style = { marginBottom: horizontalSize / (split ? 2 : 1) };
+ }
+ } else {
+ style = {
+ ...(index < latestIndex && { [marginDirection]: horizontalSize / (split ? 2 : 1) }),
+ ...(wrap && { paddingBottom: verticalSize }),
+ };
+ }
+ }
+
+ if (children === null || children === undefined) {
+ return null;
+ }
+
+ return (
+ <>
+
+ {children}
+
+ {index < latestIndex && split && (
+
+ {split}
+
+ )}
+ >
+ );
+}
diff --git a/components/space/configProvider.tsx b/components/space/configProvider.tsx
new file mode 100644
index 000000000..f9f3e7622
--- /dev/null
+++ b/components/space/configProvider.tsx
@@ -0,0 +1,21 @@
+import React from "react";
+
+export type DirectionType = "ltr" | "rtl" | undefined;
+
+export type SizeType = "small" | "middle" | "large" | undefined;
+
+export interface ConfigConsumerProps {
+ getPrefixCls: (suffixCls?: string) => string;
+ direction?: DirectionType;
+ space?: {
+ size?: SizeType | number;
+ };
+}
+
+export const defaultGetPrefixCls = (suffixCls?: string) => {
+ return suffixCls ? `auge-${suffixCls}` : "auge";
+};
+
+export const ConfigContext = React.createContext({
+ getPrefixCls: defaultGetPrefixCls,
+});
diff --git a/components/space/index.md b/components/space/index.md
new file mode 100644
index 000000000..6ff72d2ce
--- /dev/null
+++ b/components/space/index.md
@@ -0,0 +1,24 @@
+---
+title: 间距
+category: 组件
+order: 99
+sidebar: doc
+---
+
+# 属性
+
+| 属性 | 说明 | 类型 | 默认值 |
+| --------- | ------------------------------- | ---------- | ----------- |
+| className | 指定元素固定距离顶部的位置 | `number` | `undefined` |
+| style | 指定元素固定距离底部的位置 | `number` | `undefined` |
+| size | 间距大小 | `string` | `samll ` |
+| direction | 排列方向 | `Function` | `undefined` |
+| align | 对齐方式 | `Function` | `undefined` |
+| split | 分隔符 | `Function` | `undefined` |
+| wrap | 是否自动换行(horizontal 时生效) | `Function` | `undefined` |
+
+# 事件
+
+| 事件名 | 说明 | 参数 |
+| ------ | ------------------ | --------- |
+| change | 固定状态改变时触发 | `isFixed` |
diff --git a/components/space/index.scss b/components/space/index.scss
new file mode 100644
index 000000000..bc3ba6e46
--- /dev/null
+++ b/components/space/index.scss
@@ -0,0 +1,30 @@
+$auge-prefix: 'auge';
+
+$space-prefix-cls: #{$auge-prefix}-space;
+$space-item-prefix-cls: #{$auge-prefix}-space-item;
+
+.#{$space-prefix-cls} {
+ display: inline-flex;
+ &-vertical {
+ flex-direction: column;
+ }
+ &-align {
+ &-center {
+ align-items: center;
+ }
+ &-start {
+ align-items: flex-start;
+ }
+ &-end {
+ align-items: flex-end;
+ }
+ &-baseline {
+ align-items: baseline;
+ }
+ }
+}
+.#{$space-prefix-cls} {
+ &-rtl {
+ direction: rtl;
+ }
+}
diff --git a/components/space/index.tsx b/components/space/index.tsx
new file mode 100644
index 000000000..e74dde92b
--- /dev/null
+++ b/components/space/index.tsx
@@ -0,0 +1,142 @@
+import React, { useContext } from "react";
+import classNames from "classnames";
+import { ConfigContext, SizeType } from "./configProvider";
+import Item from "./Item";
+import toArray from "./toArray";
+import useFlexGapSupport from "./useFlexGapSupport";
+import "./index.scss";
+
+export interface Option {
+ keepEmpty?: boolean;
+}
+
+export const SpaceContext = React.createContext({
+ latestIndex: 0,
+ horizontalSize: 0,
+ verticalSize: 0,
+ supportFlexGap: false,
+});
+
+export type SpaceSize = SizeType | number;
+
+export interface SpaceProps extends React.HTMLAttributes {
+ className?: string;
+ style?: React.CSSProperties;
+ size?: SpaceSize | [SpaceSize, SpaceSize]; // 间距大小
+ direction?: "horizontal" | "vertical"; // 排列方向
+ align?: "start" | "end" | "center" | "baseline"; // 对齐方式
+ split?: React.ReactNode; // 分隔符
+ wrap?: boolean; // 是否自动换行
+}
+
+const spaceSize = {
+ small: 8,
+ middle: 16,
+ large: 24,
+};
+
+function getNumberSize(size: SpaceSize) {
+ return typeof size === "string" ? spaceSize[size] : size || 0;
+}
+const Space: React.FC = (props) => {
+ const {
+ getPrefixCls,
+ space,
+ direction: directionConfig,
+ } = useContext(ConfigContext);
+ const {
+ size = space?.size || "small",
+ align,
+ className,
+ children,
+ direction = "horizontal",
+ split,
+ style,
+ wrap = false,
+ ...otherProps
+ } = props;
+ const supportFlexGap = useFlexGapSupport();
+ const [horizontalSize, verticalSize] = React.useMemo(
+ () =>
+ (
+ (Array.isArray(size) ? size : [size, size]) as [SpaceSize, SpaceSize]
+ ).map((item) => getNumberSize(item)),
+ [size]
+ );
+ const childNodes = toArray(children, { keepEmpty: true });
+ const mergedAlign =
+ align === undefined && direction === "horizontal" ? "center" : align;
+ const prefixCls = getPrefixCls("space");
+ const cn = classNames(
+ prefixCls,
+ `${prefixCls}-${direction}`,
+ {
+ [`${prefixCls}-rtl`]: directionConfig === "rtl",
+ [`${prefixCls}-align-${mergedAlign}`]: mergedAlign,
+ },
+ className
+ );
+ const itemClassName = `${prefixCls}-item`;
+ const marginDirection =
+ directionConfig === "rtl" ? "marginLeft" : "marginRight";
+ // Calculate latest one
+ let latestIndex = 0;
+ const nodes = childNodes.map((child: any, i) => {
+ if (child !== null && child !== undefined) {
+ latestIndex = i;
+ }
+ const key = (child && child.key) || `${itemClassName}-${i}`;
+ return (
+ -
+ {child}
+
+ );
+ });
+ const spaceContext = React.useMemo(
+ () => ({ horizontalSize, verticalSize, latestIndex, supportFlexGap }),
+ [horizontalSize, verticalSize, latestIndex, supportFlexGap]
+ );
+
+ if (childNodes.length === 0) {
+ return null;
+ }
+ const gapStyle: React.CSSProperties = {};
+
+ if (wrap) {
+ gapStyle.flexWrap = "wrap";
+
+ if (!supportFlexGap) {
+ gapStyle.marginBottom = -verticalSize;
+ }
+ }
+
+ if (supportFlexGap) {
+ gapStyle.columnGap = horizontalSize;
+ gapStyle.rowGap = verticalSize;
+ }
+
+ return (
+
+
+ {nodes}
+
+
+ );
+};
+
+export default Space;
diff --git a/components/space/styleChecker.ts b/components/space/styleChecker.ts
new file mode 100644
index 000000000..10d7ee885
--- /dev/null
+++ b/components/space/styleChecker.ts
@@ -0,0 +1,20 @@
+let flexGapSupported: boolean | undefined;
+export const detectFlexGapSupported = () => {
+ if (flexGapSupported !== undefined) {
+ return flexGapSupported;
+ }
+
+ const flex = document.createElement("div");
+ flex.style.display = "flex";
+ flex.style.flexDirection = "column";
+ flex.style.rowGap = "1px";
+
+ flex.appendChild(document.createElement("div"));
+ flex.appendChild(document.createElement("div"));
+
+ document.body.appendChild(flex);
+ flexGapSupported = flex.scrollHeight === 1;
+ document.body.removeChild(flex);
+
+ return flexGapSupported;
+};
diff --git a/components/space/toArray.tsx b/components/space/toArray.tsx
new file mode 100644
index 000000000..da7906617
--- /dev/null
+++ b/components/space/toArray.tsx
@@ -0,0 +1,28 @@
+import React from "react";
+import { isFragment } from "react-is";
+
+export interface Option {
+ keepEmpty?: boolean;
+}
+
+export default function toArray(
+ children: React.ReactNode,
+ option: Option = {},
+): React.ReactElement[] {
+ let ret: React.ReactElement[] = [];
+
+ React.Children.forEach(children, (child: any | any[]) => {
+ if ((child === undefined || child === null) && !option.keepEmpty) {
+ return;
+ }
+ if (Array.isArray(child)) {
+ ret = ret.concat(toArray(child));
+ } else if (isFragment(child) && child.props) {
+ ret = ret.concat(toArray(child.props.children, option));
+ } else {
+ ret.push(child);
+ }
+ });
+
+ return ret;
+}
diff --git a/components/space/useFlexGapSupport.ts b/components/space/useFlexGapSupport.ts
new file mode 100644
index 000000000..fa2b43597
--- /dev/null
+++ b/components/space/useFlexGapSupport.ts
@@ -0,0 +1,11 @@
+import * as React from "react";
+import { detectFlexGapSupported } from "./styleChecker";
+
+export default () => {
+ const [flexible, setFlexible] = React.useState(false);
+ React.useEffect(() => {
+ setFlexible(detectFlexGapSupported());
+ }, []);
+
+ return flexible;
+};