Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 增加组件 Space #846

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions components/space/Item.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<div className={className} style={style}>
{children}
</div>
{index < latestIndex && split && (
<span className={`${className}-split`} style={style}>
{split}
</span>
)}
</>
);
}
21 changes: 21 additions & 0 deletions components/space/configProvider.tsx
Original file line number Diff line number Diff line change
@@ -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<ConfigConsumerProps>({
getPrefixCls: defaultGetPrefixCls,
});
24 changes: 24 additions & 0 deletions components/space/index.md
Original file line number Diff line number Diff line change
@@ -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` |
30 changes: 30 additions & 0 deletions components/space/index.scss
Original file line number Diff line number Diff line change
@@ -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;
}
}
142 changes: 142 additions & 0 deletions components/space/index.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement> {
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<SpaceProps> = (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 (
<Item
className={itemClassName}
key={key}
direction={direction}
index={i}
marginDirection={marginDirection}
split={split}
wrap={wrap}
>
{child}
</Item>
);
});
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 (
<div
className={cn}
style={{
...gapStyle,
...style,
}}
{...otherProps}
>
<SpaceContext.Provider value={spaceContext}>
{nodes}
</SpaceContext.Provider>
</div>
);
};

export default Space;
20 changes: 20 additions & 0 deletions components/space/styleChecker.ts
Original file line number Diff line number Diff line change
@@ -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;
};
28 changes: 28 additions & 0 deletions components/space/toArray.tsx
Original file line number Diff line number Diff line change
@@ -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;
}
11 changes: 11 additions & 0 deletions components/space/useFlexGapSupport.ts
Original file line number Diff line number Diff line change
@@ -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;
};