Skip to content

Commit

Permalink
feat(link): add link component (#1360)
Browse files Browse the repository at this point in the history
  • Loading branch information
nnmax committed Oct 18, 2021
1 parent 5f4852d commit c4281f8
Show file tree
Hide file tree
Showing 19 changed files with 290 additions and 158 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ storybook-static
*-report.json
*-report.xml
build-storybook.log
.eslintcache
2 changes: 1 addition & 1 deletion src/banner/demos/Banner.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import React from 'react';
import { Story, Meta } from '@storybook/react/types-6-0';
import Button from '../../button';
import Link from '../../link';
import Link from '../../legacy/link';
import Tag from '../../tag';
import Banner, { BannerProps } from '../index';
import Docs from './BannerPage';
Expand Down
66 changes: 66 additions & 0 deletions src/legacy/link/Link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react';
import classnames from 'classnames';
import { usePrefixCls } from '@gio-design/utils';
import { LinkProps } from './interface';

export { LinkProps } from './interface';

const Link: React.FC<LinkProps> = (props: LinkProps) => {
const {
component = 'a',
to = '',
disabled,
prefix: customPrefixCls,
className,
children,
icon,
...otherProps
} = props;

const prefixCls = usePrefixCls('link', customPrefixCls);
const cls = classnames(className, prefixCls, {
[`${prefixCls}--disabled`]: disabled,
});

const handleClick = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
if (disabled) {
e.preventDefault();
return;
}

if (otherProps.onClick) {
otherProps.onClick(e);
return;
}

if (to) {
window.location.href = to;
}
};

const componentProps = {
className: cls,
...otherProps,
onClick: handleClick,
};

if (component === 'a' && !otherProps.onClick) {
return (
// eslint-disable-next-line react/jsx-props-no-spreading
<a href={to} {...componentProps}>
{icon}
{children}
</a>
);
}

const ComponentProp = component;
return (
<ComponentProp {...componentProps}>
{icon}
{children}
</ComponentProp>
);
};

export default Link;
File renamed without changes.
49 changes: 49 additions & 0 deletions src/legacy/link/demos/Link.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import { Story, Meta } from '@storybook/react/types-6-0';
import { withDesign } from 'storybook-addon-designs';
import { FilterOutlined } from '@gio-design/icons';
import Docs from './LinkPage';
import Link from '../index';
import { ILinkProps } from '../interface';
// import '../style';

export default {
title: 'Basic Components/Link',
component: Link,
decorators: [withDesign],
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/file/kP3A6S2fLUGVVMBgDuUx0f/GrowingIO-Design-Components?node-id=1%3A1310',
allowFullscreen: true,
},
docs: {
page: Docs,
},
},
} as Meta;

const Template: Story<ILinkProps> = (args) => (
<div>
<Link {...args}>GrowingIO</Link>
<Link {...args} to="https://growingio.com">
GrowingIO
</Link>
<Link {...args} icon={<FilterOutlined />} to="https://growingio.com" onClick={() => window.alert('被点击')}>
GrowingIO
</Link>
</div>
);

export const Default = Template.bind({});
Default.args = {};

export const Disabled = Template.bind({});
Disabled.args = {
disabled: true,
};

export const CustomComponent = Template.bind({});
CustomComponent.args = {
component: 'span',
};
File renamed without changes.
5 changes: 5 additions & 0 deletions src/legacy/link/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Link from './Link';

export { LinkProps } from './interface';

export default Link;
38 changes: 38 additions & 0 deletions src/legacy/link/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* eslint-disable @typescript-eslint/ban-types */
import { OverrideProps } from '../../utils/interfaces';

export type HtmlElement = keyof React.ReactHTML;

export interface ILinkProps {
/**
* 自定义 Link 根元素使用的组件
*/
component?: React.ElementType;
/**
* 跳转目标链接
*/
to?: string;
/**
* 失效状态
*/
disabled?: boolean;
/**
* 替代 Link 组件 class 的 gio-link 前缀
*/
prefix?: string;

/**
* ICON
*/
icon?: React.ReactNode;
}

export interface LinkTypeMap<P = {}, D extends React.ElementType = 'a'> {
props: P & ILinkProps;
defaultComponent: D;
}

export type LinkProps<D extends React.ElementType = LinkTypeMap['defaultComponent'], P = {}> = OverrideProps<
LinkTypeMap<P, D>,
D
>;
51 changes: 51 additions & 0 deletions src/legacy/link/style/index.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
@import '../../../stylesheet/index.less';

@gio-link-prefix: ~'@{component-prefix}-link';
@gio-link-disabled: ~'@{gio-link-prefix}--disabled';
@iconfont-css-prefix: ~'@{component-prefix}-icon';

// normal
.@{gio-link-prefix} {
display: inline-block;
margin-right: 8px;
margin-left: 8px;
color: @palette-blue-4;
font-size: 14px;
line-height: 22px;
text-decoration: none;
vertical-align: middle;
cursor: pointer;

&:hover {
color: @palette-blue-5;
text-decoration: none;
}

&:active {
color: @palette-blue-3;
}

.@{iconfont-css-prefix} {
width: 14px;
height: 14px;
margin-right: 8px;
svg {
width: 14px;
height: auto;
}
}
}

// disabled
.@{gio-link-disabled} {
color: @palette-black-3;
cursor: not-allowed;

&.@{gio-link-prefix} {
&:hover,
&:active {
color: @palette-black-3;
text-decoration: none;
}
}
}
1 change: 1 addition & 0 deletions src/legacy/link/style/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './index.less';
80 changes: 27 additions & 53 deletions src/link/Link.tsx
Original file line number Diff line number Diff line change
@@ -1,66 +1,40 @@
import { LoadingOutlined } from '@gio-design/icons';
import classNames from 'classnames';
import React from 'react';
import classnames from 'classnames';
import { usePrefixCls } from '@gio-design/utils';
import usePrefixCls from '../utils/hooks/use-prefix-cls';
import { LinkProps } from './interface';

export { LinkProps } from './interface';
const Link = React.forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
const { className, children, prefix, loading = false, disabled = false, ...restProps } = props;

const Link: React.FC<LinkProps> = (props: LinkProps) => {
const {
component = 'a',
to = '',
disabled,
prefix: customPrefixCls,
className,
children,
icon,
...otherProps
} = props;

const prefixCls = usePrefixCls('link', customPrefixCls);
const cls = classnames(className, prefixCls, {
[`${prefixCls}--disabled`]: disabled,
const prefixCls = usePrefixCls('link');
const classes = classNames([prefixCls, className], {
[`${prefixCls}_disabled`]: disabled,
[`${prefixCls}_loading`]: loading,
});

const handleClick = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
if (disabled) {
e.preventDefault();
return;
}

if (otherProps.onClick) {
otherProps.onClick(e);
return;
}

if (to) {
window.location.href = to;
}
};

const componentProps = {
className: cls,
...otherProps,
onClick: handleClick,
};

if (component === 'a' && !otherProps.onClick) {
return (
// eslint-disable-next-line react/jsx-props-no-spreading
<a href={to} {...componentProps}>
{icon}
{children}
</a>
);
}
const prefixIcon = loading ? (
<span className={`${prefixCls}-prefix-icon`}>
<LoadingOutlined rotating />
</span>
) : (
prefix && <span className={`${prefixCls}-prefix-icon`}>{prefix}</span>
);

const ComponentProp = component;
return (
<ComponentProp {...componentProps}>
{icon}
// eslint-disable-next-line react/jsx-props-no-spreading
<a className={classes} ref={ref} aria-disabled={disabled || loading} {...restProps}>
{prefixIcon}
{children}
</ComponentProp>
</a>
);
});

Link.displayName = 'Link';

Link.defaultProps = {
loading: false,
disabled: false,
};

export default Link;
47 changes: 10 additions & 37 deletions src/link/demos/Link.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,22 @@
import React from 'react';
import { Story, Meta } from '@storybook/react/types-6-0';
import { withDesign } from 'storybook-addon-designs';
import { FilterOutlined } from '@gio-design/icons';
import Docs from './LinkPage';
import Link from '../index';
import { ILinkProps } from '../interface';
import { PlusOutlined } from '@gio-design/icons';
import { LinkProps } from '../interface';
import Link from '../Link';
import '../style';

export default {
title: 'Basic Components/Link',
title: 'Upgraded/Link',
component: Link,
decorators: [withDesign],
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/file/kP3A6S2fLUGVVMBgDuUx0f/GrowingIO-Design-Components?node-id=1%3A1310',
allowFullscreen: true,
},
docs: {
page: Docs,
},
},
} as Meta;

const Template: Story<ILinkProps> = (args) => (
<div>
<Link {...args}>GrowingIO</Link>
<Link {...args} to="https://growingio.com">
GrowingIO
</Link>
<Link {...args} icon={<FilterOutlined />} to="https://growingio.com" onClick={() => window.alert('被点击')}>
GrowingIO
</Link>
</div>
const Template: Story<LinkProps> = (args) => (
<Link prefix={<PlusOutlined />} {...args}>
Test Link
</Link>
);

export const Default = Template.bind({});
Default.args = {};

export const Disabled = Template.bind({});
Disabled.args = {
disabled: true,
};

export const CustomComponent = Template.bind({});
CustomComponent.args = {
component: 'span',
Default.args = {
href: 'https://www.growingio.com',
};
2 changes: 1 addition & 1 deletion src/link/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Link from './Link';

export { LinkProps } from './interface';
export type { LinkProps } from './interface';

export default Link;

1 comment on commit c4281f8

@vercel
Copy link

@vercel vercel bot commented on c4281f8 Oct 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.