diff --git a/.changeset/angry-bottles-pump.md b/.changeset/angry-bottles-pump.md new file mode 100644 index 00000000..1f66b617 --- /dev/null +++ b/.changeset/angry-bottles-pump.md @@ -0,0 +1,5 @@ +--- +'@kubed/components': patch +--- + +1. Add Collapse Component diff --git a/packages/components/package.json b/packages/components/package.json index fcb73275..a9199cdd 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -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", diff --git a/packages/components/src/Collapse/Collapse.story.tsx b/packages/components/src/Collapse/Collapse.story.tsx new file mode 100644 index 00000000..c965e17e --- /dev/null +++ b/packages/components/src/Collapse/Collapse.story.tsx @@ -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 = ( + + + + + + +); + +export const basic = () => ( + + +

Panel content Panel content Panel content

+
+ +

Panel content 2 Panel content 2 Panel content 2

+
+ +

Panel content3 Panel content3 Panel content3

+
+
+); + +export const EntityCollapse = () => { + const header = ( + + rocksdbpvc} /> + + + + + ); + + return ( + + +

Panel content1 Panel content1

+
+ +

Panel content2 Panel content2 Panel content2

+
+ +

Panel content3 Panel content3 Panel content3

+
+
+ ); +}; diff --git a/packages/components/src/Collapse/Collapse.styles.ts b/packages/components/src/Collapse/Collapse.styles.ts new file mode 100644 index 00000000..480eed68 --- /dev/null +++ b/packages/components/src/Collapse/Collapse.styles.ts @@ -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; + } +`; diff --git a/packages/components/src/Collapse/Collapse.tsx b/packages/components/src/Collapse/Collapse.tsx new file mode 100644 index 00000000..7efb9291 --- /dev/null +++ b/packages/components/src/Collapse/Collapse.tsx @@ -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; + defaultActiveKey?: Array | 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 { + Panel: typeof CollapsePanel; +} + +const Collapse: CollapseInterface = (props: PropsWithChildren) => { + 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 ( + + {getItems()} + + ); +}; + +Collapse.Panel = CollapsePanel; + +export default Collapse; diff --git a/packages/components/src/Collapse/CollapsePanel.tsx b/packages/components/src/Collapse/CollapsePanel.tsx new file mode 100644 index 00000000..88021b58 --- /dev/null +++ b/packages/components/src/Collapse/CollapsePanel.tsx @@ -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 = (props: CollapsePanelProps) => { + const { className = '', showArrow } = props; + const collapsePanelClassName = classNames( + { + 'collapse-panel-no-arrow': !showArrow, + }, + className + ); + + return ; +}; + +export default CollapsePanel; diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index dc154dbb..11920802 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -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'; diff --git a/packages/components/src/utils/motion.ts b/packages/components/src/utils/motion.ts index 3b72fadc..1cc20ad7 100644 --- a/packages/components/src/utils/motion.ts +++ b/packages/components/src/utils/motion.ts @@ -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, diff --git a/packages/components/src/utils/reactNode.ts b/packages/components/src/utils/reactNode.ts new file mode 100644 index 00000000..6a8d73b7 --- /dev/null +++ b/packages/components/src/utils/reactNode.ts @@ -0,0 +1,24 @@ +import * as React from 'react'; + +export const { isValidElement } = React; + +type AnyObject = Record; + +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; +} diff --git a/yarn.lock b/yarn.lock index d2d4c6ce..80835ddd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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" @@ -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" @@ -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"