setEnterShowTip(true)}
50 | onMouseLeave={() => setEnterShowTip(false)}
51 | />
52 |
53 | )
54 | }
55 |
56 | export default SliderBtn;
57 |
--------------------------------------------------------------------------------
/src/Scrollbar/__test__/__snapshots__/index.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Scrollbar 组件测试 create 1`] = `
4 |
5 |
89 |
90 | `;
91 |
--------------------------------------------------------------------------------
/src/Popup/__test__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render } from '@testing-library/react';
2 | import userEvent from '@testing-library/user-event';
3 | import Popup from '../index';
4 |
5 | const App = ({ children }) => (
6 |
7 |
8 |
{children}
9 |
10 |
11 | )
12 |
13 | describe('Popup 组件测试', () => {
14 | it('create', () => {
15 | const { asFragment } = render(
16 |
17 |
18 |
19 | );
20 | expect(asFragment()).toMatchSnapshot();
21 | });
22 |
23 | it('trigger', async () => {
24 | const { getByTestId } = render(
25 |
26 |
27 | ,
28 | { wrapper: App }
29 | );
30 |
31 | expect(document.querySelector('.i-popup')).not.toBeInTheDocument()
32 |
33 | const user = userEvent.setup()
34 | await user.click(getByTestId('trigger'))
35 |
36 | expect(document.querySelector('.i-popup')).toBeInTheDocument()
37 | });
38 |
39 | it('disabled', async () => {
40 | const { getByTestId } = render(
41 |
42 |
43 | ,
44 | { wrapper: App }
45 | );
46 |
47 | expect(document.querySelector('.i-popup')).not.toBeInTheDocument()
48 |
49 | const user = userEvent.setup()
50 | await user.click(getByTestId('trigger'))
51 |
52 | expect(document.querySelector('.i-popup')).not.toBeInTheDocument()
53 | });
54 | });
55 |
56 |
--------------------------------------------------------------------------------
/src/Upload/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav:
3 | title: 组件
4 | path: /components
5 | group:
6 | title: 表单组件
7 | order: 3
8 | order: 12
9 | ---
10 |
11 | # Upload 上传
12 |
13 | 用户上传任意内容的组件。
14 |
15 | ## 基本用法
16 |
17 | ```tsx
18 | import React from 'react';
19 | import { Upload } from 'idesign-react';
20 |
21 | const App = () => {
22 | const handleChange = (file) => {
23 | console.log(file)
24 | }
25 |
26 | return (
27 |
28 | );
29 | };
30 |
31 | export default App;
32 | ```
33 |
34 | ## 不同主题
35 |
36 | 可通过 `theme` 属性控制不同属性的上传组件:
37 |
38 | ```tsx
39 | import React from 'react';
40 | import { Upload } from 'idesign-react';
41 |
42 | const App = () => {
43 | const handleChange = (file) => {
44 | console.log(file)
45 | }
46 |
47 | return (
48 |
49 |
50 |
51 |
52 |
53 | );
54 | };
55 |
56 | export default App;
57 | ```
58 |
59 | ## 自定义提示
60 |
61 | 可通过 `placeholder` 属性控制自定义占位符:
62 |
63 | ```tsx
64 | import React from 'react';
65 | import { Upload } from 'idesign-react';
66 |
67 | const App = () => {
68 | return (
69 |
70 | );
71 | };
72 |
73 | export default App;
74 | ```
75 |
76 | ## 自定义节点
77 |
78 | 可通过包裹自定义节点来展示任意上传组件:
79 |
80 | ```tsx
81 | import React from 'react';
82 | import { Upload } from 'idesign-react';
83 |
84 | const App = () => {
85 | return (
86 |
87 |
88 |
89 | );
90 | };
91 |
92 | export default App;
93 | ```
94 |
95 |
96 |
--------------------------------------------------------------------------------
/src/Tag/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classNames from 'classnames';
3 | import './index.scss';
4 | import Icon from '../Icon';
5 | import { TagProps } from './type';
6 |
7 | const Tag: React.FC
= (props) => {
8 | const {
9 | children = '',
10 | className,
11 | icon,
12 | maxWidth,
13 | size,
14 | style,
15 | theme,
16 | type = 'default',
17 | onAdd,
18 | onClick = () => { },
19 | onClose,
20 | ...restProps
21 | } = props;
22 |
23 | const handleClick = (e: React.MouseEvent) => {
24 | if (onAdd) {
25 | onAdd(e);
26 | }
27 | onClick(e);
28 | };
29 |
30 | const handleClose = (e: React.MouseEvent) => {
31 | e.stopPropagation();
32 | onClose?.(e)
33 | }
34 |
35 | return (
36 |
50 | {onAdd &&
}
51 | {icon &&
}
52 | {children}
53 | {onClose && (
54 |
55 |
56 |
57 | )}
58 |
59 | );
60 | };
61 |
62 | Tag.displayName = 'Tag';
63 |
64 | export default Tag;
65 |
--------------------------------------------------------------------------------
/src/Grid/GridItem.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classNames from 'classnames';
3 | import './index.scss';
4 | import { GridItemProps } from './type';
5 |
6 | const GridItem: React.FC = (props) => {
7 | const {
8 | align,
9 | children = '',
10 | className,
11 | gutter,
12 | order,
13 | offset,
14 | style,
15 | span = 24,
16 | width,
17 | ...restProps
18 | } = props;
19 |
20 | const limitNum = (num: number) => {
21 | let result = parseInt(num.toString());
22 | if (result > 24) {
23 | result = 24;
24 | }
25 | if (result < 0) {
26 | result = 0;
27 | }
28 | return result;
29 | };
30 |
31 | const computedWidth = (width: React.CSSProperties["width"]) => {
32 | if (isNaN(width as number)) {
33 | return width;
34 | }
35 | const result = width + 'px';
36 | return result;
37 | };
38 |
39 | return (
40 |
64 | {children}
65 |
66 | );
67 | };
68 |
69 | export default GridItem;
70 |
--------------------------------------------------------------------------------
/src/Badge/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classNames from 'classnames';
3 | import './index.scss';
4 | import { BadgeProps } from './type';
5 |
6 | const Badge: React.FC = (props) => {
7 | const {
8 | children = '',
9 | className,
10 | style,
11 | count = 0,
12 | maxCount = 99,
13 | color,
14 | dot = false,
15 | size = "medium",
16 | shape = "circle",
17 | showZero = true,
18 | offset,
19 | ...restProps
20 | } = props;
21 |
22 | const getDisplayCount = () => {
23 | if (typeof count === 'number' && count > maxCount) {
24 | return `${maxCount}+`;
25 | }
26 | return count;
27 | };
28 |
29 | let isHidden = !count;
30 | if (typeof count === 'number') {
31 | isHidden = count < 1 && !showZero;
32 | }
33 |
34 | const getOffset = () => {
35 | const result: React.CSSProperties = {}
36 | if (offset) {
37 | offset[0] && (result.right = +offset[0])
38 | offset[1] && (result.marginTop = +offset[1])
39 | }
40 | return result
41 | }
42 |
43 | return (
44 |
51 | {children}
52 | {!isHidden && (
53 |
61 | {!dot ? getDisplayCount() : null}
62 |
63 | )}
64 |
65 | );
66 | };
67 |
68 | Badge.displayName = 'Badge';
69 |
70 | export default Badge;
71 |
--------------------------------------------------------------------------------
/src/Checkbox/type.ts:
--------------------------------------------------------------------------------
1 | export interface CheckboxProps {
2 | /**
3 | * 类名
4 | */
5 | className?: string;
6 | /**
7 | * 内容
8 | */
9 | children?: React.ReactNode;
10 | /**
11 | * 自定义样式
12 | */
13 | style?: React.CSSProperties;
14 | /**
15 | * 按钮多选框尺寸
16 | * @default medium
17 | */
18 | size?: 'small' | 'medium' | 'large';
19 | /**
20 | * 是否固定选中(受控)
21 | */
22 | checked?: boolean;
23 | /**
24 | * 是否默认选中(非受控)
25 | * @default false
26 | */
27 | defaultChecked?: boolean;
28 | /**
29 | * 是否禁用组件
30 | * @default false
31 | */
32 | disabled?: boolean;
33 | /**
34 | * 多选框的值
35 | */
36 | value?: string | number;
37 | /**
38 | * 值变化时触发
39 | */
40 | onChange?: (checked: boolean, ev: React.ChangeEvent) => void;
41 | }
42 |
43 | export interface CheckboxGroupProps {
44 | /**
45 | * 类名
46 | */
47 | className?: string;
48 | /**
49 | * 内容
50 | */
51 | children?: React.ReactNode;
52 | /**
53 | * 自定义样式
54 | */
55 | style?: React.CSSProperties;
56 | /**
57 | * 多选框组选中固定值(受控)
58 | */
59 | selected?: Array | string | number;
60 | /**
61 | * 多选框组选中默认值(非受控)
62 | * @default 第一项
63 | */
64 | defaultSelected?: Array | string | number;
65 | /**
66 | * 按钮多选框全局尺寸
67 | * @default medium
68 | */
69 | size?: 'small' | 'medium' | 'large';
70 | /**
71 | * 多选框组是否全局禁用
72 | */
73 | disabled?: boolean;
74 | /**
75 | * 选中某一项时触发
76 | */
77 | onChange?: (
78 | value: Array | string | number,
79 | ev: React.ChangeEvent,
80 | ) => void;
81 | }
82 |
83 | export interface CheckboxContextValue {
84 | inject: (props: CheckboxProps) => CheckboxProps;
85 | }
86 |
--------------------------------------------------------------------------------
/src/Grid/index.scss:
--------------------------------------------------------------------------------
1 | $grid-list: (
2 | 0,
3 | 1,
4 | 2,
5 | 3,
6 | 4,
7 | 5,
8 | 6,
9 | 7,
10 | 8,
11 | 9,
12 | 10,
13 | 11,
14 | 12,
15 | 13,
16 | 14,
17 | 15,
18 | 16,
19 | 17,
20 | 18,
21 | 19,
22 | 20,
23 | 21,
24 | 22,
25 | 23,
26 | 24
27 | );
28 |
29 | .i-grid {
30 | width: 100%;
31 | display: flex;
32 | flex-flow: row wrap;
33 | margin-right: 0px;
34 | margin-left: 0px;
35 | }
36 |
37 | .i-grid--align-top {
38 | align-items: flex-start;
39 | }
40 |
41 | .i-grid--align-middle {
42 | align-items: center;
43 | }
44 |
45 | .i-grid--align-bottom {
46 | align-items: flex-end;
47 | }
48 |
49 | .i-grid--justify-start {
50 | justify-content: flex-start;
51 | }
52 |
53 | .i-grid--justify-center {
54 | justify-content: center;
55 | }
56 |
57 | .i-grid--justify-end {
58 | justify-content: flex-end;
59 | }
60 |
61 | .i-grid--justify-space-between {
62 | justify-content: space-between;
63 | }
64 |
65 | .i-grid--justify-space-around {
66 | justify-content: space-around;
67 | }
68 |
69 | .i-grid__item {
70 | position: relative;
71 | max-width: 100%;
72 | }
73 |
74 | .i-grid__item--align-top {
75 | align-self: flex-start;
76 | }
77 |
78 | .i-grid__item--align-middle {
79 | align-self: center;
80 | }
81 |
82 | .i-grid__item--align-bottom {
83 | align-self: flex-end;
84 | }
85 |
86 | @each $item in $grid-list {
87 | .i-grid__item--span-#{$item} {
88 | @if $item == 0 { display: none; } @else { display: block; };
89 | max-width: calc((nth($item, 1) / 24) * 100%);
90 | flex: 0 0 calc((nth($item, 1) / 24) * 100%);
91 | }
92 | .i-grid__item--offset-#{$item} {
93 | margin-left: calc((nth($item, 1) / 24) * 100%);
94 | }
95 | .i-grid__item--order-#{$item} {
96 | order: $item;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Progress/__test__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render } from '@testing-library/react';
2 | import Progress from '../index';
3 |
4 | describe('Progress 组件测试', () => {
5 | it('create', () => {
6 | const { asFragment } = render();
7 | expect(asFragment()).toMatchSnapshot();
8 | });
9 |
10 | it('label', () => {
11 | const { queryByText } = render();
12 | expect(queryByText('😄')).toBeInTheDocument();
13 | });
14 |
15 | it('hide label', () => {
16 | const { container } = render();
17 | expect(container.querySelector('.i-progress__info')).not.toBeInTheDocument();
18 | });
19 |
20 | it('innerLabel', () => {
21 | const { container } = render();
22 | expect(container.firstChild.firstChild).toHaveClass('i-progress-bar__has-label');
23 | });
24 |
25 | it('color backColor', () => {
26 | const { container } = render();
27 | expect(container.querySelector('.i-progress-bar')).toHaveStyle('background: red');
28 | expect(container.querySelector('.i-progress-bar__inner')).toHaveStyle('background: blue');
29 | });
30 |
31 | it('width', () => {
32 | const { container } = render();
33 | expect(container.firstChild.firstChild).toHaveStyle('width: 300px');
34 | });
35 |
36 | it('strokeWidth', () => {
37 | const { container } = render();
38 | expect(container.firstChild.firstChild).toHaveStyle('height: 30px');
39 | });
40 |
41 | it('circle', () => {
42 | const { asFragment } = render();
43 | expect(asFragment()).toMatchSnapshot();
44 | });
45 | });
46 |
47 |
--------------------------------------------------------------------------------
/src/Button/__test__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, fireEvent } from '@testing-library/react';
2 | import Button from '../index';
3 |
4 | describe('Button 组件测试', () => {
5 | const renderBtn = (btn) => {
6 | const { container } = render(btn);
7 | return container.firstChild
8 | }
9 |
10 | const renderBtnHasClass = (btn, className) => {
11 | return expect(renderBtn(btn)).toHaveClass(className);
12 | }
13 |
14 | it('children', () => {
15 | const { queryByText } = render();
16 | expect(queryByText('foo')).toBeInTheDocument();
17 | });
18 |
19 | it('type', () => {
20 | renderBtnHasClass(, 'i-button--type-success')
21 | });
22 |
23 | it('variant', () => {
24 | renderBtnHasClass(, 'i-button--variant-outline')
25 | });
26 |
27 | it('active', () => {
28 | renderBtnHasClass(, 'i-button-active')
29 | });
30 |
31 | it('disabled', () => {
32 | const clickFn = jest.fn();
33 | const btn = renderBtn()
34 | expect(btn).toBeDisabled();
35 | fireEvent.click(btn);
36 | expect(clickFn).toBeCalledTimes(0);
37 | });
38 |
39 | it('size', () => {
40 | renderBtnHasClass(, 'i-button--size-small')
41 | });
42 |
43 | it('shape', () => {
44 | renderBtnHasClass(, 'i-button--shape-circle')
45 | });
46 |
47 | it('icon', () => {
48 | const { asFragment } = render();
49 | expect(asFragment()).toMatchSnapshot();
50 | });
51 |
52 | it('onClick', () => {
53 | const clickFn = jest.fn();
54 | fireEvent.click(renderBtn());
55 | expect(clickFn).toHaveBeenCalled();
56 | });
57 | });
58 |
59 |
--------------------------------------------------------------------------------
/src/ColorPicker/type.ts:
--------------------------------------------------------------------------------
1 | export interface colorListType {
2 | value: string
3 | }
4 |
5 | export interface ColorPickerProps {
6 | /**
7 | * 类名
8 | */
9 | className?: string;
10 | /**
11 | * 自定义样式
12 | */
13 | style?: React.CSSProperties;
14 | /**
15 | * 触发颜色块类名
16 | */
17 | triggerClassName?: string;
18 | /**
19 | * 触发颜色块样式
20 | */
21 | triggerStyle?: React.CSSProperties;
22 | /**
23 | * 触发颜色块尺寸
24 | * @default medium
25 | */
26 | size?: 'small' | 'medium' | 'large';
27 | /**
28 | * 固定颜色值(受控)
29 | */
30 | value?: string;
31 | /**
32 | * 默认颜色值(非受控)
33 | * @default #5e62ea
34 | */
35 | defaultValue?: string;
36 | /**
37 | * 底部预设颜色
38 | */
39 | colorList?: colorListType[]
40 | /**
41 | * 是否禁用颜色选择器
42 | * @default false
43 | */
44 | disabled?: boolean;
45 | /**
46 | * 修改颜色值时触发
47 | */
48 | onChange?: (val: string) => void
49 | /**
50 | * 切换颜色面板时触发
51 | */
52 | onTrigger?: (val: string, visible: boolean) => void
53 | }
54 |
55 | export interface ColorPanelProps extends ColorPickerProps {
56 | /**
57 | * 点击关闭按钮时触发
58 | */
59 | onClose?: () => void
60 | }
61 |
62 | export interface ColorItemProps {
63 | /**
64 | * 颜色
65 | * @default #5e62ea
66 | */
67 | color?: string;
68 | /**
69 | * 点击事件
70 | */
71 | onClick?: (val: string) => void;
72 | }
73 |
74 | export interface ColorCursorProps {
75 | /**
76 | * 自定义样式
77 | */
78 | style?: React.CSSProperties;
79 | /**
80 | * 初始横坐标比例
81 | */
82 | x?: number;
83 | /**
84 | * 初始纵坐标比例
85 | */
86 | y?: number;
87 | /**
88 | * 拖拽方向
89 | * @default xy
90 | */
91 | mode?: 'x' | 'y' | 'xy';
92 | /**
93 | * 滑块颜色
94 | * @default #5e62ea
95 | */
96 | color?: string;
97 | }
98 |
--------------------------------------------------------------------------------
/src/Carousel/__test__/__snapshots__/index.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Carousel 组件测试 create 1`] = `
4 |
5 |
9 |
12 |
16 | -
19 | item3
20 |
21 | -
24 | item1
25 |
26 | -
29 | item2
30 |
31 | -
34 | item3
35 |
36 | -
39 | item1
40 |
41 |
42 |
43 |
56 |
59 |
63 |
64 |
67 |
71 |
72 |
73 |
74 | `;
75 |
--------------------------------------------------------------------------------
/src/Breadcrumb/__test__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render } from '@testing-library/react';
2 | import Breadcrumb from '../index';
3 |
4 | describe('Breadcrumb 组件测试', () => {
5 | const renderFirstItem = (breadcrumb) => {
6 | const { container } = render(breadcrumb);
7 | return container.firstChild.firstChild
8 | }
9 |
10 | it('create', () => {
11 | const el = (
12 |
13 | item1
14 | item2
15 | item3
16 |
17 | );
18 | const { asFragment } = render(el);
19 | expect(asFragment()).toMatchSnapshot();
20 | });
21 |
22 | it('breadcrumbItem children', () => {
23 | const el = (
24 |
25 | item
26 |
27 | );
28 | expect(renderFirstItem(el)).toHaveTextContent('item');
29 | });
30 |
31 | it('breadcrumbItem disabled', () => {
32 | const el = (
33 |
34 | item
35 |
36 | );
37 | expect(renderFirstItem(el)).toHaveClass('i-breadcrumb-is-disabled');
38 | });
39 |
40 | it('max width', () => {
41 | const el = (
42 |
43 | item
44 |
45 | );
46 | expect(renderFirstItem(el).firstChild).toHaveStyle('max-width: 120px');
47 | });
48 |
49 | it('separator', () => {
50 | const el = (
51 |
52 | item
53 |
54 | );
55 | const { queryByText } = render(el);
56 | expect(queryByText('👉')).toBeInTheDocument();
57 | });
58 | });
59 |
60 |
--------------------------------------------------------------------------------
/src/Select/type.ts:
--------------------------------------------------------------------------------
1 | import { DropdownOption } from "../Dropdown/type";
2 |
3 | export interface SelectProps {
4 | /**
5 | * 类名
6 | */
7 | className?: string;
8 | /**
9 | * 内容
10 | */
11 | children?: React.ReactNode;
12 | /**
13 | * 自定义样式
14 | */
15 | style?: React.CSSProperties;
16 | /**
17 | * 选择器宽度
18 | * @default 100%
19 | */
20 | width?:React.CSSProperties["width"];
21 | /**
22 | * 固定选中值(受控)
23 | */
24 | value?: string | number | Array;
25 | /**
26 | * 默认选中值(非受控)
27 | * @default []
28 | */
29 | defaultValue?: string | number | Array;
30 | /**
31 | * 占位符
32 | * @default 请选择
33 | */
34 | placeholder?: string;
35 | /**
36 | * 下拉操作项
37 | * @default []
38 | */
39 | options?: Array;
40 | /**
41 | * 选择器尺寸
42 | * @default medium
43 | */
44 | size?: "small" | "medium" | "large";
45 | /**
46 | * 是否可一键清空
47 | * @default true
48 | */
49 | clearable?: boolean;
50 | /**
51 | * 选择框前置图标名
52 | */
53 | prefixIcon?: string;
54 | /**
55 | * 选择框后置图标名
56 | */
57 | suffixIcon?: string;
58 | /**
59 | * 选择框前置图标类名
60 | */
61 | prefixIconClass?: string;
62 | /**
63 | * 选择框后置图标类名
64 | */
65 | suffixIconClass?: string;
66 | /**
67 | * 级联子层级展开方向
68 | * @default right
69 | */
70 | cascaderDirection?: 'left' | 'right';
71 | /**
72 | * 是否可多选
73 | * @default false
74 | */
75 | multiple?: boolean;
76 | /**
77 | * 是否禁用选择器
78 | * @default false
79 | */
80 | disabled?: boolean;
81 | /**
82 | * 选中值变化时触发
83 | */
84 | onChange?: (value: string | number | Array) => void;
85 | }
86 |
87 | export interface SelectItemProps {
88 | /**
89 | * 内容
90 | */
91 | children?: React.ReactNode;
92 | }
93 |
--------------------------------------------------------------------------------
/src/Tag/__test__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, fireEvent } from '@testing-library/react';
2 | import Tag from '../index';
3 |
4 | describe('Tag 组件测试', () => {
5 | it('create', () => {
6 | const { asFragment } = render(标签);
7 | expect(asFragment()).toMatchSnapshot();
8 | });
9 |
10 | it('type', () => {
11 | const { container } = render(标签);
12 | expect(container.firstChild).toHaveClass('i-tag--type-success');
13 | });
14 |
15 | it('theme', () => {
16 | const { container } = render(标签);
17 | expect(container.firstChild).toHaveClass('i-tag--theme-dark');
18 | });
19 |
20 | it('icon', () => {
21 | const { container } = render(标签);
22 | expect(container.firstChild.firstChild).toHaveClass('icon-TagPrice');
23 | });
24 |
25 | it('onAdd', () => {
26 | const clickFn = jest.fn();
27 | const { container } = render(标签);
28 | expect(container.firstChild.firstChild).toHaveClass('icon-ThePlus');
29 | fireEvent.click(container.firstChild);
30 | expect(clickFn).toHaveBeenCalled();
31 | });
32 |
33 | it('onClose', () => {
34 | const clickFn = jest.fn();
35 | const { container } = render(标签);
36 | expect(container.firstChild.lastChild).toHaveClass('i-tag--close-btn');
37 | fireEvent.click(container.firstChild.lastChild);
38 | expect(clickFn).toHaveBeenCalled();
39 | });
40 |
41 | it('size', () => {
42 | const { container } = render(标签);
43 | expect(container.firstChild).toHaveClass('i-tag--size-small');
44 | });
45 |
46 | it('maxWidth', () => {
47 | const { container } = render(标签);
48 | expect(container.firstChild).toHaveStyle('max-width: 100px');
49 | });
50 | });
51 |
52 |
--------------------------------------------------------------------------------
/src/Radio/type.ts:
--------------------------------------------------------------------------------
1 | export interface RadioGroupProps {
2 | /**
3 | * 类名
4 | */
5 | className?: string;
6 | /**
7 | * 内容
8 | */
9 | children?: React.ReactNode;
10 | /**
11 | * 自定义样式
12 | */
13 | style?: React.CSSProperties;
14 | /**
15 | * 单选框组选中固定值(受控)
16 | */
17 | selected?: string | number;
18 | /**
19 | * 单选框组选中默认值(非受控)
20 | * @default 第一项 value
21 | */
22 | defaultSelected?: string | number;
23 | /**
24 | * 单选框组全局类型
25 | * @default radio
26 | */
27 | type?: 'radio' | 'radio-button';
28 | /**
29 | * 按钮单选框全局尺寸
30 | * @default medium
31 | */
32 | size?: 'small' | 'medium' | 'large';
33 | /**
34 | * 单选框组是否全局禁用
35 | */
36 | disabled?: boolean;
37 | /**
38 | * 选中某一项时触发
39 | */
40 | onChange?: (value: string | number, ev: React.ChangeEvent) => void;
41 | }
42 |
43 | export interface RadioProps {
44 | /**
45 | * 类名
46 | */
47 | className?: string;
48 | /**
49 | * 内容
50 | */
51 | children?: React.ReactNode;
52 | /**
53 | * 自定义样式
54 | */
55 | style?: React.CSSProperties;
56 | /**
57 | * 单选框类型
58 | * @default radio
59 | */
60 | type?: 'radio' | 'radio-button';
61 | /**
62 | * 按钮单选框尺寸
63 | * @default medium
64 | */
65 | size?: 'small' | 'medium' | 'large';
66 | /**
67 | * 是否固定选中(受控)
68 | */
69 | checked?: boolean;
70 | /**
71 | * 是否默认选中(非受控)
72 | * @default false
73 | */
74 | defaultChecked?: boolean;
75 | /**
76 | * 是否禁用组件
77 | * @default false
78 | */
79 | disabled?: boolean;
80 | /**
81 | * 单选框的值
82 | */
83 | value?: string | number;
84 | /**
85 | * 值变化时触发
86 | */
87 | onChange?: (checked: boolean, ev: React.ChangeEvent) => void;
88 | }
89 |
90 | export interface RadioContextValue {
91 | inject: (props: RadioProps) => RadioProps;
92 | }
93 |
--------------------------------------------------------------------------------
/src/Popover/type.ts:
--------------------------------------------------------------------------------
1 | export type placementType =
2 | | 'top'
3 | | 'top-left'
4 | | 'top-right'
5 | | 'bottom'
6 | | 'bottom-left'
7 | | 'bottom-right'
8 | | 'left'
9 | | 'left-top'
10 | | 'left-bottom'
11 | | 'right'
12 | | 'right-top'
13 | | 'right-bottom';
14 |
15 | export interface PopoverProps {
16 | /**
17 | * 包裹层类名
18 | */
19 | className?: string;
20 | /**
21 | * 内容
22 | */
23 | children?: React.ReactNode;
24 | /**
25 | * 弹窗内容类名
26 | */
27 | portalClassName?: string;
28 | /**
29 | * 气泡样式
30 | */
31 | style?: React.CSSProperties;
32 | /**
33 | * 气泡提示内容
34 | */
35 | content?: React.ReactNode;
36 | /**
37 | * 气泡提示位置
38 | * @default top
39 | */
40 | placement?:
41 | | 'top'
42 | | 'left'
43 | | 'right'
44 | | 'bottom'
45 | | 'top-left'
46 | | 'top-right'
47 | | 'bottom-left'
48 | | 'bottom-right'
49 | | 'left-top'
50 | | 'left-bottom'
51 | | 'right-top'
52 | | 'right-bottom';
53 | /**
54 | * 触发气泡出现的方式
55 | * @default hover
56 | */
57 | trigger?: 'hover' | 'click' | 'context-menu';
58 | /**
59 | * 手动显示气泡
60 | */
61 | visible?: boolean;
62 | /**
63 | * 气泡默认显示
64 | * @default false
65 | */
66 | defaultVisible?: boolean;
67 | /**
68 | * 是否禁用气泡
69 | * @default false
70 | */
71 | disabled?: boolean;
72 | /**
73 | * 这个值变化时手动更新位置
74 | */
75 | updateLocation?: string | number | boolean;
76 | /**
77 | * 触发气泡操作时触发
78 | */
79 | onTrigger?: (visible: boolean) => void;
80 | }
81 |
82 | export interface PortalProps {
83 | className?: string;
84 | style?: React.CSSProperties;
85 | visible?: boolean;
86 | content?: React.ReactNode;
87 | placement?: placementType;
88 | top: number;
89 | left: number;
90 | width: number;
91 | height: number;
92 | getRef?: (ref: React.Ref) => void;
93 | }
94 |
--------------------------------------------------------------------------------
/src/Steps/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react';
2 | import classNames from 'classnames';
3 | import './index.scss';
4 | import StepsItem from './StepsItem';
5 | import { StepsContextValue, StepsProps } from './type';
6 |
7 | export const StepsContext = React.createContext(null as any);
8 |
9 | const Steps: React.FC & { Item: React.ElementType } = (props) => {
10 | const {
11 | children = '',
12 | className,
13 | style,
14 | current = 0,
15 | layout = 'horizontal',
16 | reverse,
17 | dot = false
18 | } = props;
19 |
20 | // 注入每一项的 context
21 | const context: StepsContextValue = {
22 | // 将 props 注入每一项子节点的方法
23 | inject: (stepsItemProps: StepsProps) => {
24 | return {
25 | current,
26 | ...stepsItemProps,
27 | };
28 | },
29 | };
30 |
31 | const stepItemList = useMemo(() => {
32 | const childrenList = React.Children.toArray(children);
33 | const childrenDisplayList = reverse ? childrenList.reverse() : childrenList;
34 |
35 | return childrenList.map((child: any, index: number) => {
36 | const stepIndex = reverse ? childrenDisplayList.length - index - 1 : index;
37 | return React.cloneElement(child, {
38 | ...child.props,
39 | index: stepIndex,
40 | });
41 | });
42 | }, [children, reverse]);
43 |
44 | return (
45 |
46 |
55 | {stepItemList}
56 |
57 |
58 | );
59 | };
60 |
61 | Steps.Item = StepsItem
62 |
63 | StepsItem.displayName = 'StepsItem';
64 | Steps.displayName = 'Steps';
65 |
66 | export default Steps;
67 |
--------------------------------------------------------------------------------
/src/Alert/__test__/__snapshots__/index.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Alert 组件测试 type 1`] = `
4 |
5 |
8 |
12 |
15 |
18 | Alert 内容
19 |
20 |
21 |
22 |
23 | `;
24 |
25 | exports[`Alert 组件测试 type 2`] = `
26 |
27 |
30 |
34 |
37 |
40 | Alert 内容
41 |
42 |
43 |
44 |
45 | `;
46 |
47 | exports[`Alert 组件测试 type 3`] = `
48 |
49 |
52 |
56 |
59 |
62 | Alert 内容
63 |
64 |
65 |
66 |
67 | `;
68 |
69 | exports[`Alert 组件测试 type 4`] = `
70 |
71 |
74 |
78 |
81 |
84 | Alert 内容
85 |
86 |
87 |
88 |
89 | `;
90 |
--------------------------------------------------------------------------------
/docusaurus/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-library",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "docusaurus": "docusaurus",
7 | "start": "docusaurus start --hot-only --port 4396",
8 | "build": "docusaurus build",
9 | "swizzle": "docusaurus swizzle",
10 | "deploy": "docusaurus deploy",
11 | "clear": "docusaurus clear",
12 | "serve": "docusaurus serve",
13 | "write-translations": "docusaurus write-translations",
14 | "write-heading-ids": "docusaurus write-heading-ids"
15 | },
16 | "dependencies": {
17 | "@arco-design/web-react": "^2.61.1",
18 | "@docusaurus/core": "3.1.1",
19 | "@docusaurus/preset-classic": "3.1.1",
20 | "@docusaurus/theme-live-codeblock": "^3.1.1",
21 | "@mdx-js/react": "^3.0.0",
22 | "clsx": "^2.0.0",
23 | "docusaurus-plugin-image-zoom": "^2.0.0",
24 | "docusaurus-plugin-sass": "^0.2.5",
25 | "idesign-react": "workspace:*",
26 | "prism-react-renderer": "^2.3.1",
27 | "react": "^18.0.0",
28 | "react-dom": "^18.0.0",
29 | "react-live": "^4.1.6",
30 | "sass": "^1.72.0"
31 | },
32 | "devDependencies": {
33 | "@ant-design/icons": "^5.3.5",
34 | "@docusaurus/module-type-aliases": "3.1.1",
35 | "@docusaurus/types": "3.1.1",
36 | "@types/lodash": "^4.17.13",
37 | "antd": "^5.15.4",
38 | "classnames": "^2.5.1",
39 | "lodash": "^4.17.21"
40 | },
41 | "browserslist": {
42 | "production": [
43 | ">0.5%",
44 | "not dead",
45 | "not op_mini all"
46 | ],
47 | "development": [
48 | "last 3 chrome version",
49 | "last 3 firefox version",
50 | "last 5 safari version"
51 | ]
52 | },
53 | "engines": {
54 | "node": ">=18.0"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Table/index.scss:
--------------------------------------------------------------------------------
1 | .i-table {
2 | width: 100%;
3 | color: var(--i-font-2);
4 | font-size: 14px;
5 | }
6 |
7 | .i-table-thead__wrapper,
8 | .i-table-tbody__wrapper {
9 | display: table;
10 | width: 100%;
11 | min-width: 100%;
12 | margin: 0;
13 | table-layout: fixed;
14 | border-collapse: separate; // 合并相邻单元格边框
15 | border-spacing: 0;
16 | }
17 |
18 | .i-table-tbody__box {
19 | box-sizing: border-box;
20 | overflow: auto;
21 | overflow: overlay;
22 | border: 1px solid var(--i-border);
23 | border-radius: 0 0 8px 8px;
24 | &::-webkit-scrollbar-button {
25 | display: none;
26 | }
27 | }
28 |
29 | .i-table-thead {
30 | background: var(--i-bg-hover);
31 | .i-table-th {
32 | border-top: 1px solid var(--i-border);
33 | &:first-of-type {
34 | border-left: 1px solid var(--i-border);
35 | border-radius: 8px 0 0 0;
36 | }
37 | &:last-of-type {
38 | border-right: 1px solid var(--i-border);
39 | border-radius: 0 8px 0 0;
40 | }
41 | }
42 | }
43 |
44 | .i-table-th,
45 | .i-table-td {
46 | box-sizing: border-box;
47 | padding: 9px 16px;
48 | text-align: left;
49 | border: unset;
50 | &:first-of-type {
51 | border-left: none;
52 | }
53 | &:last-of-type {
54 | border-right: none;
55 | }
56 | }
57 |
58 | .i-table-td {
59 | border-bottom: 1px solid var(--i-border);
60 | }
61 |
62 | .i-table-tbody {
63 | overflow: auto;
64 | overflow: overlay;
65 | background: var(--i-bg);
66 | .i-table-tr {
67 | background: unset;
68 | &:last-of-type {
69 | .i-table-td {
70 | border-bottom: none;
71 | &:first-of-type {
72 | border-radius: 0 0 0 8px;
73 | }
74 | &:last-of-type {
75 | border-radius: 0 0 8px 0;
76 | }
77 | }
78 | }
79 | }
80 | &.i-table-tbody__stripe {
81 | .i-table-tr:nth-of-type(even) {
82 | background: var(--i-bg-hover);
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Radio/RadioGroup.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import classNames from 'classnames';
3 | import './index.scss';
4 | import { RadioContextValue, RadioGroupProps, RadioProps } from './type';
5 | import useDefault from '../hooks/useDefault';
6 |
7 | export const RadioContext = React.createContext(null as any);
8 |
9 | // 单选框组
10 | const RadioGroup: React.FC = (props) => {
11 | const initSelectedValue = () => {
12 | let result = props.defaultSelected
13 | if (!result) {
14 | React.Children.map(props.children, (child: any, index) => {
15 | index === 0 && (result = child?.props.value)
16 | })
17 | }
18 | return result
19 | }
20 | initSelectedValue()
21 | const {
22 | children = '',
23 | className,
24 | style,
25 | selected,
26 | defaultSelected = initSelectedValue(),
27 | type = 'radio',
28 | size,
29 | disabled,
30 | onChange,
31 | } = props;
32 |
33 | const [groupValue, setGroupValue] = useDefault(selected, defaultSelected, onChange);
34 |
35 | // 注入每一项单选框的 context
36 | const context: RadioContextValue = {
37 | // 将单选框组的 props 注入单项单选框的方法
38 | inject: (itemProps: RadioProps) => {
39 | // 拿到单项单选框的 value,方便与单选框组的 groupValue 做比较
40 | const itemVal = itemProps.value;
41 | return {
42 | ...itemProps,
43 | type,
44 | size,
45 | disabled,
46 | checked: groupValue === itemVal,
47 | onChange(checked, e) {
48 | e.persist();
49 | // 触发单选框组传入的 onChange
50 | itemVal && setGroupValue(itemVal, e)
51 | },
52 | };
53 | },
54 | };
55 |
56 | return (
57 |
58 |
59 | {children}
60 |
61 |
62 | );
63 | };
64 |
65 | export default RadioGroup;
66 |
--------------------------------------------------------------------------------
/docusaurus/docs/01-基础组件/Divider.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Divider 分割线
3 | ---
4 |
5 | 分割线是一个呈线状的轻量化组件,起到分割、组织、细化的作用,用于有逻辑的组织元素内容和页面结构。
6 |
7 | ## 基本用法
8 |
9 | 可通过 `dashed` 属性来控制分割线样式。
10 |
11 | ```tsx live
12 |
13 |
14 | 柯里化即Currying,是一门编译原理层面的技术,用途是实现多参函数,其为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受第一个参数的函数,并且返回接受剩余参数且返回结果的新函数。
15 |
16 |
17 |
18 | 柯里化即Currying,是一门编译原理层面的技术,用途是实现多参函数,其为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受第一个参数的函数,并且返回接受剩余参数且返回结果的新函数。
19 |
20 |
21 |
22 | 柯里化即Currying,是一门编译原理层面的技术,用途是实现多参函数,其为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受第一个参数的函数,并且返回接受剩余参数且返回结果的新函数。
23 |
24 |
25 | ```
26 |
27 | ## 带文字的分割线
28 |
29 | 可通过 `align` 属性来控制分割线文字内容的位置。
30 |
31 | ```tsx live
32 |
33 |
34 | 柯里化即Currying,是一门编译原理层面的技术,用途是实现多参函数,其为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受第一个参数的函数,并且返回接受剩余参数且返回结果的新函数。
35 |
36 |
iDesign
37 |
38 | 柯里化即Currying,是一门编译原理层面的技术,用途是实现多参函数,其为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受第一个参数的函数,并且返回接受剩余参数且返回结果的新函数。
39 |
40 |
iDesign
41 |
42 | 柯里化即Currying,是一门编译原理层面的技术,用途是实现多参函数,其为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受第一个参数的函数,并且返回接受剩余参数且返回结果的新函数。
43 |
44 |
iDesign
45 |
46 | 柯里化即Currying,是一门编译原理层面的技术,用途是实现多参函数,其为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受第一个参数的函数,并且返回接受剩余参数且返回结果的新函数。
47 |
48 |
49 | ```
50 |
51 | ## API
52 |
53 | | 属性 | 说明 | 类型 | 默认值 |
54 | | --------- | ---------- | --------------------------- | ---------- |
55 | | align | 文本位置 | `"left"\|"center"\|"right"` | `"center"` |
56 | | className | 类名 | `string` | `--` |
57 | | children | 内容 | `ReactNode` | `--` |
58 | | dashed | 是否为虚线 | `boolean` | `false` |
59 | | style | 自定义样式 | `CSSProperties` | `--` |
60 |
--------------------------------------------------------------------------------
/src/Steps/__test__/__snapshots__/index.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Steps 组件测试 create 1`] = `
4 |
5 |
8 |
11 |
14 |
17 | 步骤1
18 |
21 |
22 | 1
23 |
24 |
25 |
26 |
29 | 提示文字
30 |
31 |
32 |
33 |
36 |
39 |
42 | 步骤2
43 |
46 |
47 | 2
48 |
49 |
50 |
51 |
54 | 提示文字
55 |
56 |
57 |
58 |
61 |
64 |
67 | 步骤3
68 |
71 |
72 | 3
73 |
74 |
75 |
76 |
79 | 提示文字
80 |
81 |
82 |
83 |
84 |
85 | `;
86 |
--------------------------------------------------------------------------------
/src/Pagination/index.scss:
--------------------------------------------------------------------------------
1 | .i-pagination {
2 | display: flex;
3 | gap: 16px;
4 | align-items: center;
5 | }
6 |
7 | .i-pagination-select {
8 | width: 100px;
9 | }
10 |
11 | .i-pagination-control {
12 | display: flex;
13 | gap: 8px;
14 | align-items: center;
15 | }
16 |
17 | .i-pagination-btn {
18 | display: flex;
19 | align-items: center;
20 | justify-content: center;
21 | box-sizing: border-box;
22 | width: 32px;
23 | min-width: 32px;
24 | height: 32px;
25 | color: var(--i-font-2);
26 | font-size: 14px;
27 | border: 1px solid var(--i-border);
28 | border-radius: 4px;
29 | cursor: pointer;
30 | user-select: none;
31 | &:hover {
32 | color: var(--i-primary-txt);
33 | border-color: var(--i-primary-txt);
34 | }
35 | &.i-pagination-btn__handle {
36 | border: none;
37 | &:hover {
38 | background: var(--i-bg-hover);
39 | }
40 | }
41 | &.i-pagination-btn__active {
42 | color: var(--i-bg);
43 | background: var(--i-primary-txt);
44 | border-color: var(--i-primary-txt);
45 | &:hover {
46 | color: var(--i-bg);
47 | }
48 | }
49 | &.i-pagination-btn__disabled {
50 | color: var(--i-font-disabled);
51 | cursor: not-allowed;
52 | &:hover {
53 | color: var(--i-font-disabled);
54 | background: unset;
55 | border-color: var(--i-border);
56 | }
57 | &.i-pagination-btn__handle {
58 | border: none;
59 | }
60 | &.i-pagination-btn__active {
61 | color: var(--i-bg);
62 | background: var(--i-primary-disabled);
63 | border-color: var(--i-primary-disabled);
64 | }
65 | }
66 | }
67 |
68 | .i-pagination-btn__wrapper {
69 | display: flex;
70 | gap: 8px;
71 | align-items: center;
72 | width: 192px;
73 | overflow: hidden;
74 | &::-webkit-scrollbar {
75 | display: none;
76 | }
77 | }
78 |
79 | .i-pagination-input {
80 | display: flex;
81 | gap: 8px;
82 | align-items: center;
83 | color: var(--i-font-2);
84 | font-size: 14px;
85 | }
86 |
--------------------------------------------------------------------------------
/src/Slider/__test__/__snapshots__/index.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Slider 组件测试 create 1`] = `
4 |
5 |
25 |
26 | `;
27 |
28 | exports[`Slider 组件测试 range 1`] = `
29 |
30 |
62 |
63 | `;
64 |
65 | exports[`Slider 组件测试 vertical 1`] = `
66 |
67 |
87 |
88 | `;
89 |
--------------------------------------------------------------------------------
/src/TimePicker/index.scss:
--------------------------------------------------------------------------------
1 | .i-time-popup {
2 | & > .i-input {
3 | & > .i-input {
4 | width: 20px;
5 | padding: 0;
6 | border: none;
7 | .i-input__inner {
8 | padding-right: 0;
9 | text-align: center;
10 | }
11 | }
12 | & > .i-input__inner,
13 | .i-input-number-slider {
14 | display: none;
15 | }
16 | & > .i-input-suffix-icon {
17 | pointer-events: none;
18 | }
19 | }
20 | }
21 |
22 | .i-time-colon {
23 | margin-bottom: 2px;
24 | color: var(--i-font-2);
25 | &.i-time-colon__disabled {
26 | color: var(--i-font-disabled);
27 | }
28 | }
29 |
30 | .i-time-panel {
31 | width: 200px;
32 | }
33 |
34 | .i-time-panel-content {
35 | display: flex;
36 | align-items: center;
37 | height: 200px;
38 | }
39 |
40 | .i-time-panel-item {
41 | flex: 1;
42 | height: 100%;
43 | margin: 0;
44 | padding: 0;
45 | overflow: auto;
46 | overflow: overlay;
47 | &::-webkit-scrollbar {
48 | display: none;
49 | }
50 | &::after {
51 | display: list-item;
52 | width: 100%;
53 | height: 168px;
54 | content: '';
55 | }
56 | }
57 |
58 | .i-time-spinner-item {
59 | display: flex;
60 | align-items: center;
61 | justify-content: center;
62 | width: 100%;
63 | height: 32px;
64 | font-size: 12px;
65 | line-height: 32px;
66 | list-style: none;
67 | cursor: default;
68 | &:hover {
69 | background: var(--i-bg-hover);
70 | }
71 | &.i-time-spinner-item__selected {
72 | font-weight: bold;
73 | background: var(--i-bg-active);
74 | &:hover {
75 | background: var(--i-bg-active);
76 | }
77 | }
78 | }
79 |
80 | .i-time-panel-footer {
81 | display: flex;
82 | align-items: center;
83 | justify-content: space-between;
84 | box-sizing: border-box;
85 | width: 100%;
86 | padding: 12px;
87 | border-top: 1px solid var(--i-border);
88 | }
89 |
90 | .i-time-panel-footer__handle {
91 | display: flex;
92 | gap: 8px;
93 | align-items: center;
94 | margin-left: auto;
95 | }
96 |
--------------------------------------------------------------------------------
/src/Switch/__test__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, fireEvent } from '@testing-library/react';
2 | import Switch from '../index';
3 |
4 | describe('Switch 组件测试', () => {
5 | it('create', () => {
6 | const { asFragment } = render();
7 | expect(asFragment()).toMatchSnapshot();
8 | });
9 |
10 | it('value', () => {
11 | const { container } = render();
12 | expect(container.firstChild).toHaveClass('i-switch-is-checked');
13 | });
14 |
15 | it('defaultValue', () => {
16 | const { container } = render();
17 | expect(container.firstChild).toHaveClass('i-switch-is-checked');
18 | });
19 |
20 | it('onChange', () => {
21 | const changeFn = jest.fn();
22 | const { container } = render();
23 | fireEvent.click(container.firstChild);
24 | expect(changeFn).toHaveBeenCalled();
25 | });
26 |
27 | it('inactiveColor', () => {
28 | const { container } = render();
29 | expect(container.firstChild).toHaveStyle('background-color: blue');
30 | });
31 |
32 | it('activeColor', () => {
33 | const { container } = render();
34 | expect(container.firstChild).toHaveStyle('background-color: red');
35 | });
36 |
37 | it('size', () => {
38 | const { container } = render();
39 | expect(container.firstChild).toHaveClass('i-switch--size-small');
40 | });
41 |
42 | it('inactiveLabel', () => {
43 | const { getByText } = render();
44 | expect(getByText('关')).toBeInTheDocument();
45 | });
46 |
47 | it('activeLabel', () => {
48 | const { getByText } = render();
49 | expect(getByText('开')).toBeInTheDocument();
50 | });
51 |
52 | it('disabled', () => {
53 | const { container } = render();
54 | expect(container.firstChild).toHaveClass('i-switch-is-disabled');
55 | });
56 | });
57 |
58 |
--------------------------------------------------------------------------------
/src/Pagination/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav:
3 | title: 组件
4 | path: /components
5 | group:
6 | title: 导航组件
7 | order: 2
8 | order: 4
9 | ---
10 |
11 | # Pagination 分页
12 |
13 | 用于在当前页切换内容的分页组件。
14 |
15 | ## 基本用法
16 |
17 | ```tsx
18 | import React from 'react';
19 | import { Pagination } from 'idesign-react';
20 |
21 | const App = () => {
22 | const handleChange = (val) => {
23 | console.log(val)
24 | }
25 |
26 | return (
27 |
31 | );
32 | };
33 |
34 | export default App;
35 | ```
36 |
37 | ## 全局禁用
38 |
39 | 可通过 `disabled` 属性隐藏分页选择器:
40 |
41 | ```tsx
42 | import React from 'react';
43 | import { Pagination } from 'idesign-react';
44 |
45 | const App = () => {
46 | return (
47 |
51 | );
52 | };
53 |
54 | export default App;
55 | ```
56 |
57 | ## 隐藏分页选择器
58 |
59 | 可通过 `hideSelect` 属性隐藏分页选择器:
60 |
61 | ```tsx
62 | import React from 'react';
63 | import { Pagination } from 'idesign-react';
64 |
65 | const App = () => {
66 | return (
67 |
71 | );
72 | };
73 |
74 | export default App;
75 | ```
76 |
77 | ## 隐藏跳转输入框
78 |
79 | 可通过 `hideInput` 属性隐藏分页选择器:
80 |
81 | ```tsx
82 | import React from 'react';
83 | import { Pagination } from 'idesign-react';
84 |
85 | const App = () => {
86 | return (
87 |
91 | );
92 | };
93 |
94 | export default App;
95 | ```
96 |
97 |
98 |
--------------------------------------------------------------------------------
/src/Alert/index.scss:
--------------------------------------------------------------------------------
1 | .i-alert {
2 | position: relative;
3 | display: flex;
4 | align-items: center;
5 | box-sizing: border-box;
6 | width: 100%;
7 | margin: 0;
8 | padding: 12px 20px;
9 | overflow: hidden;
10 | border-radius: 4px;
11 | opacity: 1;
12 | transition: opacity 0.2s ease-out;
13 | }
14 |
15 | .i-alert--content {
16 | padding-right: 26px;
17 | }
18 |
19 | .i-alert--title {
20 | margin-bottom: 6px;
21 | font-weight: bold;
22 | font-size: 14px;
23 | }
24 |
25 | .i-alert--description {
26 | box-sizing: border-box;
27 | margin: 0;
28 | font-size: 14px;
29 | word-break: break-all;
30 | }
31 |
32 | .i-alert--operation {
33 | display: inline-block;
34 | box-sizing: border-box;
35 | margin-left: 8px;
36 | color: var(--i-primary-txt);
37 | cursor: pointer;
38 | user-select: none;
39 | &:hover {
40 | color: var(--i-primary-hover);
41 | }
42 | &:active {
43 | color: var(--i-primary-active);
44 | }
45 | }
46 |
47 | .i-alert--close-btn {
48 | position: absolute;
49 | right: 16px;
50 | display: flex;
51 | align-items: center;
52 | justify-content: center;
53 | padding: 4px;
54 | cursor: pointer;
55 | &:hover {
56 | .icon-Close {
57 | color: var(--i-font-3) !important;
58 | transition: color 0.2s ease-out;
59 | }
60 | }
61 | }
62 |
63 | @mixin injectColor($color, $color_background) {
64 | color: $color;
65 | background-color: $color_background;
66 | .i-icon {
67 | align-self: flex-start;
68 | margin-top: 2px;
69 | margin-right: 8px;
70 | color: $color !important;
71 | }
72 | .icon-Close {
73 | margin-right: 0;
74 | color: var(--i-font-5) !important;
75 | }
76 | }
77 |
78 | .i-alert--type-info {
79 | @include injectColor(var(--i-font-5), var(--i-bg-hover));
80 | }
81 |
82 | .i-alert--type-success {
83 | @include injectColor(var(--i-success), var(--i-success-bg));
84 | }
85 |
86 | .i-alert--type-warning {
87 | @include injectColor(var(--i-warning), var(--i-warning-bg));
88 | }
89 |
90 | .i-alert--type-error {
91 | @include injectColor(var(--i-error), var(--i-error-bg));
92 | }
93 |
--------------------------------------------------------------------------------
/src/Menu/type.ts:
--------------------------------------------------------------------------------
1 | export interface MenuProps {
2 | /**
3 | * 类名
4 | */
5 | className?: string;
6 | /**
7 | * 内容
8 | */
9 | children?: React.ReactNode;
10 | /**
11 | * 自定义样式
12 | */
13 | style?: React.CSSProperties;
14 | /**
15 | * 导航宽度
16 | */
17 | width?: React.CSSProperties['width'];
18 | /**
19 | * 固定选中值(受控)
20 | */
21 | active?: string | number;
22 | /**
23 | * 默认选中值(非受控)
24 | * @default 0
25 | */
26 | defaultActive?: string | number;
27 | /**
28 | * 前置内容
29 | */
30 | prefixContent?: React.ReactNode;
31 | /**
32 | * 后置内容
33 | */
34 | suffixContent?: React.ReactNode;
35 | /**
36 | * 导航方向
37 | * @default horizontal
38 | */
39 | direction?: 'horizontal' | 'vertical';
40 | /**
41 | * 点击菜单项时触发
42 | */
43 | onChange?: (value: string | number) => void;
44 | }
45 |
46 | interface MenuInject {
47 | /**
48 | * 索引值
49 | */
50 | index: number;
51 | /**
52 | * 透传选中值
53 | */
54 | active: string | number;
55 | /**
56 | * 透传导航方向
57 | * @default horizontal
58 | */
59 | direction: 'horizontal' | 'vertical';
60 | /**
61 | * 透传点击菜单项时触发
62 | */
63 | onChange: (value: string | number) => void;
64 | }
65 |
66 | export interface MenuItemProps extends MenuInject {
67 | /**
68 | * 类名
69 | */
70 | className?: string;
71 | /**
72 | * 内容
73 | */
74 | children?: React.ReactNode;
75 | /**
76 | * 自定义样式
77 | */
78 | style?: React.CSSProperties;
79 | /**
80 | * 单项唯一标识
81 | * @default 索引值
82 | */
83 | value?: string | number;
84 | /**
85 | * 点击单项时触发
86 | */
87 | onClick?: (value: string | number) => void;
88 | }
89 |
90 | export interface MenuGroupProps extends MenuInject {
91 | /**
92 | * 类名
93 | */
94 | className?: string;
95 | /**
96 | * 内容
97 | */
98 | children?: React.ReactNode;
99 | /**
100 | * 自定义样式
101 | */
102 | style?: React.CSSProperties;
103 | /**
104 | * 组标题
105 | */
106 | title?: React.ReactNode;
107 | }
108 |
--------------------------------------------------------------------------------
/src/Notification/Notification.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from 'react';
2 | import './index.scss';
3 | import { NotificationMethod, NotificationProps, PositionType } from './type';
4 | import Icon from '../Icon';
5 | import classNames from 'classnames';
6 |
7 | const Notification: React.FC & {
8 | success?: NotificationMethod;
9 | warning?: NotificationMethod;
10 | error?: NotificationMethod;
11 | info?: NotificationMethod;
12 | clear?: (position?: PositionType) => void;
13 | } = (props) => {
14 | const {
15 | type = 'info',
16 | content,
17 | title,
18 | closeable = false,
19 | entered = false,
20 | onClose,
21 | ...restProps
22 | } = props;
23 |
24 | const ntfRef = useRef(null)
25 | const [ntfHeight, setNtfHeight] = useState(undefined)
26 | useEffect(() => {
27 | if (entered) {
28 | const { height } = (ntfRef.current as HTMLDivElement).getBoundingClientRect()
29 | setNtfHeight(height)
30 | }
31 | }, [entered])
32 |
33 | return (
34 |
35 |
46 |
54 | {title &&
{title}
}
55 |
{content}
56 |
57 | {closeable && (
58 |
onClose?.()}>
59 |
60 |
61 | )}
62 |
63 | );
64 | };
65 |
66 | Notification.displayName = 'Notification';
67 |
68 | export default Notification;
69 |
--------------------------------------------------------------------------------
/src/Divider/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav:
3 | title: 组件
4 | path: /components
5 | group:
6 | title: 基础组件
7 | order: 1
8 | order: 3
9 | ---
10 |
11 | # Divider 分割线
12 |
13 | 分割线是一个呈线状的轻量化组件,起到分割、组织、细化的作用,用于有逻辑的组织元素内容和页面结构。
14 |
15 | ## 基本用法
16 |
17 | 可通过 `dashed` 属性来控制分割线样式。
18 |
19 | ```tsx
20 | import React from 'react';
21 | import { Divider } from 'idesign-react';
22 |
23 | const App = () => {
24 | return (
25 | <>
26 |
27 | 柯里化即
28 | Currying,是一门编译原理层面的技术,用途是实现多参函数,其为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受第一个参数的函数,并且返回接受剩余参数且返回结果的新函数。
29 |
30 |
31 |
32 | 柯里化即
33 | Currying,是一门编译原理层面的技术,用途是实现多参函数,其为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受第一个参数的函数,并且返回接受剩余参数且返回结果的新函数。
34 |
35 |
36 |
37 | 柯里化即
38 | Currying,是一门编译原理层面的技术,用途是实现多参函数,其为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受第一个参数的函数,并且返回接受剩余参数且返回结果的新函数。
39 |
40 | >
41 | );
42 | };
43 |
44 | export default App;
45 | ```
46 |
47 | ## 带文字的分割线
48 |
49 | 可通过 `align` 属性来控制分割线文字内容的位置。
50 |
51 | ```tsx
52 | import React from 'react';
53 | import { Divider } from 'idesign-react';
54 |
55 | const App = () => {
56 | return (
57 | <>
58 |
59 | 柯里化即
60 | Currying,是一门编译原理层面的技术,用途是实现多参函数,其为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受第一个参数的函数,并且返回接受剩余参数且返回结果的新函数。
61 |
62 | iDesign
63 |
64 | 柯里化即
65 | Currying,是一门编译原理层面的技术,用途是实现多参函数,其为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受第一个参数的函数,并且返回接受剩余参数且返回结果的新函数。
66 |
67 | iDesign
68 |
69 | 柯里化即
70 | Currying,是一门编译原理层面的技术,用途是实现多参函数,其为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受第一个参数的函数,并且返回接受剩余参数且返回结果的新函数。
71 |
72 | iDesign
73 |
74 | 柯里化即
75 | Currying,是一门编译原理层面的技术,用途是实现多参函数,其为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受第一个参数的函数,并且返回接受剩余参数且返回结果的新函数。
76 |
77 | >
78 | );
79 | };
80 |
81 | export default App;
82 | ```
83 |
84 |
85 |
--------------------------------------------------------------------------------
/src/BackTop/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from 'react';
2 | import classNames from 'classnames';
3 | import _ from 'lodash';
4 | import './index.scss';
5 | import Button from '../Button'
6 | import Icon from '../Icon'
7 | import Transition from '../Transition';
8 | import { BackTopProps } from './type';
9 |
10 | const BackTop: React.FC = (props) => {
11 | const {
12 | className,
13 | children,
14 | style,
15 | target = () => window,
16 | visibleHeight = 0,
17 | smooth = true,
18 | onClick = () => { },
19 | onScroll = () => { },
20 | ...restProps
21 | } = props;
22 |
23 | const [visible, setVisible] = useState(visibleHeight > 0 ? false : true);
24 |
25 | // 获取滚动层节点 t
26 | const getTarget = (target: HTMLElement | Window): HTMLElement => {
27 | return target === window ? document.documentElement : (target as HTMLElement);
28 | };
29 |
30 | const handleScroll = _.throttle((e: Event) => {
31 | const top = (e.target as HTMLElement).scrollTop
32 | top >= visibleHeight ? setVisible(true) : setVisible(false)
33 | onScroll?.()
34 | }, 16)
35 |
36 | useEffect(() => {
37 | const t = getTarget(target?.())
38 | t?.addEventListener('scroll', handleScroll)
39 | }, [])
40 |
41 | const scrollToTop = () => {
42 | const t = getTarget(target?.())
43 | t?.scrollTo({
44 | top: 0,
45 | behavior: smooth ? 'smooth' : 'auto'
46 | });
47 | onClick?.();
48 | };
49 |
50 | return (
51 |
60 |
65 | {children || (
66 |
69 | )}
70 |
71 |
72 | );
73 | };
74 |
75 | BackTop.displayName = 'BackTop';
76 |
77 | export default BackTop;
78 |
--------------------------------------------------------------------------------