Skip to content

Commit

Permalink
Merge pull request kubesphere#157 from chenz24/feat/collapse
Browse files Browse the repository at this point in the history
Feat/collapse
  • Loading branch information
chenz24 authored Apr 25, 2022
2 parents 85b9620 + c68da39 commit b4c623a
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/angry-bottles-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@kubed/components': patch
---

1. Add Collapse Component
1 change: 1 addition & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"classnames": "^2.3.1",
"dayjs": "^1.10.7",
"lodash": "^4.17.21",
"rc-collapse": "^3.2.0",
"rc-dialog": "^8.6.0",
"rc-drawer": "^4.4.3",
"rc-field-form": "^1.21.2",
Expand Down
59 changes: 59 additions & 0 deletions packages/components/src/Collapse/Collapse.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as React from 'react';
import { Error, Pod } from '@kubed/icons';
import Collapse from './Collapse';
import { BadgeAnchor, Entity, Field, Tooltip, Text, Card } from '../index';

export default {
title: 'Components/Collapse',
component: Collapse,
};

const { Panel } = Collapse;

const Avatar = (
<BadgeAnchor offset={[5, 5]}>
<Tooltip content="Badge information">
<Error className="badge" size={18} color="#fff" fill="rgb(245, 166, 35)" />
</Tooltip>
<Pod size={40} />
</BadgeAnchor>
);

export const basic = () => (
<Collapse accordion>
<Panel header="This is panel header 1" key="1">
<p>Panel content Panel content Panel content</p>
</Panel>
<Panel header="This is panel header 2" key="2">
<p>Panel content 2 Panel content 2 Panel content 2</p>
</Panel>
<Panel header="This is panel header 3" key="3">
<p>Panel content3 Panel content3 Panel content3</p>
</Panel>
</Collapse>
);

export const EntityCollapse = () => {
const header = (
<Entity hoverable className="test-classname">
<Field avatar={Avatar} label="存储类型: rocksdb" value={<a href="/">rocksdbpvc</a>} />
<Field label="存储卷" value="rocksdbpvc" />
<Field label="容量" value="1Gi" width={100} />
<Field label="访问模式" value="ReadWriteOnce" />
</Entity>
);

return (
<Collapse accordion>
<Panel header={header} key="1">
<p>Panel content1 Panel content1</p>
</Panel>
<Panel header="This is panel header 2" key="2">
<p>Panel content2 Panel content2 Panel content2</p>
</Panel>
<Panel header="This is panel header 3" key="3">
<p>Panel content3 Panel content3 Panel content3</p>
</Panel>
</Collapse>
);
};
14 changes: 14 additions & 0 deletions packages/components/src/Collapse/Collapse.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import styled from 'styled-components';
import RcCollapse from 'rc-collapse';

export const StyledCollapse = styled(RcCollapse)`
.kubed-collapse-content-hidden {
display: none;
}
.motion-collapse {
overflow: hidden;
transition: height 0.2s cubic-bezier(0.645, 0.045, 0.355, 1),
opacity 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) !important;
}
`;
83 changes: 83 additions & 0 deletions packages/components/src/Collapse/Collapse.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// https://github.com/ant-design/ant-design/blob/master/components/collapse/Collapse.tsx

import React, { PropsWithChildren } from 'react';
import { CSSMotionProps } from 'rc-motion';
import { omit } from 'lodash';

// import { CaretRight } from '@kubed/icons';
import CollapsePanel, { CollapsibleType } from './CollapsePanel';
import collapseMotion from '../utils/motion';
import toArray from '../utils/toArray';
import { cloneElement } from '../utils/reactNode';
import { StyledCollapse } from './Collapse.styles';

export type ExpandIconPosition = 'left' | 'right' | undefined;

export interface CollapseProps {
activeKey?: Array<string | number> | string | number;
defaultActiveKey?: Array<string | number> | string | number;
accordion?: boolean;
destroyInactivePanel?: boolean;
onChange?: (key: string | string[]) => void;
style?: React.CSSProperties;
className?: string;
bordered?: boolean;
prefixCls?: string;
expandIcon?: (panelProps: PanelProps) => React.ReactNode;
expandIconPosition?: ExpandIconPosition;
ghost?: boolean;
collapsible?: CollapsibleType;
children?: React.ReactNode;
}

interface PanelProps {
isActive?: boolean;
header?: React.ReactNode;
className?: string;
style?: React.CSSProperties;
showArrow?: boolean;
forceRender?: boolean;
/** @deprecated Use `collapsible="disabled"` instead */
disabled?: boolean;
extra?: React.ReactNode;
collapsible?: CollapsibleType;
}

interface CollapseInterface extends React.FC<CollapseProps> {
Panel: typeof CollapsePanel;
}

const Collapse: CollapseInterface = (props: PropsWithChildren<CollapseInterface>) => {
const openMotion: CSSMotionProps = {
...collapseMotion,
motionAppear: false,
leavedClassName: 'kubed-collapse-content-hidden',
};

const getItems = () => {
const { children } = props;
return toArray(children).map((child: React.ReactElement, index: number) => {
if (child.props?.disabled) {
const key = child.key || String(index);
const { disabled, collapsible } = child.props;
const childProps: CollapseProps & { key: React.Key } = {
...omit(child.props, ['disabled']),
key,
collapsible: collapsible ?? (disabled ? 'disabled' : undefined),
};
return cloneElement(child, childProps);
}
return child;
});
};

return (
<StyledCollapse openMotion={openMotion} prefixCls="kubed-collapse" {...props}>
{getItems()}
</StyledCollapse>
);
};

Collapse.Panel = CollapsePanel;

export default Collapse;
35 changes: 35 additions & 0 deletions packages/components/src/Collapse/CollapsePanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// https://github.com/ant-design/ant-design/blob/master/components/collapse/CollapsePanel.tsx
import React from 'react';
import classNames from 'classnames';
import RcCollapse from 'rc-collapse';

export type CollapsibleType = 'header' | 'disabled';

export interface CollapsePanelProps {
key: string | number;
header: React.ReactNode;
disabled?: boolean;
className?: string;
style?: React.CSSProperties;
showArrow?: boolean;
prefixCls?: string;
forceRender?: boolean;
id?: string;
extra?: React.ReactNode;
collapsible?: CollapsibleType;
children?: React.ReactNode;
}

const CollapsePanel: React.FC<CollapsePanelProps> = (props: CollapsePanelProps) => {
const { className = '', showArrow } = props;
const collapsePanelClassName = classNames(
{
'collapse-panel-no-arrow': !showArrow,
},
className
);

return <RcCollapse.Panel {...props} className={collapsePanelClassName} />;
};

export default CollapsePanel;
1 change: 1 addition & 0 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,6 @@ export * from './ActionConfirm/ActionConfirm';
export * from './Progress/Progress';
export * from './utils/color';
export { default as Drawer } from './Drawer/Drawer';
export { default as Collapse } from './Collapse/Collapse';
export { default as forwardRef } from './utils/forwardRef';
export { default as toArray } from './utils/toArray';
2 changes: 1 addition & 1 deletion packages/components/src/utils/motion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const skipOpacityTransition: MotionEndEventHandler = (_, event: MotionEvent) =>
event?.deadline === true || (event as TransitionEvent).propertyName === 'height';

const collapseMotion: CSSMotionProps = {
motionName: 'ant-motion-collapse',
motionName: 'motion-collapse',
onAppearStart: getCollapsedHeight,
onEnterStart: getCollapsedHeight,
onAppearActive: getRealHeight,
Expand Down
24 changes: 24 additions & 0 deletions packages/components/src/utils/reactNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from 'react';

export const { isValidElement } = React;

type AnyObject = Record<any, any>;

type RenderProps = undefined | AnyObject | ((originProps: AnyObject) => AnyObject | undefined);

export function replaceElement(
element: React.ReactNode,
replacement: React.ReactNode,
props: RenderProps
): React.ReactNode {
if (!isValidElement(element)) return replacement;

return React.cloneElement(
element,
typeof props === 'function' ? props(element.props || {}) : props
);
}

export function cloneElement(element: React.ReactNode, props?: RenderProps): React.ReactElement {
return replaceElement(element, element, props) as React.ReactElement;
}
29 changes: 29 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11325,6 +11325,17 @@ rc-align@^4.0.0:
rc-util "^5.3.0"
resize-observer-polyfill "^1.5.1"

rc-collapse@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/rc-collapse/-/rc-collapse-3.2.0.tgz#edfa2f674ec5188f79cc08318b3022abc299e38d"
integrity sha512-TQx4HihWeglsNUz7blY3PaXwr/6cKuuWH4znUfSKTVCuHUKd7c0DitFd+LUkKXdalT8kcElhkVimNzpZOvPESw==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "2.x"
rc-motion "^2.3.4"
rc-util "^5.2.1"
shallowequal "^1.1.0"

rc-dialog@^8.6.0:
version "8.6.0"
resolved "https://registry.yarnpkg.com/rc-dialog/-/rc-dialog-8.6.0.tgz#3b228dac085de5eed8c6237f31162104687442e7"
Expand Down Expand Up @@ -11371,6 +11382,15 @@ rc-motion@^2.0.0, rc-motion@^2.0.1, rc-motion@^2.3.0, rc-motion@^2.4.4:
classnames "^2.2.1"
rc-util "^5.2.1"

rc-motion@^2.3.4:
version "2.5.1"
resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-2.5.1.tgz#3eceb7d891079c0f67a72639d30e168b91839e03"
integrity sha512-h3GKMjFJkK+4z6fNfVlIMrb7WFCZsreivVvHOBb38cKcpKDx5g3kpHwn5Ekbo1+g0nnC02Dtap2trfCAPGxllw==
dependencies:
"@babel/runtime" "^7.11.1"
classnames "^2.2.1"
rc-util "^5.21.0"

rc-overflow@^1.0.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/rc-overflow/-/rc-overflow-1.2.2.tgz#95b0222016c0cdbdc0db85f569c262e7706a5f22"
Expand Down Expand Up @@ -11438,6 +11458,15 @@ rc-util@^5.0.0, rc-util@^5.0.7, rc-util@^5.2.1, rc-util@^5.3.0, rc-util@^5.5.0,
react-is "^16.12.0"
shallowequal "^1.1.0"

rc-util@^5.21.0:
version "5.21.2"
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.21.2.tgz#fa23277ba84e5561af2febdca64de3fc2b3e1528"
integrity sha512-QuuZ2tKMScGtxSx3rLzgPGGDZm/np7phMqA7OcDidSf44abvSk+AdtdD7ZvQPvCEtdC6nCSI5tEVnUaYjjD9/w==
dependencies:
"@babel/runtime" "^7.12.5"
react-is "^16.12.0"
shallowequal "^1.1.0"

rc-util@^5.4.0:
version "5.14.0"
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.14.0.tgz#52c650e27570c2c47f7936c7d32eaec5212492a8"
Expand Down

0 comments on commit b4c623a

Please sign in to comment.