(false);
14 |
15 | const handleClick = () => {
16 | let ret;
17 | if (actionFn) {
18 | ret = actionFn();
19 | }
20 | if (!ret) {
21 | closeModal();
22 | }
23 | if (ret && ret.then) {
24 | setLoading(true);
25 | ret.then(
26 | () => {
27 | closeModal();
28 | setLoading(false);
29 | },
30 | (e: Error) => {
31 | // tslint:disable-next-line:no-console
32 | console.log(e);
33 | setLoading(false);
34 | },
35 | );
36 | }
37 | };
38 |
39 | return (
40 |
43 | );
44 | };
45 |
46 | export default ActionButton;
47 |
--------------------------------------------------------------------------------
/components/radio/__test__/__snapshots__/index.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Radio should render correctly 1`] = `
4 |
45 | `;
46 |
--------------------------------------------------------------------------------
/site/demo/portal/portal.md:
--------------------------------------------------------------------------------
1 | ## Portal
2 |
3 |
4 |
5 | ```jsx
6 | const Demo = () => {
7 | const [PurePortalVisible, setPurePortalVisible] = React.useState(false);
8 | const [PortalVisible, setPortalVisible] = React.useState(false);
9 |
10 | return (
11 |
12 |
{
14 | setPortalVisible(false);
15 | }}
16 | visible={PortalVisible}
17 | mask
18 | maskClosable
19 | closeOnClickOutside
20 | className="portal"
21 | style={{ background: 'rgba(0, 0, 0, 0.2)' }}
22 | >
23 |
24 |
33 | 这里是带mask的Portal动态插入body的内容,点击遮罩层关闭
34 |
35 |
36 |
37 |
40 |
41 | );
42 | };
43 | ReactDOM.render();
44 | ```
45 |
46 |
47 |
--------------------------------------------------------------------------------
/site/demo/modal/footer.md:
--------------------------------------------------------------------------------
1 | ## 自定义页脚
2 |
3 |
4 |
5 | ```jsx
6 | const BasicModal = () => {
7 | const [visible, setVisible] = React.useState(false);
8 |
9 | const handleCancel = e => {
10 | console.log('cancel', e);
11 | setVisible(false);
12 | };
13 | const handleOk = e => {
14 | console.log('ok', e);
15 | setVisible(false);
16 | };
17 |
18 | return (
19 |
20 |
28 |
34 | Return
35 | ,
36 | ,
39 | ]}
40 | >
41 | Some contents...
42 | Some contents...
43 | Some contents...
44 |
45 |
46 | );
47 | };
48 | ReactDOM.render();
49 | ```
50 |
51 |
52 |
--------------------------------------------------------------------------------
/components/layout/__test__/index.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { mount } from 'enzyme';
3 | import Layout from '..';
4 |
5 | const { Content, Sider } = Layout;
6 |
7 | describe('Layout', () => {
8 | it('detect the sider as children', () => {
9 | const wrapper = mount(
10 |
11 | Sider
12 | Content
13 | ,
14 | );
15 | expect(wrapper.find('.ty-layout').hasClass('ty-layout--hasSider')).toBe(true);
16 | });
17 |
18 | it('detect the sider inside the children', () => {
19 | const wrapper = mount(
20 |
21 |
22 | Sider
23 |
24 | Content
25 | ,
26 | );
27 | expect(wrapper.find('.ty-layout').hasClass('ty-layout--hasSider')).toBe(true);
28 | });
29 |
30 | it('should have 50% width of sidebar', async () => {
31 | const wrapper = mount(
32 |
33 |
34 | Sider
35 |
36 | Content
37 | ,
38 | );
39 | expect(
40 | wrapper
41 | .find('.ty-layout__sider')
42 | .at(0)
43 | .prop('style').width,
44 | ).toBe('50%');
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/components/index.tsx:
--------------------------------------------------------------------------------
1 | export { default as ConfigProvider } from './context/ConfigContext';
2 | export { default as Icon } from './icon';
3 | export { default as Button } from './button';
4 | export { default as Portal } from './portal';
5 | export { default as Modal } from './modal';
6 | export { default as Layout } from './layout';
7 | export { default as Row } from './row';
8 | export { default as Col } from './col';
9 | export { default as AppBar } from './appBar';
10 | export { default as Drawer } from './drawer';
11 | export { default as Input } from './input';
12 | export { default as Radio } from './radio';
13 | export { default as Checkbox } from './checkbox';
14 | export { default as message } from './message';
15 | export { default as Switch } from './switch';
16 |
17 | /* @remove-on-es-build-begin */
18 | // this file is not used if use https://github.com/ant-design/babel-plugin-import
19 | const ENV = process.env.NODE_ENV;
20 | if (
21 | ENV !== 'production' &&
22 | ENV !== 'test' &&
23 | typeof console !== 'undefined' &&
24 | console.warn &&
25 | typeof window !== 'undefined'
26 | ) {
27 | console.warn(
28 | 'You are using a whole package of tyche-ui, ' +
29 | 'please use https://www.npmjs.com/package/babel-plugin-import to reduce app bundle size.',
30 | );
31 | }
32 | /* @remove-on-es-build-end */
33 |
--------------------------------------------------------------------------------
/site/components/AppBar.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { AppBar, Icon, Button, Drawer } from 'components';
3 | import SiderNav from './SiderNav';
4 |
5 | const { IconButton, Typography, ToolBar } = AppBar;
6 |
7 | export default () => {
8 | const [drawerVisible, setDrawerVisible] = useState(false);
9 | const handleIconButtonClick = () => {
10 | setDrawerVisible(true);
11 | };
12 |
13 | const closeDrawer = () => {
14 | setDrawerVisible(false);
15 | };
16 |
17 | return (
18 |
19 |
20 |
21 |
22 |
23 |
24 | Tyche UI
25 |
26 |
27 |
30 |
31 |
32 |
33 |
34 |
35 | );
36 | };
37 |
--------------------------------------------------------------------------------
/components/icon/svgs/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/drawer/style/index.scss:
--------------------------------------------------------------------------------
1 | @import '../../theme/mixins/mixins.scss';
2 | @import '../../theme/default.scss';
3 |
4 | @include b(drawer) {
5 | position: fixed;
6 | top: 0;
7 | z-index: 201;
8 | @include e(mask) {
9 | position: fixed;
10 | top: 0;
11 | right: 0;
12 | bottom: 0;
13 | left: 0;
14 | height: 100%;
15 | background-color: $--color-black;
16 | opacity: 0;
17 | touch-action: none;
18 | &-appear-active {
19 | opacity: 1;
20 | transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
21 | }
22 | &-appear-done {
23 | opacity: 1;
24 | }
25 | &-exit {
26 | opacity: 0;
27 | transition: opacity 195ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
28 | }
29 | }
30 | @include e(sider) {
31 | position: fixed;
32 | height: 100%;
33 | overflow: auto;
34 | background-color: $--color-white;
35 | box-shadow: 0 8px 10px -5px rgba(0, 0, 0, 0.2), 0 16px 24px 2px rgba(0, 0, 0, 0.14),
36 | 0 6px 30px 5px rgba(0, 0, 0, 0.12);
37 | -webkit-overflow-scrolling: touch;
38 | @include m(left) {
39 | left: 0;
40 | }
41 | @include m(right) {
42 | right: 0;
43 | }
44 | @include m(top) {
45 | top: 0;
46 | right: 0;
47 | left: 0;
48 | }
49 | @include m(bottom) {
50 | right: 0;
51 | bottom: 0;
52 | left: 0;
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/components/appBar/AppBar.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef } from 'react';
2 | import clsx from 'clsx';
3 | import { tuple } from '../_util/type';
4 | import { usePrefixCls } from '../_util/hooks';
5 | import { IconButton, Typography, ToolBar } from './AppBarInner';
6 |
7 | const AppBarColors = tuple('primary', 'secondary', 'success', 'warning', 'danger');
8 | export type AppBarColor = (typeof AppBarColors)[number];
9 |
10 | export interface AppBarProps {
11 | className?: string;
12 | children?: React.ReactNode;
13 | color?: AppBarColor;
14 | style?: React.CSSProperties;
15 | }
16 |
17 | export interface AppBarComponent extends React.ForwardRefExoticComponent
{
18 | IconButton: typeof IconButton;
19 | Typography: typeof Typography;
20 | ToolBar: typeof ToolBar;
21 | }
22 |
23 | const AppBar = forwardRef((props: AppBarProps, ref: React.RefObject) => {
24 | const { color = 'default', className, children, ...rest } = props;
25 | const prefixCls = usePrefixCls('appbar');
26 |
27 | const classes = clsx(prefixCls, className, {
28 | [`${prefixCls}--${color}`]: color,
29 | });
30 |
31 | return (
32 |
33 | {children}
34 |
35 | );
36 | }) as AppBarComponent;
37 |
38 | AppBar.IconButton = IconButton;
39 | AppBar.Typography = Typography;
40 | AppBar.ToolBar = ToolBar;
41 |
42 | export default AppBar;
43 |
--------------------------------------------------------------------------------
/components/modal/__test__/Modal.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { mount } from 'enzyme';
3 | import Modal from '..';
4 |
5 | describe('Modal', () => {
6 | it('should render correctly', () => {
7 | const wrapper = mount(
8 |
9 | Some contents...
10 | ,
11 | );
12 | expect(wrapper).toMatchSnapshot();
13 | });
14 |
15 | it('render without footer', () => {
16 | const wrapper = mount();
17 | expect(wrapper.find('.ty-modal__footer').length).toBeFalsy();
18 | });
19 |
20 | it('onCancel should be called', () => {
21 | const handleCancel = jest.fn();
22 | const wrapper = mount();
23 | wrapper
24 | .find('.ty-modal__close')
25 | .at(0)
26 | .simulate('click');
27 | expect(handleCancel).toBeCalledTimes(1);
28 | wrapper
29 | .find('.ty-btn__text--default')
30 | .at(0)
31 | .simulate('click');
32 | expect(handleCancel).toBeCalledTimes(2);
33 | });
34 |
35 | it('onOk should be called', () => {
36 | const handleOk = jest.fn();
37 | const wrapper = mount();
38 | wrapper
39 | .find('.ty-btn__text--primary')
40 | .at(0)
41 | .simulate('click');
42 | expect(handleOk).toHaveBeenCalled();
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/components/radio/RadioGroup.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef, ReactNode, useCallback } from 'react';
2 | import eq from 'lodash-es/eq';
3 | import RadioContext from './RadioContext';
4 | import { RadioEvent, RadioColor } from './Radio';
5 |
6 | const { Provider } = RadioContext;
7 |
8 | export interface RadioGroupProps {
9 | color?: RadioColor;
10 | children?: ReactNode;
11 | disabled?: boolean;
12 | isValueEqual?: (value1: unknown, value2: unknown) => boolean;
13 | onChange: (e: RadioEvent) => void;
14 | value: unknown;
15 | }
16 |
17 | const RadioGroup = forwardRef((props: RadioGroupProps, ref: React.RefObject) => {
18 | const {
19 | value,
20 | children,
21 | isValueEqual = eq,
22 | onChange,
23 | color = 'primary',
24 | disabled = false,
25 | } = props;
26 |
27 | const getContextValue = useCallback(
28 | (
29 | value: unknown,
30 | isValueEqual: (a: unknown, b: unknown) => boolean,
31 | color: RadioColor,
32 | disabled: boolean,
33 | ) => ({
34 | value,
35 | isValueEqual,
36 | color,
37 | disabled,
38 | onRadioChange: onChange,
39 | hasRadioGroup: true,
40 | }),
41 | [],
42 | );
43 |
44 | return (
45 |
46 | {children}
47 |
48 | );
49 | });
50 |
51 | export default RadioGroup;
52 |
--------------------------------------------------------------------------------
/site/demo/grid/justify.md:
--------------------------------------------------------------------------------
1 | ## Flex 布局
2 |
3 | 给`Row`设置`type="flex"`可以使用`flex`布局,使用`justify`可以给 Row 的子元素定义不同的排版方式。
4 |
5 |
6 |
7 | ```jsx
8 | const grid = (
9 |
10 |
start
11 |
12 | col-4
13 | col-4
14 | col-4
15 | col-4
16 |
17 |
18 |
center
19 |
20 | col-4
21 | col-4
22 | col-4
23 | col-4
24 |
25 |
26 |
right
27 |
28 | col-4
29 | col-4
30 | col-4
31 | col-4
32 |
33 |
34 |
space-between
35 |
36 | col-4
37 | col-4
38 | col-4
39 | col-4
40 |
41 |
42 |
space-around
43 |
44 | col-4
45 | col-4
46 | col-4
47 | col-4
48 |
49 |
50 | );
51 | ReactDOM.render(grid);
52 | ```
53 |
54 |
55 |
--------------------------------------------------------------------------------
/components/button-base/style/index.scss:
--------------------------------------------------------------------------------
1 | @import '../../theme/mixins/mixins.scss';
2 |
3 | @include b(ripple) {
4 | position: relative;
5 | user-select: none;
6 | @include e(wrapper) {
7 | position: absolute;
8 | top: 0;
9 | left: 0;
10 | z-index: 0;
11 | width: 100%;
12 | height: 100%;
13 | overflow: hidden;
14 | border-radius: inherit;
15 | }
16 | @include e(element) {
17 | position: absolute;
18 | top: 0;
19 | left: 0;
20 | border-radius: 50%;
21 | opacity: 0;
22 | pointer-events: none;
23 | @include when(visible) {
24 | transform: scale(1);
25 | opacity: 0.36;
26 | animation: ripple-element-enter 550ms cubic-bezier(0.4, 0, 0.2, 1);
27 | }
28 | @include m(child) {
29 | display: block;
30 | width: 100%;
31 | height: 100%;
32 | background-color: currentColor;
33 | border-radius: 50%;
34 | opacity: 1;
35 | @include when(leaving) {
36 | opacity: 0;
37 | animation: ripple-element-child-exit 550ms cubic-bezier(0.4, 0, 0.2, 1);
38 | }
39 | }
40 | }
41 | }
42 |
43 | @keyframes ripple-element-enter {
44 | 0% {
45 | transform: scale(0);
46 | opacity: 0.1;
47 | }
48 | 100% {
49 | transform: scale(1);
50 | opacity: 0.36;
51 | }
52 | }
53 |
54 | @keyframes ripple-element-child-exit {
55 | 0% {
56 | opacity: 1;
57 | }
58 | 100% {
59 | opacity: 0;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest/presets/js-with-babel',
3 | verbose: true,
4 | // collectCoverage: true,
5 | coverageDirectory: 'coverage',
6 | coverageReporters: ['text', 'lcov'],
7 | collectCoverageFrom: [
8 | 'components/**/*.{ts,tsx}',
9 | '!components/index.{ts,tsx}',
10 | '!components/*/style/index.tsx',
11 | '!components/style/index.tsx',
12 | '!components/*/locale/index.tsx',
13 | '!components/*/__tests__/**/type.tsx',
14 | '!components/**/*/interface.{ts,tsx}',
15 | ],
16 | transformIgnorePatterns: ['/node_modules/(?!(lodash-es|fecha)/)'],
17 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'md'],
18 | setupFilesAfterEnv: ['jest-extended', '@testing-library/react/cleanup-after-each'],
19 | setupFiles: ['./tests/setup.js'],
20 | globals: {
21 | 'ts-jest': {
22 | tsConfig: './tsconfig.json',
23 | },
24 | transform: {
25 | '^.+\\.(js|jsx|mjs)$': 'babel-jest',
26 | '^.+\\.(ts|tsx)$': 'ts-jest',
27 | },
28 | },
29 | moduleNameMapper: {
30 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
31 | '/tests/__mocks__/file-mock.js',
32 | '\\.(css|sass|scss)$': '/tests/__mocks__/object-mock.js',
33 | '^lodash-es$': 'lodash',
34 | },
35 | snapshotSerializers: ['enzyme-to-json/serializer'],
36 | testMatch: [`/components/**/__test__/**/*.test.(js|jsx|ts|tsx)`],
37 | };
38 |
--------------------------------------------------------------------------------
/site/pages/Layout.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Layout } from 'components/index';
3 |
4 | const { Header, Content, Footer, Sider } = Layout;
5 |
6 | export default () => {
7 | return (
8 |
9 | {/*
1
10 |
11 |
12 | This is Content
13 |
14 | */}
15 |
16 |
2
17 |
18 |
19 |
20 | {
24 | console.log('@onBreakpoint', broken);
25 | }}
26 | >
27 | Sider
28 |
29 | Content
30 |
31 |
32 |
33 |
34 | {/*
3
35 |
36 |
37 |
38 | Content
39 | Sider
40 |
41 |
42 |
43 |
44 |
4
45 |
46 | Sider
47 |
48 |
49 | Content
50 |
51 |
52 | */}
53 |
54 | );
55 | };
56 |
--------------------------------------------------------------------------------
/components/appBar/__test__/__snapshots__/index.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`AppBar should render correctly 1`] = `
4 |
7 |
10 |
37 |
38 |
41 | Title
42 |
43 |
46 |
59 |
60 |
61 | `;
62 |
--------------------------------------------------------------------------------
/site/pages/Portal.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Button, Portal } from 'components/index';
3 |
4 | export default () => {
5 | const [PurePortalVisible, setPurePortalVisible] = useState(false);
6 | const [PortalVisible, setPortalVisible] = useState(false);
7 | return (
8 |
9 |
10 | 这里是.demo-portal-conent的内容
11 |
12 | {PurePortalVisible && (
13 |
14 | 这是 PurePortal 动态插入.demo-portal-conent的内容
15 |
16 | )}
17 |
24 | {
26 | setPortalVisible(false);
27 | }}
28 | visible={PortalVisible}
29 | append
30 | maskClosable
31 | closeOnClickOutside
32 | className="tttppp"
33 | style={{ background: 'rgba(0, 0, 0, 0.2)' }}
34 | >
35 | 这里是带mask的Portal动态插入body的内容
36 |
37 |
40 |
41 | );
42 | };
43 |
--------------------------------------------------------------------------------
/site/demo/buttons/icon.md:
--------------------------------------------------------------------------------
1 | ### 图标
2 |
3 |
4 |
5 | ```js
6 | const instance = (
7 |
8 |
9 |
12 |
15 |
18 |
21 |
24 |
27 |
28 |
29 |
33 |
37 |
41 |
45 |
49 |
50 |
51 | );
52 | ReactDOM.render(instance);
53 | ```
54 |
55 |
56 |
--------------------------------------------------------------------------------
/site/demo/checkbox/checkAll.md:
--------------------------------------------------------------------------------
1 | ## 全选
2 |
3 | 使用受控组件实现全选效果。
4 |
5 |
6 |
7 | ```jsx
8 | const CheckboxGroup = Checkbox.Group;
9 |
10 | const plainOptions = ['Apple', 'Pear', 'Orange'];
11 | const defaultCheckedList = ['Apple', 'Orange'];
12 |
13 | const App = () => {
14 | const [checkedList, setCheckedList] = React.useState(defaultCheckedList);
15 | const [indeterminate, setIndeterminate] = React.useState(true);
16 | const [checkAll, setCheckAll] = React.useState(false);
17 |
18 | const onChange = checkedList => {
19 | setCheckedList(checkedList);
20 | setIndeterminate(!!checkedList.length && checkedList.length < plainOptions.length);
21 | setCheckAll(checkedList.length === plainOptions.length);
22 | };
23 |
24 | const onCheckAllChange = e => {
25 | setCheckedList(e.target.checked ? plainOptions : []);
26 | setIndeterminate(false);
27 | setCheckAll(e.target.checked);
28 | };
29 |
30 |
31 | return (
32 |
33 |
34 |
39 | Check all
40 |
41 |
42 |
43 |
48 |
49 | );
50 | };
51 |
52 | ReactDOM.render();
53 | ```
54 |
55 |
56 |
--------------------------------------------------------------------------------
/site/demo/modal/_props.md:
--------------------------------------------------------------------------------
1 | ## API
2 |
3 | #### Modal
4 | | 参数 | 说明 | 类型 | 默认值 |
5 | | --- | --- | --- | --- |
6 | | afterClose | Modal 完全关闭后的回调 | function | - |
7 | | bodyStyle | Modal body 样式 | object | - |
8 | | blockPageScroll | 弹出遮罩层后是否阻止浏览器滚动 | boolean | true |
9 | | cancelColor | 取消按钮颜色,可选值`primary`、 `secondary`、 `success`、 `warning`、 `danger` | string | - |
10 | | cancelText | 取消按钮文字 | string/ReactNode | 取消 |
11 | | cancelShape | 取消按钮形状,可选值为 `contained`、 `text` 、 `outlined` 、 `circle`、 `round`、 `icon` 或者不设 | string | Modal默认值`text`,Comfirm默认值`outlined` |
12 | | closable | 是否显示右上角的关闭按钮 | boolean | true |
13 | | content | 内容 | string/ReactNode | - |
14 | | onCancel | 点击遮罩层或右上角叉或取消按钮的回调 | function(e) | - |
15 | | okColor | 确定按钮颜色,可选值`primary`、 `secondary`、 `success`、 `warning`、 `danger` | string | primary |
16 | | destroy | `comfirm`销毁自身实例 | - | - |
17 | | destroyAll | 通过`Modal.destroyAll()`销毁所有`confirm`实例 | - | - |
18 | | onOk | 点击确定回调 | function(e) | - |
19 | | okShape | 确定按钮形状,可选值为 `contained`、 `text` 、 `outlined` 、 `circle`、 `round`、 `icon` 或者不设 | string | Modal默认值`text`,Comfirm默认值`outlined` |
20 | | okText | 确定按钮文字 | string/ReactNode | 确定 |
21 | | maskClosable | 点击遮罩时是否触发onCancel | boolean | Modal默认值`true`,Comfirm默认值`false` |
22 | | maskStyle | 遮罩样式 | object | {} |
23 | | style | 可用于设置浮层的样式,调整浮层位置等 | object | {} |
24 | | title | 标题 | string/ReactNode | - |
25 | | visible | 对话框是否可见 | boolean | - |
26 | | width | 宽度 | string/number | Modal默认值`520`Confirm默认值`416` |
27 | | zIndex | 设置`Modal`的`z-index` | number | 1000 |
28 |
--------------------------------------------------------------------------------
/components/button-base/Ripple.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Transition } from 'react-transition-group';
3 | import clsx from 'clsx';
4 | import { usePrefixCls } from '../_util/hooks';
5 | import { RippleProps } from './TouchRipple';
6 |
7 | const DURATION = 550;
8 |
9 | const Ripple = (props: RippleProps) => {
10 | const { rippleX, rippleY, rippleSize, ...restProps } = props;
11 | const [visible, setVisible] = useState(false);
12 | const [leaving, setLeaving] = useState(false);
13 |
14 | const handleEnter = () => {
15 | setVisible(true);
16 | };
17 | const handleExit = () => {
18 | setLeaving(true);
19 | };
20 |
21 | const prefixCls = usePrefixCls('ripple');
22 |
23 | const rippleClassName = clsx(`${prefixCls}__element`, {
24 | [`${prefixCls}_visible`]: visible,
25 | });
26 |
27 | const childClassName = clsx(`${prefixCls}__element--child`, {
28 | [`${prefixCls}_leaving`]: leaving,
29 | });
30 |
31 | return (
32 |
41 |
50 |
51 |
52 |
53 | );
54 | };
55 |
56 | export default Ripple;
57 |
--------------------------------------------------------------------------------
/components/button/Button.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import clsx from 'clsx';
3 | import { tuple } from '../_util/type';
4 | import { usePrefixCls } from '../_util/hooks';
5 | import ButtonBase, { ButtonProps as BaseButtonProps } from '../button-base';
6 |
7 | const ButtonShapes = tuple('contained', 'text', 'outlined', 'circle', 'round', 'icon');
8 | export type ButtonShape = (typeof ButtonShapes)[number];
9 | const ButtonColors = tuple('default', 'primary', 'secondary', 'success', 'warning', 'danger');
10 | export type ButtonColor = (typeof ButtonColors)[number];
11 | const ButtonSizes = tuple('large', 'default', 'small');
12 | export type ButtonSize = (typeof ButtonSizes)[number];
13 |
14 | export type ButtonProps = {
15 | shape?: ButtonShape;
16 | color?: ButtonColor;
17 | fab?: boolean;
18 | size?: ButtonSize;
19 | } & BaseButtonProps;
20 |
21 | const Button: React.FunctionComponent = props => {
22 | const {
23 | className,
24 | shape = 'contained',
25 | color = 'default',
26 | fab,
27 | size = 'default',
28 | children,
29 | ...rest
30 | } = props;
31 | const prefixCls = usePrefixCls('btn');
32 |
33 | const classes = clsx(prefixCls, className, {
34 | [`${prefixCls}__${shape}`]: shape,
35 | [`${prefixCls}__${shape}--${color}`]: true,
36 | [`${prefixCls}--fab`]: fab,
37 | [`${prefixCls}--${size}`]: size && size !== 'default',
38 | });
39 |
40 | return (
41 |
42 | {children}
43 |
44 | );
45 | };
46 |
47 | export default Button;
48 |
--------------------------------------------------------------------------------
/components/checkbox/__test__/Checkbox.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { mount } from 'enzyme';
3 | import Checkbox from '..';
4 |
5 | describe('Checkbox', () => {
6 | it('toggle checked', () => {
7 | const checkbox = mount(checkbox);
8 | expect(checkbox.find('.ty-checkbox--checked').length).toBe(0);
9 | checkbox.find('input').simulate('change');
10 | expect(checkbox.find('.ty-checkbox--checked').length).toBe(1);
11 | });
12 |
13 | it('indeterminate', () => {
14 | const checkbox = mount(checkbox);
15 | expect(checkbox.find('.ty-checkbox--primary').length).toBe(1);
16 | expect(checkbox.find('.ty-checkbox--checked').length).toBe(1);
17 | checkbox.find('input').simulate('change');
18 | expect(checkbox.find('.ty-checkbox--checked').length).toBe(1);
19 | });
20 |
21 | it('disabled', () => {
22 | const checkbox = mount(checkbox);
23 | expect(checkbox.find('.ty-checkbox--disabled').length).toBe(1);
24 | const indeterminateCheckbox = mount(
25 |
26 | checkbox
27 | ,
28 | );
29 | expect(indeterminateCheckbox.find('.ty-checkbox--checked').length).toBe(1);
30 | expect(indeterminateCheckbox.find('.ty-checkbox--disabled').length).toBe(1);
31 | });
32 |
33 | it('fire change events when value changes', () => {
34 | const handleChange = jest.fn();
35 | const checkbox = mount();
36 | checkbox.find('input').simulate('change');
37 | expect(handleChange).toHaveBeenCalled();
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/components/radio/style/index.scss:
--------------------------------------------------------------------------------
1 | @import '../../theme/mixins/mixins.scss';
2 | @import '../../theme/default.scss';
3 |
4 | @include b(radio) {
5 | display: inline-flex;
6 | align-items: center;
7 | cursor: pointer;
8 |
9 | @include e(icon) {
10 | position: relative;
11 | display: flex;
12 | justify-content: center;
13 | align-items: center;
14 | padding: 9px;
15 | color: $--color-black;
16 | %icon-svg {
17 | display: inline-block;
18 | width: 1em;
19 | height: 1em;
20 | font-size: 1.5em;
21 | fill: currentColor;
22 | }
23 | @include e(border) {
24 | @extend %icon-svg;
25 | }
26 | @include e(circle) {
27 | @extend %icon-svg;
28 | position: absolute;
29 | opacity: 0;
30 | // transform: scale(0);
31 | // transition: transform 150ms cubic-bezier(0.4, 0, 1, 1);
32 | }
33 | }
34 |
35 | @include e(input) {
36 | position: absolute;
37 | top: 0;
38 | left: 0;
39 | width: 100%;
40 | height: 100%;
41 | opacity: 0;
42 | }
43 |
44 | @include e(ripple) {
45 | position: absolute;
46 | z-index: 1;
47 | }
48 |
49 | @each $key, $color in $--colors {
50 | @include m($key) {
51 | @include e(icon) {
52 | color: $color;
53 | }
54 | }
55 | }
56 |
57 | @include m(checked) {
58 | @include e(circle) {
59 | opacity: 1;
60 | // transform: scale(1);
61 | // transition: transform 150ms cubic-bezier(0, 0, 0.2, 1);
62 | }
63 | }
64 |
65 | @include m(disabled) {
66 | color: $--color-disabled;
67 | cursor: not-allowed;
68 | @include e(icon) {
69 | color: inherit;
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/scripts/config/webpack.base.ts:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | const markdownRenderer = require('react-markdown-reader').renderer;
3 |
4 | const config: webpack.Configuration = {
5 | resolve: {
6 | extensions: ['.ts', '.tsx', '.js', '.jsx'],
7 | },
8 | module: {
9 | rules: [
10 | {
11 | test: /\.tsx?$/,
12 | loader: 'awesome-typescript-loader',
13 | },
14 | // {
15 | // test: /\.scss$/,
16 | // use: [
17 | // process.env.NODE_ENV !== 'production' ? 'style-loader' : MiniCssExtractPlugin.loader,
18 | // 'css-loader',
19 | // 'sass-loader',
20 | // ],
21 | // },
22 | {
23 | test: /\.svg$/,
24 | loader: 'svg-sprite-loader',
25 | },
26 | {
27 | test: /\.md$/,
28 | use: [
29 | {
30 | loader: 'html-loader',
31 | },
32 | {
33 | loader: 'markdown-loader',
34 | options: {
35 | pedantic: true,
36 | renderer: markdownRenderer([
37 | 'javascript',
38 | 'bash',
39 | 'xml',
40 | 'css',
41 | 'less',
42 | 'json',
43 | 'diff',
44 | 'typescript',
45 | ]),
46 | },
47 | },
48 | ],
49 | },
50 | {
51 | test: /\.(js|jsx)$/,
52 | loader: 'babel-loader',
53 | exclude: /node_modules/,
54 | query: {
55 | cacheDirectory: false,
56 | presets: ['@babel/preset-env', '@babel/preset-react'],
57 | },
58 | },
59 | ],
60 | },
61 | };
62 |
63 | export default config;
64 |
--------------------------------------------------------------------------------
/scripts/config/webpack.dist.ts:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import webpackMerge from 'webpack-merge';
3 | import MiniCssExtractPlugin from 'mini-css-extract-plugin';
4 | import base from './webpack.base';
5 | import { getProjectPath } from '../utils/projectHelper';
6 | const postcssConfig = require('./postcssConfig');
7 |
8 | const config: webpack.Configuration = {
9 | mode: 'production',
10 | entry: {
11 | index: [getProjectPath('index')],
12 | },
13 | output: {
14 | filename: 'tyche.js',
15 | path: getProjectPath('dist'),
16 | library: 'tyche-ui',
17 | libraryTarget: 'umd',
18 | globalObject: 'this',
19 | },
20 | externals: {
21 | react: {
22 | commonjs: 'react',
23 | commonjs2: 'react',
24 | amd: 'react',
25 | root: 'React',
26 | },
27 | 'react-dom': {
28 | commonjs: 'react-dom',
29 | commonjs2: 'react-dom',
30 | amd: 'react-dom',
31 | root: 'ReactDOM',
32 | },
33 | },
34 | module: {
35 | rules: [
36 | {
37 | test: /\.scss$/,
38 | use: [
39 | MiniCssExtractPlugin.loader,
40 | {
41 | loader: 'css-loader',
42 | options: {
43 | sourceMap: true,
44 | },
45 | },
46 | {
47 | loader: 'postcss-loader',
48 | options: Object.assign({}, postcssConfig, { sourceMap: true }),
49 | },
50 | 'sass-loader',
51 | ],
52 | },
53 | ],
54 | },
55 | plugins: [
56 | // new CleanWebpackPlugin(),
57 | new MiniCssExtractPlugin({
58 | filename: 'tyche.css',
59 | chunkFilename: '[id].css',
60 | }),
61 | ],
62 | };
63 |
64 | export default webpackMerge(base, config);
65 |
--------------------------------------------------------------------------------
/scripts/config/webpack.site.docs.ts:
--------------------------------------------------------------------------------
1 | import webpack from 'webpack';
2 | import webpackMerge from 'webpack-merge';
3 | import MiniCssExtractPlugin from 'mini-css-extract-plugin';
4 | import HtmlWebpackPlugin from 'html-webpack-plugin';
5 | import CleanWebpackPlugin from 'clean-webpack-plugin';
6 | import { getProjectPath } from '../utils/projectHelper';
7 | import base from './webpack.base';
8 | const postcssConfig = require('./postcssConfig');
9 |
10 | const config: webpack.Configuration = {
11 | mode: 'production',
12 | entry: {
13 | site: getProjectPath('site', 'index'),
14 | },
15 | output: {
16 | path: getProjectPath('docs'),
17 | filename: '[name].[hash].js',
18 | },
19 | resolve: {
20 | alias: {
21 | components: getProjectPath('components'),
22 | },
23 | },
24 | module: {
25 | rules: [
26 | {
27 | test: /\.scss$/,
28 | use: [
29 | MiniCssExtractPlugin.loader,
30 | {
31 | loader: 'css-loader',
32 | options: {
33 | sourceMap: true,
34 | },
35 | },
36 | {
37 | loader: 'postcss-loader',
38 | options: Object.assign({}, postcssConfig, { sourceMap: true }),
39 | },
40 | 'sass-loader',
41 | ],
42 | },
43 | ],
44 | },
45 | plugins: [
46 | new MiniCssExtractPlugin({
47 | filename: 'tyche.[hash].css',
48 | chunkFilename: '[id].css',
49 | }),
50 | new CleanWebpackPlugin(),
51 | new HtmlWebpackPlugin({
52 | title: 'tyche-ui',
53 | template: getProjectPath('site', 'index.html'),
54 | favicon: getProjectPath('site', 'tycheUI.png'),
55 | }),
56 | ],
57 | };
58 |
59 | export default webpackMerge(base, config);
60 |
--------------------------------------------------------------------------------
/components/modal/ModalWrapper.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CSSTransition } from 'react-transition-group';
3 | import { usePrefixCls } from '../_util/hooks';
4 | import { duration } from '../_util/transition';
5 |
6 | export interface ModalWrapperProps {
7 | visible?: boolean;
8 | mask?: boolean;
9 | maskClosable?: boolean;
10 | onCancel?: (e: React.MouseEvent) => void;
11 | zIndex?: number;
12 | maskStyle?: React.CSSProperties;
13 | }
14 |
15 | const ModalWrapper: React.FunctionComponent = props => {
16 | const {
17 | mask = true,
18 | maskClosable = true,
19 | onCancel,
20 | zIndex,
21 | visible,
22 | maskStyle,
23 | children,
24 | } = props;
25 | const prefixCls = usePrefixCls('modal');
26 |
27 | const handleMaskClick = (e: React.MouseEvent) => {
28 | if (e.target === e.currentTarget && mask && maskClosable) {
29 | if (onCancel) {
30 | onCancel(e);
31 | }
32 | }
33 | };
34 |
35 | const maskStyleObj = {
36 | ...maskStyle,
37 | zIndex,
38 | };
39 |
40 | return (
41 |
42 | {mask && (
43 |
52 |
53 |
54 | )}
55 |
56 | {children}
57 |
58 |
59 | );
60 | };
61 |
62 | export default ModalWrapper;
63 |
--------------------------------------------------------------------------------
/components/icon/svgs/code-close.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/components/grid/__test__/index.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, mount } from 'enzyme';
3 | import { Col, Row } from '..';
4 |
5 | jest.mock('enquire.js', () => {
6 | let that;
7 | let unmatchFun;
8 | return {
9 | unregister: jest.fn(),
10 | register: (media, options) => {
11 | if (media === '(max-width: 575px)') {
12 | that = this;
13 | options.match.call(that);
14 | unmatchFun = options.unmatch;
15 | }
16 | },
17 | callunmatch() {
18 | unmatchFun.call(that);
19 | },
20 | };
21 | });
22 |
23 | describe('Grid', () => {
24 | it('should render Col', () => {
25 | const wrapper = render();
26 | expect(wrapper).toMatchSnapshot();
27 | });
28 |
29 | it('should render Row', () => {
30 | const wrapper = render(
);
31 | expect(wrapper).toMatchSnapshot();
32 | });
33 |
34 | it('renders wrapped Col correctly', () => {
35 | const MyCol = () => ;
36 | const wrapper = render(
37 |
38 |
39 |
40 |
41 |
42 | ,
43 | );
44 | expect(wrapper).toMatchSnapshot();
45 | });
46 |
47 | it('should work correct when gutter is object', () => {
48 | const enquire = require('enquire.js');
49 | const wrapper = mount(
);
50 | expect(wrapper.find('div').prop('style')).toEqual({
51 | marginLeft: -10,
52 | marginRight: -10,
53 | });
54 | enquire.callunmatch();
55 | expect(
56 | wrapper
57 | .update()
58 | .find('div')
59 | .prop('style'),
60 | ).toEqual(undefined);
61 | wrapper.unmount();
62 | expect(enquire.unregister).toHaveBeenCalled();
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/components/message/Message.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CSSTransition } from 'react-transition-group';
3 | import clsx from 'clsx';
4 | import { tuple } from '../_util/type';
5 | import Portal from '../portal';
6 | import Icon from '../icon';
7 | import { usePrefixCls } from '../_util/hooks';
8 |
9 | const MessageColors = tuple('default', 'primary', 'secondary', 'success', 'warning', 'danger');
10 | export type MessageColor = (typeof MessageColors)[number];
11 |
12 | export interface MessageProps {
13 | iconType?: string;
14 | color?: MessageColor;
15 | content?: React.ReactNode | string;
16 | visible: boolean;
17 | selector: HTMLElement | string;
18 | afterClose(): void;
19 | }
20 |
21 | const Message: React.FunctionComponent = props => {
22 | const { color = 'default', iconType, selector, visible, afterClose, content } = props;
23 |
24 | const prefixCls = usePrefixCls('message');
25 | const contentClasses = clsx(`${prefixCls}__content`, {
26 | [`${prefixCls}--${color}`]: color,
27 | });
28 |
29 | const onExited = () => {
30 | afterClose();
31 | };
32 |
33 | return (
34 |
35 |
46 |
47 |
48 | {iconType && }
49 | {content}
50 |
51 |
52 |
53 |
54 | );
55 | };
56 |
57 | export default Message;
58 |
--------------------------------------------------------------------------------
/components/message/__test__/index.test.jsx:
--------------------------------------------------------------------------------
1 | import message from '..';
2 |
3 | describe('message', () => {
4 | beforeEach(() => {
5 | jest.useFakeTimers();
6 | });
7 |
8 | afterEach(() => {
9 | message.destroy();
10 | jest.useRealTimers();
11 | });
12 |
13 | it('render correctly', () => {
14 | message.success('success');
15 | expect(document.querySelectorAll('.ty-message__notice').length).toBe(1);
16 | });
17 |
18 | it('onClose should be called', () => {
19 | const onClose = jest.fn();
20 | message.success('success', 2, onClose);
21 | jest.runTimersToTime(1000);
22 | expect(onClose).not.toHaveBeenCalled();
23 | jest.runAllTimers();
24 | expect(onClose).toHaveBeenCalled();
25 | });
26 |
27 | it('should hide message correctly', () => {
28 | const hide = message.loading('success', 0);
29 | expect(document.querySelectorAll('.ty-message__notice').length).toBe(1);
30 | hide();
31 | jest.runAllTimers();
32 | expect(document.querySelectorAll('.ty-message__notice').length).toBe(0);
33 | });
34 |
35 | it('should be able to destroy globally', () => {
36 | message.info('whatever', 0);
37 | message.info('whatever', 0);
38 | expect(document.querySelectorAll('.ty-message').length).toBe(1);
39 | expect(document.querySelectorAll('.ty-message__notice').length).toBe(2);
40 | message.destroy();
41 | jest.runAllTimers();
42 | expect(document.querySelectorAll('.ty-message').length).toBe(0);
43 | expect(document.querySelectorAll('.ty-message__notice').length).toBe(0);
44 | });
45 |
46 | it('should not need to use duration argument when using the onClose arguments', () => {
47 | const onClose = jest.fn();
48 | message.info('whatever', onClose);
49 | message.destroy();
50 | jest.runAllTimers();
51 | expect(onClose).toHaveBeenCalled();
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/components/message/style/index.scss:
--------------------------------------------------------------------------------
1 | @import '../../theme/mixins/mixins.scss';
2 | @import '../../theme/default.scss';
3 |
4 | .ty-message {
5 | position: fixed;
6 | top: 16px;
7 | left: 0;
8 | z-index: 10000;
9 | width: 100%;
10 | text-align: center;
11 | pointer-events: none;
12 | }
13 |
14 | @include b(message) {
15 | @include e(notice) {
16 | padding: 8px 0;
17 | @include e(content) {
18 | display: inline-flex;
19 | align-items: center;
20 | box-sizing: border-box;
21 | min-width: 256px;
22 | max-width: 96%;
23 | min-height: 46px;
24 | padding: 6px 16px;
25 | border-radius: 2px;
26 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
27 | pointer-events: all;
28 | }
29 | }
30 |
31 | @include e(icon) {
32 | margin-right: 12px;
33 | font-size: 20px;
34 | }
35 |
36 | $--message--colors: (
37 | 'default': $--color-black-bg,
38 | 'primary': $--color-primary,
39 | 'success': $--color-success,
40 | 'warning': $--color-warning,
41 | 'danger': $--color-danger,
42 | );
43 | @each $key, $color in $--message--colors {
44 | @include m($key) {
45 | color: $--color-white;
46 | background-color: $color;
47 | }
48 | }
49 |
50 | &-appear {
51 | &-active {
52 | animation: notifyMoveIn 225ms cubic-bezier(0.4, 0, 0.2, 1);
53 | }
54 | }
55 | &-exit {
56 | &-active {
57 | animation: notifyMoveOut 195ms cubic-bezier(0.4, 0, 0.2, 1) both;
58 | }
59 | }
60 | }
61 |
62 | @keyframes notifyMoveIn {
63 | 0% {
64 | transform: translateY(-100%);
65 | opacity: 0;
66 | }
67 |
68 | 100% {
69 | transform: translateY(0%);
70 | opacity: 1;
71 | }
72 | }
73 |
74 | @keyframes notifyMoveOut {
75 | 0% {
76 | transform: translateY(0%);
77 | opacity: 1;
78 | }
79 |
80 | 100% {
81 | transform: translateY(-100%);
82 | opacity: 0;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/site/pages/Drawer.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Button, Drawer } from 'components';
3 |
4 | export default () => {
5 | const [leftVisible, setLeftVisible] = useState(false);
6 | const [rightVisible, setRightVisible] = useState(false);
7 | const [topVisible, setTopVisible] = useState(false);
8 | const [bottomVisible, setBottomVisible] = useState(false);
9 |
10 | return (
11 |
12 |
20 | {
23 | setLeftVisible(false);
24 | }}
25 | >sider
26 |
27 |
35 | {
39 | setRightVisible(false);
40 | }}
41 | />
42 |
43 |
51 | {
55 | setTopVisible(false);
56 | }}
57 | />
58 |
59 |
67 | {
71 | setBottomVisible(false);
72 | }}
73 | />
74 |
75 | );
76 | };
77 |
--------------------------------------------------------------------------------
/components/radio/__test__/index.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, mount } from 'enzyme';
3 | import Radio from '..';
4 |
5 | describe('Radio', () => {
6 | it('should render correctly', () => {
7 | const wrapper = render(Test);
8 | expect(wrapper).toMatchSnapshot();
9 | });
10 |
11 | it('fire change events when value changes', () => {
12 | const handleChange = jest.fn();
13 | const wrapper = mount(
14 |
15 | Test
16 | ,
17 | );
18 | wrapper.find('input').simulate('change');
19 | expect(handleChange).toHaveBeenCalled();
20 | });
21 | });
22 |
23 | describe('Radio Group', () => {
24 | it('Group will liftup the change event of input and props in Radio', () => {
25 | const handleChange = jest.fn();
26 | const val = 1;
27 | const wrapper = mount(
28 |
29 | A
30 | B
31 | B
32 | ,
33 | );
34 | handleChange.mockImplementation(evt => {
35 | expect(typeof evt.stopPropagation).toBe('function');
36 | expect(typeof evt.preventDefault).toBe('function');
37 | evt.stopPropagation();
38 | evt.preventDefault();
39 |
40 | wrapper.setProps({ value: evt.target.value });
41 | });
42 |
43 | expect(
44 | wrapper
45 | .find(Radio)
46 | .at(0)
47 | .children()
48 | .hasClass('ty-radio--checked'),
49 | ).toBe(true);
50 |
51 | wrapper
52 | .find('input')
53 | .at(1)
54 | .simulate('change');
55 | expect(handleChange).toHaveBeenCalled();
56 |
57 | expect(
58 | wrapper
59 | .find(Radio)
60 | .at(1)
61 | .children()
62 | .hasClass('ty-radio--checked'),
63 | ).toBe(true);
64 | });
65 | });
66 |
--------------------------------------------------------------------------------
/components/icon/svgs/code-open.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/components/grid/style/col.scss:
--------------------------------------------------------------------------------
1 | @import '../../theme/mixins/mixins.scss';
2 | @import '../../theme/default.scss';
3 |
4 | [class*='#{$namespace}-col-'] {
5 | float: left;
6 | box-sizing: border-box;
7 | }
8 |
9 | @for $i from 0 through 24 {
10 | .#{$namespace}-col-#{$i} {
11 | width: 100% / 24 * $i;
12 | }
13 |
14 | .#{$namespace}-col-offset-#{$i} {
15 | margin-left: 100% / 24 * $i;
16 | }
17 |
18 | .#{$namespace}-col-push-#{$i} {
19 | position: relative;
20 | left: 100% / 24 * $i;
21 | }
22 |
23 | .#{$namespace}-col-pull-#{$i} {
24 | position: relative;
25 | right: 100% / 24 * $i;
26 | }
27 |
28 | .#{$namespace}-col-order-#{$i} {
29 | order: $i;
30 | }
31 | }
32 |
33 | @mixin handleMedia($key, $map: $--breakpoints) {
34 | // 循环断点Map,如果存在则返回
35 | @if map-has-key($map, $key) {
36 | $media: #{inspect(map-get($map, $key))};
37 | @media #{$media} {
38 | @content;
39 | }
40 | } @else {
41 | @warn "Undefeined points: `#{$map}`";
42 | }
43 | }
44 |
45 | @mixin generateAttributes($breakpoint) {
46 | .#{$namespace}-col-#{$breakpoint}-0 {
47 | display: none;
48 | }
49 | @for $i from 0 through 24 {
50 | .#{$namespace}-col-#{$breakpoint}-#{$i} {
51 | width: 100% / 24 * $i;
52 | }
53 |
54 | .#{$namespace}-col-#{$breakpoint}-offset-#{$i} {
55 | margin-left: 100% / 24 * $i;
56 | }
57 |
58 | .#{$namespace}-col-#{$breakpoint}-pull-#{$i} {
59 | position: relative;
60 | right: 100% / 24 * $i;
61 | }
62 |
63 | .#{$namespace}-col-#{$breakpoint}-push-#{$i} {
64 | position: relative;
65 | left: 100% / 24 * $i;
66 | }
67 | .#{$namespace}-col-#{$breakpoint}-order-#{$i} {
68 | order: $i;
69 | }
70 | }
71 | }
72 |
73 | $breakpoints: ('sm', 'md', 'lg', 'xl', 'xxl');
74 |
75 | // xs不添加@media
76 | @include generateAttributes(xs);
77 |
78 | @each $breakpoint in $breakpoints {
79 | @include handleMedia($breakpoint) {
80 | @include generateAttributes($breakpoint);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/site/pages/Grid.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Row, Col } from 'components/index';
3 |
4 | export default () => {
5 | return (
6 |
7 |
8 |
9 | col-6
10 |
11 |
12 | col-6
13 |
14 |
15 | col-6
16 |
17 |
18 | col-6
19 |
20 |
21 |
22 |
23 | col-6
24 |
25 |
26 | col-6
27 |
28 |
29 |
30 |
31 | col-6
32 |
33 |
34 | col-6
35 |
36 |
37 |
38 |
39 | col-6
40 |
41 |
42 | col-6
43 |
44 |
45 |
46 |
47 | col-6
48 |
49 |
50 | col-6
51 |
52 |
53 |
54 |
55 | col-6
56 |
57 |
58 | col-6
59 |
60 |
61 |
62 | );
63 | };
64 |
--------------------------------------------------------------------------------
/components/checkbox/style/index.scss:
--------------------------------------------------------------------------------
1 | @import '../../theme/mixins/mixins.scss';
2 | @import '../../theme/default.scss';
3 |
4 | @include b(checkbox) {
5 | display: inline-flex;
6 | align-items: center;
7 | margin-right: 6px;
8 | cursor: pointer;
9 |
10 | @include e(group) {
11 | display: inline-block;
12 | }
13 |
14 | @include m(checked) {
15 | @include e(border) {
16 | opacity: 0;
17 | }
18 | @include e(inner) {
19 | opacity: 1;
20 | // transform: scale(1);
21 | // transition: transform 225ms cubic-bezier(0, 0, 0.2, 1);
22 | }
23 | }
24 |
25 | @include m(disabled) {
26 | color: $--color-disabled;
27 | cursor: not-allowed;
28 | @include e(icon) {
29 | color: inherit;
30 | }
31 | @include e(border) {
32 | color: inherit;
33 | }
34 | }
35 |
36 | @each $key, $color in $--colors {
37 | @include m($key) {
38 | @include e(icon) {
39 | color: $color;
40 | }
41 | }
42 | }
43 |
44 | @include e(icon) {
45 | position: relative;
46 | padding: 8px;
47 |
48 | %icon-svg {
49 | display: inline-block;
50 | width: 1em;
51 | height: 1em;
52 | font-size: 1.5em;
53 | vertical-align: middle;
54 | fill: currentColor;
55 | }
56 |
57 | @include e(border) {
58 | @extend %icon-svg;
59 | color: $--color-black;
60 | opacity: 1;
61 | // transition: opacity 225ms cubic-bezier(0, 0, 0.2, 1);
62 | }
63 | @include e(inner) {
64 | position: absolute;
65 | top: 8px;
66 | left: 8px;
67 | opacity: 0;
68 | // transform: scale(0);
69 |
70 | @extend %icon-svg;
71 | }
72 |
73 | @include e(ripple) {
74 | position: absolute;
75 | top: 0;
76 | left: 0;
77 | width: 100%;
78 | height: 100%;
79 | }
80 |
81 | @include e(input) {
82 | position: absolute;
83 | top: 0;
84 | left: 0;
85 | width: 100%;
86 | height: 100%;
87 | opacity: 0;
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/components/button/__test__/index.test.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Button from '..';
3 | import { render, mount } from 'enzyme';
4 |
5 | describe('Button', () => {
6 | it('render correctly', () => {
7 | const wrapper = render();
8 | expect(wrapper).toMatchSnapshot();
9 |
10 | // size
11 | const wrapper1 = render();
12 | expect(wrapper1).toMatchSnapshot();
13 |
14 | // shape
15 | const wrapper2 = render();
16 | expect(wrapper2).toMatchSnapshot();
17 |
18 | // color
19 | const wrapper3 = render();
20 | expect(wrapper3).toMatchSnapshot();
21 | });
22 |
23 | it('should support link button', () => {
24 | const wrapper = mount(
25 | ,
28 | );
29 | expect(wrapper.render()).toMatchSnapshot();
30 | });
31 |
32 | it('should support ripple attribute', async () => {
33 | const wrapper = mount();
34 | expect(wrapper.render()).toMatchSnapshot();
35 | });
36 |
37 | it('should support loading', () => {
38 | const LoadingButton = () => {
39 | const [loading, setLoading] = useState(false);
40 | const toggleLoading = () => {
41 | setLoading(!loading);
42 | };
43 | return (
44 |
47 | );
48 | };
49 | const wrapper = mount();
50 | expect(wrapper.find('.ty-btn__loading').length).toBe(0);
51 | wrapper.simulate('click');
52 | expect(wrapper.find('.ty-btn__loading').length).toBe(1);
53 | });
54 |
55 | it('should not render as link button when href is undefined', () => {
56 | const wrapper = mount();
57 | expect(wrapper.render()).toMatchSnapshot();
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/components/button-base/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import clsx from 'clsx';
3 | import { usePrefixCls } from '../_util/hooks';
4 | import { Omit, tuple } from '../_util/type';
5 | import TouchRipple from './TouchRipple';
6 | import Loading from './Loading';
7 |
8 | const ButtonHTMLTypes = tuple('submit', 'button', 'reset');
9 | export type ButtonHTMLType = (typeof ButtonHTMLTypes)[number];
10 |
11 | export interface BaseButtonProps extends React.DOMAttributes {
12 | children?: React.ReactNode;
13 | component?: string;
14 | className?: string;
15 | loading?: boolean;
16 | disabled?: boolean;
17 | ripple?: boolean;
18 | center?: boolean;
19 | }
20 |
21 | export type AnchorButtonProps = {
22 | href?: string;
23 | target?: string;
24 | } & BaseButtonProps &
25 | Omit, 'type'>;
26 |
27 | export type NativeButtonProps = {
28 | htmlType?: ButtonHTMLType;
29 | } & BaseButtonProps &
30 | Omit, 'type'>;
31 |
32 | export type ButtonProps = Partial;
33 |
34 | const BaseButton: React.FunctionComponent = props => {
35 | const {
36 | component = 'button',
37 | href,
38 | children,
39 | className,
40 | disabled,
41 | loading,
42 | htmlType,
43 | ripple = true,
44 | center,
45 | ...rest
46 | } = props;
47 | const Component: any = href ? 'a' : component;
48 | const prefixRippleCls = usePrefixCls('ripple');
49 | const prefixButtonCls = usePrefixCls('btn');
50 | const buttonProps = {
51 | href,
52 | disabled: disabled || loading,
53 | type: htmlType,
54 | };
55 | const classes = clsx(prefixRippleCls, className, {
56 | [`${prefixButtonCls}_disabled`]: buttonProps.disabled,
57 | });
58 | return (
59 |
60 | {children}
61 | {ripple && !buttonProps.disabled && }
62 | {loading && }
63 |
64 | );
65 | };
66 |
67 | export default BaseButton;
68 |
--------------------------------------------------------------------------------
/components/modal/__test__/Confirm.test.jsx:
--------------------------------------------------------------------------------
1 | import Modal from '..';
2 | const { confirm } = Modal;
3 |
4 | describe('Comfirm', () => {
5 | afterEach(() => {
6 | document.body.innerHTML = '';
7 | });
8 |
9 | function open(args) {
10 | confirm({
11 | title: 'Want to delete these items?',
12 | content: 'some descriptions',
13 | ...args,
14 | });
15 | }
16 | function $$(className) {
17 | return document.body.querySelectorAll(className);
18 | }
19 |
20 | it('onCancel should be called', () => {
21 | const onCancel = jest.fn();
22 | const onOk = jest.fn();
23 | open({
24 | onCancel,
25 | onOk,
26 | });
27 | $$('.ty-btn')[0].click();
28 | expect(onCancel.mock.calls.length).toBe(1);
29 | expect(onOk.mock.calls.length).toBe(0);
30 | });
31 |
32 | it('onOk should be called', () => {
33 | const onCancel = jest.fn();
34 | const onOk = jest.fn();
35 | open({
36 | onCancel,
37 | onOk,
38 | });
39 |
40 | $$('.ty-btn')[1].click();
41 | expect(onOk.mock.calls.length).toBe(1);
42 | expect(onCancel.mock.calls.length).toBe(0);
43 | });
44 |
45 | it('could be update', () => {
46 | const instance = confirm({
47 | title: 'title',
48 | content: 'content',
49 | });
50 |
51 | expect($$('.ty-confirm__title')[0].innerHTML).toBe('title');
52 | expect($$('.ty-confirm__content')[0].innerHTML).toBe('content');
53 |
54 | instance.update({
55 | title: 'new title',
56 | content: 'new content',
57 | });
58 | expect($$('.ty-confirm__title')[0].innerHTML).toBe('new title');
59 | expect($$('.ty-confirm__content')[0].innerHTML).toBe('new content');
60 | });
61 |
62 | it('could be destory', () => {
63 | jest.useFakeTimers();
64 | const instance = confirm({
65 | title: 'title',
66 | content: 'content',
67 | });
68 | expect($$('.ty-modal__root')).toHaveLength(1);
69 | instance.destroy();
70 | jest.runAllTimers();
71 | expect($$('.ty-modal__root')).toHaveLength(0);
72 | jest.useRealTimers();
73 | });
74 | });
75 |
--------------------------------------------------------------------------------
/site/demo/drawer/basic.md:
--------------------------------------------------------------------------------
1 | ## Drawer 抽屉
2 |
3 |
4 |
5 | ```js
6 | const Drawers = () => {
7 | const [leftVisible, setLeftVisible] = React.useState(false);
8 | const [rightVisible, setRightVisible] = React.useState(false);
9 | const [topVisible, setTopVisible] = React.useState(false);
10 | const [bottomVisible, setBottomVisible] = React.useState(false);
11 |
12 | return (
13 |
14 |
22 | {
25 | setLeftVisible(false);
26 | }}
27 | >
28 | Left
29 |
30 |
31 |
39 | {
43 | setRightVisible(false);
44 | }}
45 | >
46 | Right
47 |
48 |
49 |
57 | {
61 | setTopVisible(false);
62 | }}
63 | >
64 | Top
65 |
66 |
67 |
75 | {
79 | setBottomVisible(false);
80 | }}
81 | >
82 | Bottom
83 |
84 |
85 | );
86 | };
87 | ReactDOM.render();
88 | ```
89 |
90 |
91 |
--------------------------------------------------------------------------------
/components/appBar/AppBarInner.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef } from 'react';
2 | import clsx from 'clsx';
3 | import { usePrefixCls } from '../_util/hooks';
4 | import { Col, Button } from '../';
5 | import { ColProps } from '../grid/Col';
6 | import { ButtonProps as IButtonProps } from '../button';
7 |
8 | interface IconButtonProps extends ColProps {
9 | className?: string;
10 | style?: React.CSSProperties;
11 | children?: React.ReactNode;
12 | ButtonProps?: IButtonProps;
13 | }
14 |
15 | export const IconButton = forwardRef(
16 | (props: IconButtonProps, ref: React.RefObject) => {
17 | const { className, children, ButtonProps, ...rest } = props;
18 | const prefixCls = usePrefixCls('appbar');
19 | const classes = clsx(`${prefixCls}__icon`, className);
20 |
21 | return (
22 |
23 |
26 |
27 | );
28 | },
29 | );
30 |
31 | interface TypographyProps {
32 | className?: string;
33 | style?: React.CSSProperties;
34 | children?: React.ReactNode;
35 | }
36 |
37 | export const Typography = forwardRef(
38 | (props: TypographyProps, ref: React.RefObject) => {
39 | const { className, children, ...rest } = props;
40 | const prefixCls = usePrefixCls('appbar');
41 | const classes = clsx(`${prefixCls}__typography`, className);
42 |
43 | return (
44 |
45 | {children}
46 |
47 | );
48 | },
49 | );
50 |
51 | interface ToolBarProps {
52 | className?: string;
53 | style?: React.CSSProperties;
54 | children?: React.ReactNode;
55 | }
56 |
57 | export const ToolBar = forwardRef((props: ToolBarProps, ref: React.RefObject) => {
58 | const { className, children, ...rest } = props;
59 | const prefixCls = usePrefixCls('appbar');
60 | const classes = clsx(`${prefixCls}__toolBar`, className);
61 |
62 | return (
63 |
64 | {children}
65 |
66 | );
67 | });
68 |
--------------------------------------------------------------------------------
/components/switch/style/index.scss:
--------------------------------------------------------------------------------
1 | @import '../../theme/mixins/mixins.scss';
2 | @import '../../theme/default.scss';
3 |
4 | @include b(switch) {
5 | position: relative;
6 | display: inline-block;
7 | box-sizing: border-box;
8 | width: 58px;
9 | height: 38px;
10 | padding: 12px;
11 | vertical-align: middle;
12 | background-color: transparent;
13 | border: 1px solid transparent;
14 | cursor: pointer;
15 |
16 | @include m(default) {
17 | @include e(node) {
18 | background-color: $--color-black-bg;
19 | }
20 | @include e(circle) {
21 | background-color: $--color-white;
22 | }
23 | }
24 | @each $key, $color in $--colors {
25 | @include m($key) {
26 | @include e(node) {
27 | background-color: $color;
28 | }
29 | @include e(circle) {
30 | background-color: $color;
31 | }
32 | }
33 | }
34 |
35 | @include m(disabled) {
36 | cursor: not-allowed;
37 | user-select: none;
38 | @include e(node) {
39 | background-color: $--color-disabled;
40 | }
41 | @include e(circle) {
42 | background-color: $--color-white;
43 | }
44 | }
45 |
46 | &:focus {
47 | outline: 0;
48 | }
49 |
50 | @include m(checked) {
51 | @include e(ripple) {
52 | transform: translateX(40%);
53 | transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
54 | }
55 | }
56 |
57 | @include e(node) {
58 | display: inline-block;
59 | width: 100%;
60 | height: 100%;
61 | background-color: $--color-black-bg;
62 | border-radius: 7px;
63 | opacity: 0.5;
64 | }
65 | @include e(ripple) {
66 | position: absolute;
67 | top: 0;
68 | left: 0;
69 | z-index: 1;
70 | transition: left 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,
71 | transform 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
72 | }
73 | @include e(circle) {
74 | position: relative;
75 | z-index: 1;
76 | width: 20px;
77 | height: 20px;
78 | background-color: $--color-white;
79 | border-radius: 50%;
80 | box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14),
81 | 0 2px 1px -1px rgba(0, 0, 0, 0.12);
82 | cursor: pointer;
83 | pointer-events: none;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | defaults: &defaults
2 | docker:
3 | - image: circleci/node:8
4 |
5 | version: 2
6 | jobs:
7 | prepare:
8 | <<: *defaults
9 | steps:
10 | - checkout
11 | - restore_cache:
12 | keys:
13 | - v1-dependencies-{{ checksum "package.json" }}
14 | - run: yarn install
15 | - save_cache:
16 | paths:
17 | - node_modules
18 | key: v1-dependencies-{{ checksum "package.json" }}
19 | - persist_to_workspace:
20 | root: .
21 | paths:
22 | - node_modules
23 | dist:
24 | <<: *defaults
25 | steps:
26 | - checkout
27 | - attach_workspace:
28 | at: .
29 | - run: yarn dist
30 | - persist_to_workspace:
31 | root: .
32 | paths:
33 | - dist
34 | - package.json
35 | - README.md
36 | - LICENSE
37 | compile:
38 | <<: *defaults
39 | steps:
40 | - checkout
41 | - attach_workspace:
42 | at: .
43 | - run: yarn compile
44 | - persist_to_workspace:
45 | root: .
46 | paths:
47 | - lib
48 | - es
49 | test:
50 | <<: *defaults
51 | steps:
52 | - checkout
53 | - attach_workspace:
54 | at: .
55 | - run: yarn ci
56 | - run: bash <(curl -s https://codecov.io/bash)
57 | - store_test_results:
58 | path: test-results
59 | publish:
60 | <<: *defaults
61 | steps:
62 | - attach_workspace:
63 | at: .
64 | - run: npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN
65 | - run: npm publish
66 |
67 | workflows:
68 | version: 2
69 | build_accept_deploy:
70 | jobs:
71 | - prepare
72 | - test:
73 | requires:
74 | - prepare
75 | - dist:
76 | requires:
77 | - test
78 | filters:
79 | branches:
80 | only: deploy
81 | - compile:
82 | requires:
83 | - test
84 | filters:
85 | branches:
86 | only: deploy
87 | - publish:
88 | requires:
89 | - dist
90 | - compile
91 |
--------------------------------------------------------------------------------
/components/switch/__test__/index.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, fireEvent } from '@testing-library/react';
3 | import Switch from '..';
4 |
5 | describe('Switch', () => {
6 | it('should render correctly', () => {
7 | const { asFragment } = render();
8 | expect(asFragment()).toMatchSnapshot();
9 | });
10 |
11 | it('should support checked', () => {
12 | render();
13 | expect(document.querySelectorAll('.ty-switch--checked').length).toBe(1);
14 | });
15 |
16 | it('should support keyBoard events', () => {
17 | render();
18 | const switchDOM = document.querySelector('.ty-switch');
19 | expect(switchDOM.classList).not.toContain('ty-switch--checked');
20 | fireEvent.keyDown(switchDOM, { key: 'right', keyCode: 39 });
21 | expect(switchDOM.classList).toContain('ty-switch--checked');
22 | fireEvent.keyDown(switchDOM, { key: 'left', keyCode: 37 });
23 | expect(switchDOM.classList).not.toContain('ty-switch--checked');
24 | });
25 |
26 | it('should fire onChange correctly', () => {
27 | const handleChange = jest.fn();
28 | render();
29 | const switchDOM = document.querySelector('.ty-switch');
30 | fireEvent.click(switchDOM);
31 | expect(handleChange).toHaveBeenCalled();
32 | });
33 |
34 | it('should support disabled', () => {
35 | const handleChange = jest.fn();
36 | const { container } = render();
37 | fireEvent.click(container);
38 | expect(handleChange).not.toHaveBeenCalled();
39 | });
40 |
41 | it('should fire onClick correctly', () => {
42 | const handleClick = jest.fn();
43 | render();
44 | const switchDOM = document.querySelector('.ty-switch');
45 | fireEvent.click(switchDOM);
46 | expect(handleClick).toHaveBeenCalled();
47 | });
48 |
49 | it('should support defaultChecked', () => {
50 | const { container } = render();
51 | expect(container.querySelectorAll('.ty-switch--checked').length).toBe(1);
52 | fireEvent.click(container.querySelector('.ty-switch'));
53 | expect(container.querySelectorAll('.ty-switch--checked').length).toBe(0);
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/site/pages/Input.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Input, Icon } from 'components';
3 |
4 | export default () => {
5 | const [inputVal, setInputVal] = useState("");
6 |
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
23 |
24 | }
27 | labelFloat
28 | placeholder="请输入姓名"
29 | />
30 | } placeholder="请输入姓名" />
31 | } placeholder="请输入姓名" />
32 | } placeholder="请输入姓名" />
33 | } placeholder="请输入姓名" />
34 | {
39 | console.log('focus');
40 | }}
41 | onBlur={() => {
42 | console.log('blur');
43 | }}
44 | />
45 | {
51 | setInputVal(e.target.value);
52 | }}
53 | />
54 | {
55 | console.log(1);
56 | }} />
57 | }
59 | placeholder="请输入"
60 | type="textarea"
61 | />
62 | }
67 | placeholder="请输入"
68 | type="textarea"
69 | />
70 |
71 | );
72 | };
73 |
--------------------------------------------------------------------------------
/components/button/__test__/__snapshots__/index.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Button render correctly 1`] = `
4 |
16 | `;
17 |
18 | exports[`Button render correctly 2`] = `
19 |
31 | `;
32 |
33 | exports[`Button render correctly 3`] = `
34 |
46 | `;
47 |
48 | exports[`Button render correctly 4`] = `
49 |
61 | `;
62 |
63 | exports[`Button should not render as link button when href is undefined 1`] = `
64 |
76 | `;
77 |
78 | exports[`Button should support link button 1`] = `
79 |
84 |
87 | link button
88 |
89 |
92 |
93 | `;
94 |
95 | exports[`Button should support ripple attribute 1`] = `
96 |
105 | `;
106 |
--------------------------------------------------------------------------------
/site/react-code-view/lib/style/dracula.css:
--------------------------------------------------------------------------------
1 | .cm-s-dracula.CodeMirror {
2 | margin: 12px;
3 | font-size: 16px;
4 |
5 | font-family: 'Lucida Console', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
6 | line-height: 24px;
7 | tab-size: 1.5em;
8 | border-radius: 6px;
9 | box-shadow: rgba(0, 0, 0, 0.55) 0 1px 12px;
10 | }
11 |
12 | .cm-s-dracula.CodeMirror,
13 | .cm-s-dracula .CodeMirror-gutters {
14 | color: #f8f8f2 !important;
15 | background-color: #282a36 !important;
16 | border: none;
17 | }
18 | .cm-s-dracula .CodeMirror-gutters {
19 | color: #282a36;
20 | }
21 | .cm-s-dracula .CodeMirror-cursor {
22 | border-left: solid thin #f8f8f0;
23 | }
24 | .cm-s-dracula .CodeMirror-linenumber {
25 | color: #6d8a88;
26 | }
27 | .cm-s-dracula .CodeMirror-selected {
28 | background: rgba(255, 255, 255, 0.1);
29 | }
30 | .cm-s-dracula .CodeMirror-line::selection,
31 | .cm-s-dracula .CodeMirror-line > span::selection,
32 | .cm-s-dracula .CodeMirror-line > span > span::selection {
33 | background: rgba(255, 255, 255, 0.1);
34 | }
35 | .cm-s-dracula .CodeMirror-line::-moz-selection,
36 | .cm-s-dracula .CodeMirror-line > span::-moz-selection,
37 | .cm-s-dracula .CodeMirror-line > span > span::-moz-selection {
38 | background: rgba(255, 255, 255, 0.1);
39 | }
40 | .cm-s-dracula span.cm-comment {
41 | color: #6272a4;
42 | }
43 | .cm-s-dracula span.cm-string,
44 | .cm-s-dracula span.cm-string-2 {
45 | color: #f1fa8c;
46 | }
47 | .cm-s-dracula span.cm-number {
48 | color: #bd93f9;
49 | }
50 | .cm-s-dracula span.cm-variable {
51 | color: #50fa7b;
52 | }
53 | .cm-s-dracula span.cm-variable-2 {
54 | color: white;
55 | }
56 | .cm-s-dracula span.cm-def {
57 | color: #50fa7b;
58 | }
59 | .cm-s-dracula span.cm-operator {
60 | color: #ff79c6;
61 | }
62 | .cm-s-dracula span.cm-keyword {
63 | color: #ff79c6;
64 | }
65 | .cm-s-dracula span.cm-atom {
66 | color: #bd93f9;
67 | }
68 | .cm-s-dracula span.cm-meta {
69 | color: #f8f8f2;
70 | }
71 | .cm-s-dracula span.cm-tag {
72 | color: #ff79c6;
73 | }
74 | .cm-s-dracula span.cm-attribute {
75 | color: #50fa7b;
76 | }
77 | .cm-s-dracula span.cm-qualifier {
78 | color: #50fa7b;
79 | }
80 | .cm-s-dracula span.cm-property {
81 | color: #66d9ef;
82 | }
83 | .cm-s-dracula span.cm-builtin {
84 | color: #50fa7b;
85 | }
86 | .cm-s-dracula span.cm-variable-3,
87 | .cm-s-dracula span.cm-type {
88 | color: #ffb86c;
89 | }
90 |
91 | .cm-s-dracula .CodeMirror-activeline-background {
92 | background: rgba(255, 255, 255, 0.1);
93 | }
94 | .cm-s-dracula .CodeMirror-matchingbracket {
95 | color: white !important;
96 | text-decoration: underline;
97 | }
98 |
--------------------------------------------------------------------------------
/components/drawer/drawer.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef, useState, useRef, useLayoutEffect } from 'react';
2 | import { CSSTransition } from 'react-transition-group';
3 | import { usePrefixCls } from '../_util/hooks';
4 | import { duration } from '../_util/transition';
5 | import Portal from '../portal';
6 | import Sider, { SiderProps } from './Sider';
7 | import { useForkRef } from '../_util/reactHelpers';
8 |
9 | export interface DrawerProps extends Partial {
10 | visible?: boolean;
11 | mask?: boolean;
12 | onClose?: (e: React.MouseEvent) => void;
13 | }
14 |
15 | const Drawer = forwardRef((props: DrawerProps, ref: React.RefObject) => {
16 | const { visible = false, width, onClose, mask = true, children, ...rest } = props;
17 | const prefixCls = usePrefixCls('drawer');
18 | const [prevVisible, setPrevVisible] = useState(false);
19 | const [exciting, setExciting] = useState(false);
20 | const maskNode = useRef(null);
21 | const handleRef = useForkRef(maskNode, ref);
22 |
23 | const removeMoveHandler = (e: React.TouchEvent | TouchEvent) => {
24 | e.preventDefault();
25 | };
26 |
27 | useLayoutEffect(() => {
28 | if (maskNode && maskNode.current) {
29 | maskNode.current.addEventListener('touchmove', removeMoveHandler, { passive: false });
30 | }
31 | return () => {
32 | if (maskNode && maskNode.current) {
33 | maskNode.current.removeEventListener('touchmove', removeMoveHandler);
34 | }
35 | };
36 | }, [visible]);
37 |
38 | if (visible !== prevVisible) {
39 | if (prevVisible && !visible) {
40 | setExciting(true);
41 | }
42 | setPrevVisible(visible);
43 | }
44 |
45 | const handleMaskClick = (e: React.MouseEvent) => {
46 | if (onClose) {
47 | onClose(e);
48 | }
49 | };
50 |
51 | const onMaskExited = () => {
52 | setExciting(false);
53 | };
54 |
55 | return (
56 |
57 | {mask && (
58 |
69 |
70 |
71 | )}
72 |
73 | {children}
74 |
75 |
76 | );
77 | });
78 | export default Drawer;
79 |
--------------------------------------------------------------------------------