├── .github
└── FUNDING.yml
├── .gitignore
├── .npmignore
├── .prettierrc.js
├── .storybook
├── main.js
└── preview.js
├── .stylelintrc.js
├── .travis.yml
├── CHANGELOG.md
├── README.md
├── gulpfile.js
├── package.json
├── plop-template
├── component.hbs
├── componentIndex.hbs
├── componentStories.hbs
└── componentStyle.hbs
├── plopfile.js
├── public
└── index.html
├── src
├── components
│ ├── Alert
│ │ ├── _style.scss
│ │ ├── alert.stories.mdx
│ │ ├── alert.tsx
│ │ └── index.tsx
│ ├── AutoComplete
│ │ ├── _style.scss
│ │ ├── autocomplete.example.tsx
│ │ ├── autocomplete.stories.mdx
│ │ ├── autocomplete.tsx
│ │ └── index.tsx
│ ├── Avatar
│ │ ├── _style.scss
│ │ ├── avatar.stories.mdx
│ │ ├── avatar.tsx
│ │ └── index.tsx
│ ├── Badge
│ │ ├── _style.scss
│ │ ├── badge.example.tsx
│ │ ├── badge.stories.mdx
│ │ ├── badge.tsx
│ │ └── index.tsx
│ ├── Button
│ │ ├── _style.scss
│ │ ├── button.stories.mdx
│ │ ├── button.test.tsx
│ │ ├── button.tsx
│ │ └── index.tsx
│ ├── Card
│ │ ├── _style.scss
│ │ ├── card.stories.mdx
│ │ ├── card.tsx
│ │ └── index.tsx
│ ├── Carousel
│ │ ├── _style.scss
│ │ ├── carousel.stories.mdx
│ │ ├── carousel.tsx
│ │ └── index.tsx
│ ├── CheckBox
│ │ ├── _style.scss
│ │ ├── checkbox.example.tsx
│ │ ├── checkbox.stories.mdx
│ │ ├── checkbox.tsx
│ │ └── index.tsx
│ ├── Divider
│ │ ├── _style.scss
│ │ ├── divider.stories.mdx
│ │ ├── divider.tsx
│ │ └── index.tsx
│ ├── Form
│ │ ├── _style.scss
│ │ ├── form.example.tsx
│ │ ├── form.stories.mdx
│ │ ├── form.tsx
│ │ └── index.tsx
│ ├── Grid
│ │ ├── _style.scss
│ │ ├── grid.stories.mdx
│ │ ├── grid.tsx
│ │ └── index.tsx
│ ├── I18n
│ │ ├── _style.scss
│ │ ├── i18n.example.tsx
│ │ ├── i18n.stories.mdx
│ │ ├── i18n.tsx
│ │ └── index.tsx
│ ├── Icon
│ │ ├── _style.scss
│ │ ├── icon.stories.mdx
│ │ ├── icon.test.tsx
│ │ ├── icon.tsx
│ │ └── index.tsx
│ ├── Input
│ │ ├── _style.scss
│ │ ├── index.tsx
│ │ ├── input.stories.mdx
│ │ └── input.tsx
│ ├── InputNumber
│ │ ├── _style.scss
│ │ ├── index.tsx
│ │ ├── inputnumber.example.tsx
│ │ ├── inputnumber.stories.mdx
│ │ └── inputnumber.tsx
│ ├── Layout
│ │ ├── _style.scss
│ │ ├── index.tsx
│ │ ├── layout.stories.mdx
│ │ └── layout.tsx
│ ├── List
│ │ ├── _style.scss
│ │ ├── index.tsx
│ │ ├── list.example.tsx
│ │ ├── list.stories.mdx
│ │ └── list.tsx
│ ├── Menu
│ │ ├── _style.scss
│ │ ├── index.tsx
│ │ ├── menu.stories.mdx
│ │ ├── menu.test.tsx
│ │ ├── menu.tsx
│ │ ├── menuitem.stories.mdx
│ │ ├── menuitem.tsx
│ │ ├── submenu.stories.mdx
│ │ └── submenu.tsx
│ ├── Message
│ │ ├── _style.scss
│ │ ├── index.tsx
│ │ ├── message.stories.mdx
│ │ └── message.tsx
│ ├── Modal
│ │ ├── _style.scss
│ │ ├── index.tsx
│ │ ├── modal.example.tsx
│ │ ├── modal.stories.mdx
│ │ └── modal.tsx
│ ├── MultiSelect
│ │ ├── _style.scss
│ │ ├── index.tsx
│ │ ├── multiselect.stories.mdx
│ │ └── multiselect.tsx
│ ├── Pagination
│ │ ├── _style.scss
│ │ ├── index.tsx
│ │ ├── pagination.stories.mdx
│ │ └── pagination.tsx
│ ├── Popconfirm
│ │ ├── _style.scss
│ │ ├── index.tsx
│ │ ├── popconfirm.example.tsx
│ │ ├── popconfirm.stories.mdx
│ │ └── popconfirm.tsx
│ ├── Progress
│ │ ├── _style.scss
│ │ ├── index.tsx
│ │ ├── progress.example.tsx
│ │ ├── progress.stories.mdx
│ │ └── progress.tsx
│ ├── Radio
│ │ ├── _style.scss
│ │ ├── index.tsx
│ │ ├── radio.stories.mdx
│ │ └── radio.tsx
│ ├── Select
│ │ ├── _style.scss
│ │ ├── index.tsx
│ │ ├── select.stories.mdx
│ │ └── select.tsx
│ ├── Switch
│ │ ├── _style.scss
│ │ ├── index.tsx
│ │ ├── switch.stories.mdx
│ │ └── switch.tsx
│ ├── Table
│ │ ├── _style.scss
│ │ ├── index.tsx
│ │ ├── table.example.tsx
│ │ ├── table.stories.mdx
│ │ └── table.tsx
│ ├── Transition
│ │ ├── index.tsx
│ │ ├── transition.stories.mdx
│ │ └── transition.tsx
│ ├── Upload
│ │ ├── _style.scss
│ │ ├── index.tsx
│ │ ├── upload.stories.mdx
│ │ ├── upload.tsx
│ │ └── uploadlist.tsx
│ └── VirtualList
│ │ ├── _style.scss
│ │ ├── index.tsx
│ │ ├── virtuallist.example.tsx
│ │ ├── virtuallist.stories.mdx
│ │ └── virtuallist.tsx
├── hooks
│ ├── useClickOutside.stories.mdx
│ ├── useClickOutside.tsx
│ ├── useControlReverse.stories.mdx
│ ├── useControlReverse.tsx
│ ├── useDebounce.stories.mdx
│ ├── useDebounce.tsx
│ ├── useForm.stories.mdx
│ ├── useForm.tsx
│ ├── useStopScroll.stories.mdx
│ └── useStopScroll.tsx
├── index.tsx
├── page
│ ├── login.example.tsx
│ ├── login.stories.mdx
│ ├── register.example.tsx
│ └── register.stories.mdx
├── react-app-env.d.ts
├── setupTests.ts
├── stories
│ ├── Welcome.stories.mdx
│ └── color.stories.mdx
└── styles
│ ├── _animation.scss
│ ├── _color.scss
│ ├── _reboot.scss
│ ├── _variables.scss
│ ├── index.scss
│ └── mixin
│ ├── _animatemixin.scss
│ ├── _clearfix.scss
│ ├── _index.scss
│ └── _size.scss
├── tsconfig.build.json
├── tsconfig.json
└── yarn.lock
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: https://yehuozhili-1259443377.cos.ap-nanjing.myqcloud.com/pay.jpg
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 | /storybook-static
25 | /dist
26 | .vscode
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .storybook
2 | .vscode
3 | coverage
4 | node_modules
5 | plop-template
6 | public
7 | src
8 | storybook-static
9 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 100,
3 | tabWidth: 4,
4 | useTabs: true,
5 | semi: true,
6 | parser: 'typescript',
7 | jsxBracketSameLine: false,
8 | arrowParens: "always",
9 | };
10 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
2 | const TerserPlugin = require("terser-webpack-plugin");
3 | let maxAssetSize = 1024 * 1024;
4 | let minSize = 30000;
5 | module.exports = {
6 | stories: ["../src/**/*.stories.(tsx|mdx)"],
7 | addons: [
8 | "@storybook/preset-create-react-app",
9 | "@storybook/addon-actions",
10 | "@storybook/addon-links",
11 | {
12 | name: "@storybook/addon-docs",
13 | options: {
14 | configureJSX: true
15 | }
16 | },
17 | "@storybook/addon-viewport/register"
18 | ],
19 | managerWebpack: async (config) => {
20 | config.optimization.splitChunks = { chunks: "all", maxSize: maxAssetSize };
21 | const isprod = config.mode === "production";
22 | let tser = isprod
23 | ? [
24 | new TerserPlugin({
25 | terserOptions: {
26 | parse: {
27 | ecma: 8
28 | },
29 | compress: {
30 | ecma: 5,
31 | warnings: false,
32 | comparisons: false,
33 | inline: 2
34 | },
35 | mangle: {
36 | safari10: true
37 | },
38 | output: {
39 | ecma: 5,
40 | comments: false,
41 | ascii_only: true
42 | }
43 | }
44 | })
45 | ]
46 | : [];
47 | config.performance = {
48 | maxAssetSize: maxAssetSize
49 | };
50 | config.optimization = {
51 | minimizer: tser,
52 | minimize: isprod ? true : false,
53 | splitChunks: {
54 | chunks: "all",
55 | maxSize: maxAssetSize,
56 | minSize
57 | },
58 | runtimeChunk: true
59 | };
60 | //config.plugins.push(new BundleAnalyzerPlugin())
61 | return config;
62 | },
63 | webpackFinal: async (config) => {
64 | config.module.rules.push({
65 | test: /\.(ts|tsx)$/,
66 | use: [
67 | {
68 | loader: require.resolve("react-docgen-typescript-loader"),
69 | options: {
70 | shouldExtractLiteralValuesFromEnum: true,
71 | propFilter: (prop) => {
72 | if (prop.parent) {
73 | return !prop.parent.fileName.includes("node_modules");
74 | }
75 | return true;
76 | }
77 | }
78 | }
79 | ]
80 | });
81 | config.module.rules.push({
82 | test: /\.(gif|jpe?g|png|svg)$/,
83 | use: {
84 | loader: "url-loader",
85 | options: { name: "[name].[ext]" }
86 | }
87 | });
88 | const isprod = config.mode === "production";
89 | isprod ? (config.devtool = "none") : null;
90 |
91 | let tser = isprod ? config.optimization.minimizer : [];
92 |
93 | config.performance = {
94 | maxAssetSize: maxAssetSize
95 | };
96 | config.optimization = {
97 | minimizer: tser,
98 | minimize: isprod ? true : false,
99 | splitChunks: {
100 | chunks: "all",
101 | maxSize: maxAssetSize,
102 | minSize
103 | },
104 | runtimeChunk: true
105 | };
106 | //config.plugins.push(new BundleAnalyzerPlugin())
107 | config.resolve.extensions.push(".ts", ".tsx");
108 | return config;
109 | }
110 | };
111 |
--------------------------------------------------------------------------------
/.storybook/preview.js:
--------------------------------------------------------------------------------
1 | import "../src/styles/index.scss";
2 | import { create } from "@storybook/theming";
3 | import { addParameters, configure } from "@storybook/react";
4 | import { DocsPage, DocsContainer } from "@storybook/addon-docs/blocks";
5 | addParameters({
6 | docs: {
7 | container: DocsContainer,
8 | page: DocsPage,
9 | PreviewSource: "open"
10 | },
11 | options: {
12 | theme: create({
13 | brandTitle: "BigBear-UI",
14 | brandUrl: "https://github.com/yehuozhili/bigbear-ui"
15 | })
16 | },
17 | dependencies: {
18 | withStoriesOnly: true,
19 | hideEmpty: true
20 | }
21 | });
22 | const loaderFn = () => {
23 | return [
24 | require("../src/stories/Welcome.stories.mdx"),
25 | require("../src/stories/color.stories.mdx"),
26 | require("../src/components/Layout/layout.stories.mdx"),
27 | require("../src/components/Grid/grid.stories.mdx"),
28 | require("../src/components/Divider/divider.stories.mdx"),
29 | require("../src/components/Alert/alert.stories.mdx"),
30 | require("../src/components/Avatar/avatar.stories.mdx"),
31 | require("../src/components/Badge/badge.stories.mdx"),
32 | require("../src/components/Button/button.stories.mdx"),
33 | require("../src/components/Card/card.stories.mdx"),
34 | require("../src/components/Carousel/carousel.stories.mdx"),
35 | require("../src/components/Input/input.stories.mdx"),
36 | require("../src/components/InputNumber/inputnumber.stories.mdx"),
37 | require("../src/components/Switch/switch.stories.mdx"),
38 | require("../src/components/Radio/radio.stories.mdx"),
39 | require("../src/components/CheckBox/checkbox.stories.mdx"),
40 | require("../src/components/Select/select.stories.mdx"),
41 | require("../src/components/MultiSelect/multiselect.stories.mdx"),
42 | require("../src/components/AutoComplete/autocomplete.stories.mdx"),
43 | require("../src/components/Form/form.stories.mdx"),
44 | require("../src/components/Upload/upload.stories.mdx"),
45 | require("../src/components/List/list.stories.mdx"),
46 | require("../src/components/VirtualList/virtuallist.stories.mdx"),
47 | require("../src/components/Icon/icon.stories.mdx"),
48 | require("../src/components/Pagination/pagination.stories.mdx"),
49 | require("../src/components/Table/table.stories.mdx"),
50 | require("../src/components/Modal/modal.stories.mdx"),
51 | require("../src/components/Menu/menu.stories.mdx"),
52 | require("../src/components/Menu/menuitem.stories.mdx"),
53 | require("../src/components/Menu/submenu.stories.mdx"),
54 | require("../src/components/Progress/progress.stories.mdx"),
55 | require("../src/components/Popconfirm/popconfirm.stories.mdx"),
56 | require("../src/components/Message/message.stories.mdx"),
57 | require("../src/components/I18n/i18n.stories.mdx"),
58 | require("../src/page/login.stories.mdx"),
59 | require("../src/page/register.stories.mdx"),
60 | require("../src/components/Transition/transition.stories.mdx"),
61 | require("../src/hooks/useForm.stories.mdx"),
62 | require("../src/hooks/useClickOutside.stories.mdx"),
63 | require("../src/hooks/useDebounce.stories.mdx"),
64 | require("../src/hooks/useStopScroll.stories.mdx"),
65 | require("../src/hooks/useControlReverse.stories.mdx")
66 | ];
67 | };
68 | configure(loaderFn, module);
69 |
--------------------------------------------------------------------------------
/.stylelintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | "stylelint-config-standard",
4 | "stylelint-config-sass-guidelines",
5 | "stylelint-config-prettier"
6 | ],
7 | plugins: ["stylelint-order", "stylelint-scss"],
8 | rules: {
9 | "declaration-property-value-blacklist": null,
10 | "max-nesting-depth": null,
11 | "no-descending-specificity": null,
12 | "selector-max-compound-selectors": null
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "stable"
4 | cache:
5 | directories:
6 | - node_modules
7 | env:
8 | - CI=true
9 | script:
10 | - npm run coverall
11 | - npm run build-storybook
12 | deploy:
13 | provider: pages
14 | skip_cleanup: true
15 | github_token: $githubtoken
16 | local_dir: storybook-static
17 | on:
18 | branch: master
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # bigbear-ui
2 | bigbear-ui是个人制作的拟物化小型轻量级ui库
3 |
4 | 
5 | 
6 | 
7 | [](https://travis-ci.com/yehuozhili/bigbear-ui)
8 | [](https://coveralls.io/github/yehuozhili/bigbear-ui?branch=master)
9 |
10 | ✨ 特性
11 |
12 |
13 | - 📕 详细的文档与介绍
14 | - 🎨 使用富有特色的Neumorphism拟物化风格
15 | - 📦 开箱即用的高质量 React 组件
16 | - 🔥 使用 TypeScript 开发,提供完整的类型定义文件
17 |
18 |
19 |
20 |
21 | ## 安装
22 | 使用 npm 或 yarn 安装
23 |
24 | ```
25 | $ npm install bigbear-ui --save
26 | ```
27 |
28 | ## 引入样式
29 |
30 | ```
31 | import 'bigbear-ui/dist/index.css';
32 | ```
33 | ## 导入组件
34 |
35 | ```
36 | import {componentName} from 'bigbear-ui';
37 | ```
38 |
39 | ## 在线文档
40 |
41 | https://yehuozhili.github.io/bigbear-ui/
42 |
43 |
44 |
45 |
46 | ## 本地文档
47 |
48 | 下载代码,npm安装,使用`npm run storybook`即可获得本地文档。
49 | ```
50 | git clone https://github.com/yehuozhili/bigbear-ui.git
51 | npm install
52 | npm run storybook
53 | ```
54 |
55 | ## 使用scss
56 |
57 | scss放入bigbear-ui/dist/esm/styles/index.scss。
58 | ```
59 | @import "bigbear-ui/dist/esm/styles/index.scss";
60 | ```
61 |
62 |
63 |
64 | ## 使用bigbear-ui-cli
65 |
66 | 目前暂时只制作了一个模板供下载。如果需要react-router+redux+thunk以及mock数据可以使用此模板快速开发。
67 |
68 | https://www.npmjs.com/package/bigbear-ui-cli
69 |
70 | ```
71 | npm i bigbear-ui-cli -g
72 | ```
73 |
74 | ## 项目demo
75 |
76 | http://94.191.80.37:6698/#/
77 |
78 |
79 |
80 | ## 制作初衷
81 |
82 | 制作一个属于自己的组件库应该是每个前端人员都有的梦想,有时候自己写出某些好的组件也想记录下来。
83 |
84 |
85 |
86 | ## 设计理念
87 |
88 | 新拟物风格早就存在,但是这种风格受限性很强,特别是对于背景色的要求,因为只有通过背景色制造的高光和加深才能制作出完美的凸起和凹下。
89 |
90 | 最初想法可能是做个浅色的风格和一个深色的风格,但是后来觉得,这样定制化过强,大部分时候,场景都是比较复杂的,也并不需要特别完美的定制效果,于是我将阴影效果进行改造,做出个比较通用的效果。
91 |
92 | 这种风格最适合做小工具,同时组件库体积又小,避免占太多空间。
93 |
94 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require("gulp");
2 | const ts = require("gulp-typescript");
3 |
4 | const paths = {
5 | dest: {
6 | esm: "dist/esm",
7 | cjs: "dist/cjs"
8 | },
9 | styles: "src/**/*.scss",
10 | scripts: ["src/**/*.{ts,tsx}", "!src/**/*.example.{ts,tsx}"]
11 | };
12 |
13 | function compileCJS() {
14 | const { dest, scripts } = paths;
15 | return gulp
16 | .src(scripts)
17 | .pipe(
18 | ts({
19 | outDir: "dist",
20 | target: "es5",
21 | module: "commonjs",
22 | declaration: true,
23 | jsx: "react",
24 | moduleResolution: "node",
25 | allowSyntheticDefaultImports: true
26 | })
27 | )
28 | .pipe(gulp.dest(dest.cjs));
29 | }
30 |
31 | function compileESM() {
32 | const { dest, scripts } = paths;
33 | return gulp
34 | .src(scripts)
35 | .pipe(
36 | ts({
37 | outDir: "dist",
38 | target: "es5",
39 | module: "esnext",
40 | declaration: true,
41 | jsx: "react",
42 | moduleResolution: "node",
43 | allowSyntheticDefaultImports: true
44 | })
45 | )
46 | .pipe(gulp.dest(dest.esm));
47 | }
48 |
49 | function copyScss() {
50 | return gulp
51 | .src(paths.styles)
52 | .pipe(gulp.dest(paths.dest.esm))
53 | .pipe(gulp.dest(paths.dest.cjs));
54 | }
55 |
56 | const build = gulp.parallel(copyScss, compileCJS, compileESM);
57 | exports.build = build;
58 |
59 | exports.default = build;
60 |
--------------------------------------------------------------------------------
/plop-template/component.hbs:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 |
4 | function {{name}}(){
5 | return
6 | }
7 |
8 | export default {{name}};
--------------------------------------------------------------------------------
/plop-template/componentIndex.hbs:
--------------------------------------------------------------------------------
1 | import {{name}} from "./{{lowerCase name}}";
2 |
3 | export default {{name}};
4 |
--------------------------------------------------------------------------------
/plop-template/componentStories.hbs:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import {{name}} from './{{lowerCase name}}';
3 |
4 |
5 |
6 |
7 |
8 | # {{name}}
9 |
10 |
11 | ## 基本使用
12 |
13 |
14 |
15 | <{{name}}>{{name}}>
16 |
17 |
18 |
19 |
20 |
21 |
22 | ## 属性详情
23 |
24 |
--------------------------------------------------------------------------------
/plop-template/componentStyle.hbs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yehuozhili/bigbear-ui/33bbced0843c893ac26ee99a250d2fa9d4268d7a/plop-template/componentStyle.hbs
--------------------------------------------------------------------------------
/plopfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(plop) {
2 | // controller generator
3 | plop.addHelper("lowerCase", function(p) {
4 | return p.toLowerCase().trim();
5 | });
6 | plop.setGenerator("component", {
7 | description: "create component",
8 | prompts: [
9 | {
10 | type: "input",
11 | name: "name",
12 | message: "component name please"
13 | }
14 | ],
15 | actions: [
16 | {
17 | type: "add",
18 | path: "src/components/{{name}}/{{lowerCase name}}.tsx",
19 | templateFile: "plop-template/component.hbs"
20 | },
21 | {
22 | type: "add",
23 | path: "src/components/{{name}}/index.tsx",
24 | templateFile: "plop-template/componentIndex.hbs"
25 | },
26 | {
27 | type: "add",
28 | path: "src/components/{{name}}/_style.scss",
29 | templateFile: "plop-template/componentStyle.hbs"
30 | },
31 | {
32 | type: "add",
33 | path: "src/components/{{name}}/{{lowerCase name}}.stories.mdx",
34 | templateFile: "plop-template/componentStories.hbs"
35 | }
36 | ]
37 | });
38 | };
39 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
20 | React App
21 |
22 |
23 |
24 | You need to enable JavaScript to run this app.
25 |
26 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/components/Alert/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-alert{
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: center;
5 | padding:10px;
6 | position: relative;
7 | >span{
8 | &:nth-child(1){
9 | font-size: $font-size-lg;
10 | font-weight: $font-weight-bold;
11 | }
12 | svg{
13 | margin-right: 10px;
14 | }
15 | }
16 | >button{
17 | position:absolute;
18 | right:0;
19 | top:0
20 | }
21 | }
22 | @each $key,$val in $theme-colors{
23 | .bigbear-alert-#{$key}{
24 | @include neufactory-noactive(nth($val,1),nth($val,3),nth($val,4));
25 |
26 | color:#{nth($val,2)};
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/Alert/alert.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import Alert from './alert';
3 | import Icon from '../Icon'
4 |
5 |
6 |
7 |
8 |
9 | # Alert 提示框
10 |
11 |
12 | ## 基本用法
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | ## close 与 type
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | ## 带图标使用
41 |
42 |
43 |
44 | }/>
45 |
46 |
47 | } />
48 |
49 |
50 |
51 | ## description 描述
52 |
53 |
54 |
55 |
69 |
70 |
71 |
72 | ## 可传children
73 |
74 |
75 |
76 | 432432
90 |
91 |
92 |
93 | ## 属性详情
94 |
95 |
--------------------------------------------------------------------------------
/src/components/Alert/alert.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | FC,
3 | useState,
4 | useEffect,
5 | ReactNode,
6 | CSSProperties,
7 | DOMAttributes,
8 | useRef
9 | } from "react";
10 | import classNames from "classnames";
11 | import Button from "../Button";
12 | import Icon from "../Icon";
13 | import Transition from "../Transition";
14 | import { AnimationName } from "../Transition/transition";
15 |
16 | export interface AlertProps extends DOMAttributes {
17 | /** 标题 */
18 | title?: string;
19 | /** 类型*/
20 | type?:
21 | | "primary"
22 | | "default"
23 | | "danger"
24 | | "secondary"
25 | | "success"
26 | | "info"
27 | | "light"
28 | | "warning"
29 | | "dark";
30 | /**是否有关闭按钮*/
31 | close?: boolean;
32 | /** 内容*/
33 | description?: ReactNode;
34 | /** 动画方向 */
35 | directions?: "left" | "top" | "right" | "bottom" | "allscale";
36 | /** 自动关闭延时时间,0表示不自动关闭 */
37 | autoclosedelay?: number;
38 | /** 额外类名 */
39 | className?: string;
40 | /** 图标 */
41 | icon?: ReactNode;
42 | /**启用开场动画 */
43 | initAnimate?: boolean;
44 | /**是否套一层div */
45 | wrapper?: boolean;
46 | /** 主动关闭逻辑回调函数,如果需要主动消失,需要操作setState,或者使用上层组件的state*/
47 | initiativeCloseCallback?: (
48 | setState: React.Dispatch>,
49 | e: React.MouseEvent
50 | ) => void;
51 | /** 自动关闭后的回调函数 */
52 | closeCallback?: () => void;
53 | /** 使用自定义动画的类名 */
54 | animateClassName?: string;
55 | /** 动画持续时间*/
56 | timeout?: number;
57 | children?: ReactNode;
58 | /** 外层样式*/
59 | style?: CSSProperties;
60 | }
61 |
62 | export const Alert: FC = function(props: AlertProps) {
63 | const {
64 | title,
65 | type,
66 | timeout,
67 | description,
68 | animateClassName,
69 | directions,
70 | autoclosedelay,
71 | className,
72 | initAnimate,
73 | wrapper,
74 | closeCallback,
75 | initiativeCloseCallback,
76 | children,
77 | style,
78 | icon,
79 | close,
80 | ...restProps
81 | } = props;
82 | const classes = classNames(
83 | "bigbear-alert",
84 | `bigbear-alert-${type}`,
85 | className ? className : ""
86 | );
87 | const nodeRef = useRef(null);
88 | const [state, setState] = useState(!initAnimate);
89 | useEffect(() => {
90 | if (initAnimate) {
91 | setState(true);
92 | }
93 | let handler: number;
94 | if (autoclosedelay) {
95 | handler = window.setTimeout(() => {
96 | setState(false);
97 | if (closeCallback) closeCallback();
98 | }, autoclosedelay);
99 | }
100 | return () => clearTimeout(handler);
101 | }, [autoclosedelay, closeCallback, initAnimate]);
102 | return (
103 |
111 |
112 |
113 | {icon && icon}
114 | {title}
115 |
116 | {description && {description} }
117 | {children}
118 | {close && (
119 | {
122 | if (initiativeCloseCallback) {
123 | initiativeCloseCallback(setState, e);
124 | } else {
125 | setState(false);
126 | }
127 | }}
128 | >
129 |
130 |
131 | )}
132 |
133 |
134 | );
135 | };
136 |
137 | Alert.defaultProps = {
138 | title: "",
139 | type: "default",
140 | close: false,
141 | description: null,
142 | directions: "top",
143 | autoclosedelay: 0,
144 | initAnimate: false,
145 | wrapper: false,
146 | timeout: 300
147 | };
148 |
149 | export default Alert;
150 |
--------------------------------------------------------------------------------
/src/components/Alert/index.tsx:
--------------------------------------------------------------------------------
1 | import Alert from "./alert";
2 | export default Alert;
3 |
--------------------------------------------------------------------------------
/src/components/AutoComplete/_style.scss:
--------------------------------------------------------------------------------
1 |
2 | .bigbear-autocomplete{
3 | .bigbear-autocomplete-ul{
4 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
5 |
6 | list-style: none;
7 | padding:5px;
8 | position: absolute;
9 | width: 100%;
10 | .bigbear-autocomplete-item{
11 | @include neufactory($white,$neu-whiteshadow1,$neu-whiteshadow2);
12 | @include neufactory-hover($info,$neu-infoshadow1,$neu-infoshadow2);
13 |
14 | cursor: pointer;
15 | margin:5px;
16 | padding: 5px;
17 | &:hover{
18 | color:$white;
19 | }
20 | }
21 | .autocomplete-item-highlight{
22 | @include neufactory($primary,$neu-blueshadow1,$neu-blueshadow2);
23 | @include neufactory-hover($primary,$neu-blueshadow1,$neu-blueshadow2);
24 |
25 | color:$white;
26 | }
27 | }
28 | .bigbear-autocomplete-spin{
29 | @include absolut-middle();
30 |
31 | z-index: 10;
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/src/components/AutoComplete/autocomplete.example.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import AutoComplete, { DataSourceType } from "./autocomplete";
3 |
4 | const data = [
5 | "yehuozhili",
6 | "bigbear",
7 | "nike",
8 | "hello kitty",
9 | "shop",
10 | "eat",
11 | "pikaqiu",
12 | "gobigger",
13 | "dell"
14 | ];
15 | const myfilter = (query: string) => {
16 | return data.filter((name) => name.includes(query)).map((v) => ({ value: v }));
17 | };
18 |
19 | const AutocompleteExample = () => {
20 | return (
21 | console.log(item)}
25 | callback={(e) => console.log(e)}
26 | >
27 | );
28 | };
29 | export default AutocompleteExample;
30 |
31 | const ToUseTemplete = () => {
32 | return (
33 | {item.value} }
35 | renderFilter={myfilter}
36 | >
37 | );
38 | };
39 | export { ToUseTemplete };
40 |
41 | const asyncfilter: (query: string) => Promise = (query: string) => {
42 | return new Promise((res) => {
43 | setTimeout(() => {
44 | res(data.filter((name) => name.includes(query)).map((v) => ({ value: v })));
45 | }, 1000);
46 | });
47 | };
48 |
49 | export const AsyncTest = () => {
50 | return ;
51 | };
52 |
--------------------------------------------------------------------------------
/src/components/AutoComplete/autocomplete.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import Icon from '../Icon';
3 | import AutoComplete from './autocomplete';
4 | import AutocompleteExample,{ToUseTemplete,AsyncTest} from './autocomplete.example';
5 |
6 |
7 |
8 |
9 |
10 |
11 | # AutoComplete 自动补全
12 |
13 |
14 | ## 基本用法
15 | 需要自行提供数据,以及对数据做出筛选的函数,当用户输入时,回调筛选函数进行提示。
16 |
17 | 此组件是在Input组件上做了层封装。
18 |
19 |
20 | 输入`i`字母试试自动补全功能 ↓
21 |
22 | ```jsx
23 | const data=['yehuozhili','bigbear','nike','hello kitty','shop','eat','pikaqiu','gobigger','dell'];
24 | const myfilter=(query:string)=>{
25 | return data.filter(name=>name.includes(query)).map(v=>({value:v}))
26 | }
27 | const AutocompleteExample =()=>{
28 | return console.log(item)}
30 | callback={(e)=>console.log(e)}
31 | >
32 | }
33 | ```
34 |
35 |
36 |
37 |
38 |
41 |
42 |
43 |
44 |
45 |
46 | ## 自定义模板
47 |
48 |
49 | ```jsx
50 | const ToUseTemplete=()=>{
51 | return (
52 | ({item.value} )} renderFilter={myfilter}>
53 | )
54 | }
55 | ```
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | ## 支持异步,支持键盘上下回车esc选择
67 |
68 | ```jsx
69 | const asyncfilter:(query: string) => Promise=(query:string)=>{
70 | return new Promise((res)=>{
71 | setTimeout(() => {
72 | res(data.filter(name=>name.includes(query)).map(v=>({value:v})))
73 | }, 1000);
74 | })
75 | }
76 | export const AsyncTest=()=>{
77 | return (
78 |
79 | )
80 | }
81 | ```
82 |
83 |
84 |
85 |
86 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | ## 属性详情
96 |
97 |
--------------------------------------------------------------------------------
/src/components/AutoComplete/index.tsx:
--------------------------------------------------------------------------------
1 | import AutoComplete from "./autocomplete";
2 | export default AutoComplete;
3 |
--------------------------------------------------------------------------------
/src/components/Avatar/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-avatar{
2 | overflow: hidden;
3 | >img{
4 | @include square(100%);
5 |
6 | vertical-align: top;
7 | }
8 | >.bigbear-icon{
9 | @include square(100%);
10 |
11 | vertical-align: top;
12 | }
13 |
14 | >div{
15 | @include square(100%);
16 |
17 | align-items: center;
18 | display: flex;
19 | justify-content: center;
20 | vertical-align: top;
21 | }
22 | }
23 |
24 | @each $key,$val in $theme-colors{
25 | .bigbear-avatar-type-#{$key}{
26 | @include square(100%);
27 | @include neufactory-noactive(nth($val,1),nth($val,3),nth($val,4));
28 |
29 | color:#{nth($val,2)};
30 | display: inline-block;
31 | }
32 | }
33 |
34 | @each $key,$val in $theme-colors{
35 | .bigbear-avatar-wrapper-#{$key}{
36 | @include ringwrapper(nth($val,1),nth($val,3),nth($val,4));
37 |
38 | display: inline-block;
39 | }
40 | }
41 |
42 | .bigbear-avatar-size-default{
43 | @include square($avatar-size-default);
44 |
45 | display: inline-block;
46 | overflow: hidden;
47 | padding:$avatar-padding-default;
48 | }
49 | .bigbear-avatar-size-lg{
50 | @include square($avatar-size-lg);
51 |
52 | display: inline-block;
53 | font-size: $avatar-fontsize-lg;
54 | overflow: hidden;
55 | padding:$avatar-padding-lg;
56 | }
57 | .bigbear-avatar-size-sm{
58 | @include square($avatar-size-sm);
59 |
60 | display: inline-block;
61 | font-size: $avatar-fontsize-sm;
62 | overflow: hidden;
63 | padding:$avatar-padding-sm;
64 | }
65 | .isround{
66 | border-radius: 50%;
67 | >.bigbear-avatar{
68 | border-radius: 50%;
69 | }
70 | }
--------------------------------------------------------------------------------
/src/components/Avatar/avatar.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, DOMAttributes } from "react";
2 | import classNames from "classnames";
3 |
4 | export interface AvatarProps extends DOMAttributes {
5 | /** 大小*/
6 | size?: "lg" | "sm" | "default";
7 | /** 圆形 */
8 | round?: boolean;
9 | /** 颜色 */
10 | type?:
11 | | "primary"
12 | | "default"
13 | | "danger"
14 | | "secondary"
15 | | "success"
16 | | "info"
17 | | "light"
18 | | "warning"
19 | | "dark";
20 | }
21 |
22 | export const Avatar: FC = (props) => {
23 | const { size, round, type, children, ...restProps } = props;
24 | const cnames = classNames("bigbear-avatar", `bigbear-avatar-type-${type}`);
25 | const wrappernames = classNames(`bigbear-avatar-wrapper-${type} bigbear-avatar-size-${size}`, {
26 | isround: round
27 | });
28 | return (
29 |
30 | {children ? children : null}
31 |
32 | );
33 | };
34 | Avatar.defaultProps = {
35 | size: "default",
36 | round: false,
37 | type: "default"
38 | };
39 |
40 | export default Avatar;
41 |
--------------------------------------------------------------------------------
/src/components/Avatar/index.tsx:
--------------------------------------------------------------------------------
1 | import Avatar from "./avatar";
2 |
3 | export default Avatar;
4 |
--------------------------------------------------------------------------------
/src/components/Badge/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-badge{
2 | display: inline-block;
3 | position: relative;
4 | vertical-align: bottom;
5 | }
6 |
7 | .bigbear-badge-count{
8 | border-radius:$badge-border-radius;
9 | font-size: $badge-font-size;
10 | height: 24px;
11 | padding: $badge-padding;
12 | position: absolute;
13 | right: 0;
14 | top: 0;
15 | transform: translate3d(50%,-50%,0);
16 | z-index: 800;
17 | }
18 |
19 |
20 | .bigbear-badge-count.nochildren{
21 | position: static;
22 | transform: translate3d(0,0,0);
23 | }
24 |
25 | @each $key,$val in $theme-colors{
26 | .bigbear-count-#{$key}{
27 | @include neufactory-noactive(nth($val, 1),nth($val,3),nth($val,4));
28 |
29 | color:#{nth($val,2)};
30 | }
31 | }
32 | .badge-hide{
33 | display: none;
34 | }
35 | .badge-dot{
36 | @include square(5px);
37 |
38 | border-radius:50%;
39 | padding: 0;
40 | }
--------------------------------------------------------------------------------
/src/components/Badge/badge.example.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import Button from "../Button";
3 | import Icon from "../Icon";
4 | import Badge from "./badge";
5 |
6 | export function BadgeExample() {
7 | const [numberObj, setNumber] = useState({
8 | trueNumber: 0,
9 | displayNumber: "0"
10 | });
11 | const [visible, setVisible] = useState(false);
12 | const addValidate = (number: number) => {
13 | if (!visible) setVisible(true);
14 | if (number + 1 > 99) {
15 | setNumber({
16 | trueNumber: numberObj.trueNumber + 1,
17 | displayNumber: "99+"
18 | });
19 | } else {
20 | setNumber({
21 | trueNumber: numberObj.trueNumber + 1,
22 | displayNumber: numberObj.trueNumber + 1 + ""
23 | });
24 | }
25 | };
26 | const minusValidate = (number: number) => {
27 | if (number - 1 <= 0) {
28 | setVisible(false);
29 | setNumber({ trueNumber: 0, displayNumber: "0" });
30 | } else if (number > 0 && number - 1 <= 99) {
31 | setNumber({
32 | trueNumber: numberObj.trueNumber - 1,
33 | displayNumber: numberObj.trueNumber - 1 + ""
34 | });
35 | } else {
36 | setNumber({
37 | trueNumber: numberObj.trueNumber - 1,
38 | displayNumber: "99+"
39 | });
40 | }
41 | };
42 | return (
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
点击按钮控制角标数字增减,99以上会变成99+:
51 |
addValidate(numberObj.trueNumber)}>数字加一
52 |
53 |
minusValidate(numberObj.trueNumber)}>数字减一
54 |
55 | );
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/Badge/badge.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import Badge from './badge'
3 | import Button from '../Button'
4 | import Icon from '../Icon';
5 | import {BadgeExample} from './badge.example'
6 |
7 |
8 |
9 |
10 |
11 |
12 | # Badge 徽章
13 |
14 |
15 | badge可以单独使用,也可以包裹元素使用,包裹元素会移动到元素右上角。
16 |
17 |
18 |
19 |
20 |
count100
21 |
22 |
23 |
24 |
} type={'dark'}>
25 |
26 |
只显示圆点
27 |
} type={'danger'}>
28 |
29 |
30 |
31 |
32 |
33 | 控制上下限等行为需要自行封装,封装示例:👇
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | ```jsx
45 | export function BadgeExample(){
46 | const [numberObj,setNumber]=useState({
47 | trueNumber:0,
48 | displayNumber:'0'
49 | })
50 | const [visible,setVisible]=useState(false)
51 | const addValidate=(number:number)=>{
52 | if(!visible)setVisible(true)
53 | if(number+1>99){
54 | setNumber({
55 | trueNumber:numberObj.trueNumber+1,
56 | displayNumber:'99+'
57 | })
58 | }else{
59 | setNumber({
60 | trueNumber:numberObj.trueNumber+1,
61 | displayNumber:numberObj.trueNumber+1+''
62 | })
63 | }
64 | }
65 | const minusValidate=(number:number)=>{
66 | if(number-1<=0){
67 | setVisible(false)
68 | setNumber({trueNumber:0,displayNumber:'0'})
69 | }else if(number>0&&number-1<=99){
70 | setNumber({
71 | trueNumber:numberObj.trueNumber-1,
72 | displayNumber:numberObj.trueNumber-1+''
73 | })
74 | }else{
75 | setNumber({
76 | trueNumber:numberObj.trueNumber-1,
77 | displayNumber:'99+'
78 | })
79 | }
80 | }
81 | return(
82 |
83 |
84 |
85 |
86 |
87 |
点击按钮控制角标数字增减,99以上会变成99+:
88 |
addValidate(numberObj.trueNumber)}>数字加一
89 |
90 |
minusValidate(numberObj.trueNumber)}>数字减一
91 |
92 | )
93 | }
94 | ```
95 |
96 |
97 | ## 属性详情
98 |
99 |
--------------------------------------------------------------------------------
/src/components/Badge/badge.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, ReactNode, useRef, useEffect, DOMAttributes } from "react";
2 | import classNames from "classnames";
3 |
4 | export interface BadgeProps extends DOMAttributes {
5 | /** 颜色*/
6 | type?:
7 | | "primary"
8 | | "default"
9 | | "danger"
10 | | "secondary"
11 | | "success"
12 | | "info"
13 | | "light"
14 | | "warning"
15 | | "dark";
16 | /**最外层元素的额外类名*/
17 | className?: string;
18 | /**ref回调*/
19 | refCallback?: (e: HTMLDivElement | null) => void;
20 | /** 数字,文本,图标都可以传*/
21 | count?: ReactNode;
22 | /** 控制是否显示*/
23 | visible?: boolean;
24 | /** 是否只显示小点 */
25 | dot?: boolean;
26 | }
27 |
28 | export const Badge: FC = (props) => {
29 | const { refCallback, className, type, count, visible, dot, children, ...restProps } = props;
30 | const classes = classNames("bigbear-badge", `bigbear-type-${type}`, className);
31 | const divref = useRef(null);
32 | useEffect(() => {
33 | if (refCallback && divref.current) {
34 | refCallback(divref.current);
35 | }
36 | }, [refCallback]);
37 | return (
38 |
39 | {count || dot ? (
40 |
46 | {count}
47 |
48 | ) : null}
49 | {children ? children : null}
50 |
51 | );
52 | };
53 | Badge.defaultProps = {
54 | type: "danger",
55 | visible: true,
56 | dot: false,
57 | count: ""
58 | };
59 |
60 | export default Badge;
61 |
--------------------------------------------------------------------------------
/src/components/Badge/index.tsx:
--------------------------------------------------------------------------------
1 | import Badge from "./badge";
2 |
3 | export default Badge;
4 |
--------------------------------------------------------------------------------
/src/components/Button/_style.scss:
--------------------------------------------------------------------------------
1 |
2 | .btn{
3 | @include button-size($btn-padding-x,$btn-padding-y,$btn-font-size,$border-radius);
4 |
5 | background-image: none;
6 | border:$btn-border-width solid transparent;
7 | color:$body-color;
8 | cursor: pointer;
9 | display: inline-block;
10 | font-weight: $btn-font-weight;
11 | line-height:$btn-line-height;
12 | position: relative;
13 | text-align: center;
14 | transition: $btn-transition;
15 | vertical-align: middle;
16 | white-space: nowrap;
17 | &[disabled]{
18 | cursor:not-allowed;
19 | opacity: $btn-disabled-opacity;
20 | > *{
21 | pointer-events: none;
22 | }
23 | }
24 | }
25 | //size
26 |
27 | .btn-size-lg{
28 | @include button-size($btn-padding-x-lg,$btn-padding-y-lg,$btn-font-size-lg,$border-radius-lg);
29 | }
30 | .btn-size-sm{
31 | @include button-size($btn-padding-x-sm,$btn-padding-y-sm,$btn-font-size-sm,$border-radius-sm);
32 | }
33 | .btn-size-default{
34 | @include button-size($btn-padding-x,$btn-padding-y,$btn-font-size,$border-radius);
35 | }
36 | //type
37 | @each $key,$val in $theme-colors{
38 | .btn-type-#{$key}{
39 | @include button-style(nth($val, 1),nth($val,2));
40 | @include neufactory(nth($val, 1),nth($val,3),nth($val,4));
41 | }
42 | }
43 |
44 | .btn-type-link{
45 | @include neufactory($white, $neu-whiteshadow1,$neu-whiteshadow2);
46 |
47 | border:1px dashed lighten($btn-link-color,10%);
48 | color: $btn-link-color;
49 | font-weight: $font-weight-base;
50 | text-decoration: $link-decoration;
51 | &:hover{
52 | color: $btn-link-hover-color;
53 | text-decoration: $link-hover-decoration;
54 | }
55 | &:focus{
56 | text-decoration: $link-hover-decoration;
57 | }
58 | &.disabled{
59 | color:$btn-link-disabled-color;
60 | pointer-events: none;
61 | border:1px dashed $btn-link-disabled-color;
62 | }
63 | }
64 |
65 | .btn-type-neu-w-up{
66 | @include neu-white($btn-neu-normal);
67 |
68 | color: $gray-9;
69 | font-weight: $font-weight-bold;
70 | &:hover{
71 | outline: none;
72 | }
73 | &:focus{
74 | outline: none;
75 | }
76 | &.disabled{
77 | pointer-events: none;
78 | }
79 | }
80 | .btn-type-neu-w-down{
81 | @include neu-white-down($btn-neu-normal);
82 |
83 | color: $gray-9;
84 | font-weight: $font-weight-bold;
85 | &:hover{
86 | outline: none;
87 | }
88 | &:focus{
89 | outline: none;
90 | }
91 | &.disabled{
92 | pointer-events: none;
93 | }
94 | }
95 | .btn-type-neu-w-co{
96 | @include neu-white-co($btn-neu-normal);
97 |
98 | color: $gray-9;
99 | font-weight: $font-weight-bold;
100 | &:hover{
101 | outline: none;
102 | }
103 | &:focus{
104 | outline: none;
105 | }
106 | &.disabled{
107 | pointer-events: none;
108 | }
109 | }
110 | .btn-type-neu-w-fl{
111 | @include neu-white-fl($btn-neu-normal);
112 |
113 | color: $gray-9;
114 | font-weight: $font-weight-bold;
115 | &:hover{
116 | outline: none;
117 | }
118 | &:focus{
119 | outline: none;
120 | }
121 | &.disabled{
122 | pointer-events: none;
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/components/Button/button.test.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, fireEvent } from "@testing-library/react";
3 | import Button, { ButtonProps } from "./button";
4 |
5 | const defaultProps = {
6 | onClick: jest.fn()
7 | };
8 |
9 | const testProps: ButtonProps = {
10 | btnType: "primary",
11 | size: "lg",
12 | className: "testprops"
13 | };
14 |
15 | const disabledProps: ButtonProps = {
16 | disabled: true,
17 | onClick: jest.fn()
18 | };
19 |
20 | describe("test Button component", () => {
21 | it("should render the correct default button", () => {
22 | const wrapper = render(hello );
23 | const ele = wrapper.getByText("hello") as HTMLButtonElement;
24 | expect(ele).toBeInTheDocument();
25 | expect(ele.tagName).toEqual("BUTTON");
26 | expect(ele).toHaveClass("btn btn-type-default");
27 | expect(ele.disabled).toBeFalsy();
28 | fireEvent.click(ele);
29 | expect(defaultProps.onClick).toHaveBeenCalled();
30 | });
31 | it("should render the correct component based on different props ", () => {
32 | const wrapper = render(hello );
33 | const ele = wrapper.getByText("hello");
34 | expect(ele).toBeInTheDocument();
35 | expect(ele).toHaveClass("btn-type-primary testprops");
36 | });
37 | it("should render a link when btnType equal link", () => {
38 | const wrapper = render(linkbutton );
39 | const ele = wrapper.getByText("linkbutton");
40 | expect(ele).toBeInTheDocument();
41 | expect(ele.tagName).toEqual("A");
42 | expect(ele).toHaveClass("btn btn-type-link");
43 | });
44 | it("should render disabled when disabled set", () => {
45 | const wrapper = render(hello );
46 | const ele = wrapper.getByText("hello") as HTMLButtonElement;
47 | expect(ele).toBeInTheDocument();
48 | expect(ele.disabled).toBeTruthy();
49 | fireEvent.click(ele);
50 | expect(disabledProps.onClick).not.toHaveBeenCalled();
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/src/components/Button/button.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, ButtonHTMLAttributes, AnchorHTMLAttributes, useEffect, useRef } from "react";
2 | import classNames from "classnames";
3 |
4 | export type ButtonSize = "lg" | "sm" | "default";
5 | export type ButtonType =
6 | | "primary"
7 | | "default"
8 | | "danger"
9 | | "secondary"
10 | | "success"
11 | | "info"
12 | | "light"
13 | | "warning"
14 | | "dark"
15 | | "link";
16 |
17 | export interface BaseButtonProps {
18 | className?: string;
19 | disabled?: boolean;
20 | /** 是否底色渐变 */
21 | lineargradient?: boolean;
22 | /** 设置按钮大小 */
23 | size?: ButtonSize;
24 | /**
25 | * 设置按钮类型
26 | */
27 | btnType?: ButtonType;
28 | /** link类型才有效的url */
29 | href?: string;
30 | /** 回调ref,组件加载完成回调ref,返回值会在卸载组件时调用 */
31 | refcallback?: (
32 | ref: React.RefObject
33 | ) => (() => void) | undefined;
34 | }
35 |
36 | type NativeButtonProps = ButtonHTMLAttributes & BaseButtonProps;
37 | type AnchorButtonProps = BaseButtonProps & AnchorHTMLAttributes;
38 |
39 | export type ButtonProps = Partial;
40 |
41 | export const Button: FC = (props: ButtonProps) => {
42 | const {
43 | btnType,
44 | lineargradient,
45 | size,
46 | disabled,
47 | children,
48 | href,
49 | className,
50 | refcallback,
51 | ...restProps
52 | } = props;
53 |
54 | const classes = classNames("btn", className, {
55 | [`btn-type-${btnType}`]: btnType,
56 | [`btn-size-${size}`]: size,
57 | disabled: btnType === "link" && disabled,
58 | [`bigbear-layout-lineargradient-${btnType}`]: lineargradient
59 | });
60 | const btnRef = useRef(null);
61 | useEffect(() => {
62 | let uninstall: any = null;
63 | if (refcallback) {
64 | uninstall = refcallback(btnRef);
65 | }
66 | return () => {
67 | if (typeof uninstall === "function") {
68 | uninstall();
69 | }
70 | };
71 | }, [refcallback]);
72 |
73 | if (btnType === "link") {
74 | return (
75 |
76 | {children}
77 |
78 | );
79 | } else {
80 | return (
81 |
82 | {children}
83 |
84 | );
85 | }
86 | };
87 |
88 | Button.defaultProps = {
89 | disabled: false,
90 | btnType: "default",
91 | size: "default",
92 | href: "/",
93 | lineargradient: false
94 | };
95 |
96 | export default Button;
97 |
--------------------------------------------------------------------------------
/src/components/Button/index.tsx:
--------------------------------------------------------------------------------
1 | import Button from "./button";
2 |
3 | export default Button;
4 |
--------------------------------------------------------------------------------
/src/components/Card/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-card-wrapper{
2 | padding:$card-wrapper-padding;
3 | .bigbear-card-img{
4 | width:100%;
5 | padding:$card-wrapper-padding;
6 | >img{
7 | width: 100%;
8 | }
9 | }
10 | .bigbear-card-bottom{
11 | padding:$card-wrapper-padding;
12 | .bigbear-card-btitle{
13 | font-size: $font-size-lg;
14 | font-weight: $font-weight-bold;
15 |
16 | }
17 | }
18 | .bigbear-card-id{
19 | display: flex;
20 | justify-content:flex-start;
21 | align-items: center;
22 | .bigbear-card-text{
23 | flex:1;
24 | margin-left: $card-wrapper-padding *2 ;
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/components/Card/card.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import Card from './card';
3 | import Icon from '../Icon';
4 | import Button from '../Button'
5 |
6 |
7 |
8 |
9 |
10 | # Card 卡片
11 |
12 |
13 | ## 基本使用
14 |
15 |
16 |
17 |
20 | }
21 | style={{width:'300px'}}
22 | bottomTitle='日偏食'
23 | bottomDescription='当地时间2020年6月21日,全球多国上演“超级日环食”奇观,这次日环食是本世纪迄今为止最壮观的一次。太阳将有超过99%的面积被月球遮住,只剩下一圈金边,非常罕见,又被称为“金边日食”。'
24 | >
25 |
26 |
27 |
28 | ## color&elevate
29 |
30 |
31 |
32 |
35 | }
36 | cardType='primary'
37 | elevateCard='wrapper'
38 |
39 | imgType='danger'
40 | bottomType='info'
41 | style={{width:'300px'}}
42 | bottomTitle='日偏食'
43 | bottomDescription='当地时间2020年6月21日,全球多国上演“超级日环食”奇观,这次日环食是本世纪迄今为止最壮观的一次。太阳将有超过99%的面积被月球遮住,只剩下一圈金边,非常罕见,又被称为“金边日食”。'
44 | >
45 |
46 |
47 |
48 | ## id
49 |
50 |
51 |
52 | ,
55 | round:true
56 |
57 | }}
58 | type='id'
59 | idText={
60 |
yehuozhili
61 |
bigbear-ui
62 |
}
63 |
64 | style={{width:'300px'}}
65 | >
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | ## 属性详情
84 |
85 |
--------------------------------------------------------------------------------
/src/components/Card/card.tsx:
--------------------------------------------------------------------------------
1 | import React, { PropsWithChildren, ReactNode, CSSProperties } from "react";
2 | import classNames from "classnames";
3 | import Avatar from "../Avatar";
4 | import { AvatarProps } from "../Avatar/avatar";
5 | export type normalType =
6 | | "primary"
7 | | "default"
8 | | "danger"
9 | | "secondary"
10 | | "success"
11 | | "info"
12 | | "light"
13 | | "warning"
14 | | "dark";
15 |
16 | export interface CardProps {
17 | /** 卡片类型结构 */
18 | type?: "default" | "id";
19 | /** 是*/
20 | style?: CSSProperties;
21 | /** 显示的图片 */
22 | img?: ReactNode;
23 | /** 头像传参,参考avatar组件*/
24 | avatarProps?: PropsWithChildren;
25 | /** 头像旁区域 */
26 | idText?: ReactNode;
27 | /** 底部标题,也可以通过children自定义 */
28 | bottomTitle?: ReactNode;
29 | /** 底部描述,也可以通过children自定义 */
30 | bottomDescription?: ReactNode;
31 | /** 卡片底座颜色 */
32 | cardType?: normalType;
33 | /** 卡片底座凹凸 */
34 | elevateCard?: "block" | "wrapper";
35 | /** 卡片上部颜色 */
36 | imgType?: normalType;
37 | /** 卡片上部凹凸 */
38 | elevateImg?: "block" | "wrapper";
39 | /** 卡片底部颜色 */
40 | bottomType?: normalType;
41 | /** 卡片底部凹凸 */
42 | elevateBottom?: "block" | "wrapper";
43 | /** 外部容器额外类名 */
44 | className?: string;
45 | }
46 |
47 | function Card(props: PropsWithChildren) {
48 | const {
49 | type,
50 | style,
51 | img,
52 | cardType,
53 | imgType,
54 | className,
55 | bottomType,
56 | elevateCard,
57 | elevateImg,
58 | elevateBottom,
59 | children,
60 | bottomTitle,
61 | bottomDescription,
62 | avatarProps,
63 | idText
64 | } = props;
65 | const wrapperClass = classNames("bigbear-card-wrapper", className, {
66 | [`bigbear-layout-${elevateCard}-${cardType}`]: cardType
67 | });
68 | const imgClass = classNames("bigbear-card-img", {
69 | [`bigbear-layout-${elevateImg}-${imgType}`]: imgType
70 | });
71 | const bottomClass = classNames("bigbear-card-bottom", {
72 | [`bigbear-layout-${elevateBottom}-${bottomType}`]: bottomType
73 | });
74 | return (
75 |
76 |
77 | {type === "default" && img}
78 |
79 | {type === "id" && (
80 |
84 | )}
85 |
86 |
87 |
{bottomTitle}
88 |
{bottomDescription}
89 | {children}
90 |
91 |
92 | );
93 | }
94 |
95 | Card.defaultProps = {
96 | type: "default",
97 | cardType: "default",
98 | elevateCard: "block",
99 | imgType: "default",
100 | elevateImg: "block",
101 | bottomType: "default",
102 | elevateBottom: "block"
103 | };
104 |
105 | export default Card;
106 |
--------------------------------------------------------------------------------
/src/components/Card/index.tsx:
--------------------------------------------------------------------------------
1 | import Card from "./card";
2 |
3 | export default Card;
4 |
--------------------------------------------------------------------------------
/src/components/Carousel/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-carousel-wrapper{
2 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
3 |
4 | padding:10px;
5 | .bigbear-carousel-viewport{
6 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
7 |
8 | border-radius: 10px;
9 | display: flex;
10 | overflow: hidden;
11 | position: relative;
12 |
13 | }
14 | .bigbear-carousel-ul{
15 | align-items: center;
16 | display: flex;
17 | justify-content: center;
18 | list-style-type: none;
19 | padding:10px;
20 | .bigbear-carousel-li{
21 | @include ringwrapper($white,$neu-whiteshadow1,$neu-whiteshadow2);
22 | @include square(16px);
23 |
24 | border-radius: 50%;
25 | cursor: pointer;
26 | margin:5px;
27 | position: relative;
28 | &::after{
29 | @include neufactory-noactive($primary,$neu-blueshadow1,$neu-blueshadow2);
30 | @include square(10px);
31 |
32 | border-radius: 50%;
33 | content: '';
34 | display: block;
35 | left:3px;
36 | opacity: 0;
37 | position: absolute;
38 | top:3px;
39 | transform: scale(0);
40 | transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s;
41 | }
42 | &.carousel-active::after{
43 | @include neufactory-noactive($primary,$neu-blueshadow1,$neu-blueshadow2);
44 | @include square(10px);
45 |
46 | border-radius: 50%;
47 | content: '';
48 | display: block;
49 | left:3px;
50 | opacity: 1;
51 | position: absolute;
52 | top:3px;
53 | transform: scale(1);
54 | transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s;
55 | }
56 | }
57 | }
58 | }
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | .left{
67 | left:300px;
68 | transform: translateX(33.333%);
69 | transition: all 0.5s ease
70 | }
71 |
72 | .left.bigbear-carousel-exit-active {
73 | transform: translateX(33.333%);
74 | transition: none;
75 | }
76 | .left.bigbear-carousel-enter {
77 | transform: translateX(33.333%);
78 | transition: all 0.5s ease;
79 | }
80 | .left.bigbear-carousel-enter-active {
81 | transform: translateX(33.333%);
82 | transition: all 0.5s ease;
83 | }
84 | .left.bigbear-carousel-enter-done{
85 | transform: translateX(0);
86 | transition: all 0.5s ease;
87 | }
88 |
89 |
90 | .right{
91 | transform: translateX(-33.333%);
92 | }
93 |
94 | .right.bigbear-carousel-exit-active {
95 | transform: translateX(-33.333%);
96 | transition: all 0.5s ease;
97 | }
98 | .right.bigbear-carousel-enter {
99 | transform: translateX(-33.333%);
100 | transition: all 0.5s ease;
101 | }
102 | .right.bigbear-carousel-enter-active {
103 | transform: translateX(0%);
104 | transition: all 0.5s ease;
105 | }
106 | .right.bigbear-carousel-enter-done{
107 | transform: translateX(0%);
108 | transition: all 0.5s ease;
109 | }
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/src/components/Carousel/index.tsx:
--------------------------------------------------------------------------------
1 | import Carousel from "./carousel";
2 |
3 | export default Carousel;
4 |
--------------------------------------------------------------------------------
/src/components/CheckBox/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-checkbox-wrapper{
2 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
3 |
4 | display: inline-block;
5 | padding: 5px;
6 | .bigbear-checkbox-label {
7 | cursor: pointer;
8 | margin:0;
9 | margin-left:5px;
10 | margin-right: 5px;
11 | position: relative;
12 | .bigbear-checkbox-input{
13 | opacity: 0;
14 | position: absolute;
15 | }
16 | .bigbear-checkbox-dot {
17 | @include square(20px);
18 | @include ringwrapper($white,$neu-whiteshadow1,$neu-whiteshadow2);
19 |
20 | display: inline-block;
21 | position: relative;
22 | vertical-align: sub;
23 |
24 | }
25 | .bigbear-checkbox-dot::before{
26 | @include size(3px,15px);
27 | @include neufactory-noactive($primary,$neu-blueshadow1,$neu-blueshadow2);
28 |
29 | content: '';
30 | left:5px;
31 | opacity: 0;
32 | position: absolute;
33 | top:5px;
34 | transform:rotate(45deg) scale(0) translate(140%,-40%);
35 | transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;
36 | }
37 | .bigbear-checkbox-dot::after{
38 | @include size(3px,9px);
39 | @include neufactory-noactive($primary,$neu-blueshadow1,$neu-blueshadow2);
40 |
41 | content: '';
42 | left:5px;
43 | opacity: 0;
44 | position: absolute;
45 | top:5px;
46 | transform:rotate(135deg) scale(0) translate(90%,-35%);
47 | transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;
48 | }
49 | .bigbear-checkbox-dot.checkbox-active::before{
50 | @include size(3px,15px);
51 | @include neufactory-noactive($primary,$neu-blueshadow1,$neu-blueshadow2);
52 |
53 | content: '';
54 | left:5px;
55 | opacity: 1;
56 | position: absolute;
57 | top:5px;
58 | transform:rotate(45deg) scale(1) translate(140%,-40%);
59 | transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;
60 | }
61 | .bigbear-checkbox-dot.checkbox-active::after{
62 | @include size(3px,9px);
63 | @include neufactory-noactive($primary,$neu-blueshadow1,$neu-blueshadow2);
64 |
65 | content: '';
66 | left:5px;
67 | opacity: 1;
68 | position: absolute;
69 | top:5px;
70 | transform:rotate(135deg) scale(1) translate(90%,-35%);
71 | transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;
72 | }
73 | .bigbear-checkbox-value{
74 | padding:5px;
75 | text-shadow: 1px 1px 4px $neu-whiteshadow1, -1px -1px 4px $neu-whiteshadow2;
76 | }
77 | .checkbox-active ~ .bigbear-checkbox-value{
78 | color:$primary;
79 | transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;
80 | }
81 | &.checkbox-disabled{
82 | cursor: not-allowed;
83 | opacity: 0.5;
84 | }
85 | }
86 | }
--------------------------------------------------------------------------------
/src/components/CheckBox/checkbox.example.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import CheckBox from "./checkbox";
3 |
4 | export function CheckBoxExample() {
5 | const data = ["check1", "check2", "check3"];
6 | const [state, setState] = useState([]);
7 | return (
8 |
9 | {
12 | e[0]
13 | ? setState(new Array(data.length).fill(true))
14 | : setState(new Array(data.length).fill(false));
15 | }}
16 | >
17 |
18 | {
21 | e[i] = !e[i];
22 | setState([...e]);
23 | }}
24 | parentState={state}
25 | >
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/CheckBox/checkbox.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import CheckBox from './checkbox';
3 | import {CheckBoxExample} from './checkbox.example'
4 |
5 |
6 |
7 |
8 |
9 | # CheckBox 多选按钮
10 |
11 |
12 |
13 | ## 基本使用
14 |
15 |
16 |
17 | console.log(e)} >
18 |
19 |
20 |
21 |
22 | ## 默认选中 defaultIndexArr
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | ## 禁用 disableIndex
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | ## 无文字 notext
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | ## 全选
49 |
50 | 全选需要自行封装,利用checkbox的状态转移来制作,排斥等特殊规则制作同理:👇
51 |
52 | 注意:使用状态转移状态交由父组件处理,组件内不维护状态,初始值等属性无效。
53 |
54 | ```jsx
55 | export function CheckBoxExample(){
56 | const data=['check1','check2','check3']
57 | const [state,setState]=useState([])
58 | return(
59 |
60 | {
61 | e[0]?setState(new Array(data.length).fill(true)):setState(new Array(data.length).fill(false))
62 | }}>
63 |
64 | {
65 | e[i]=!e[i]
66 | setState([...e])
67 | }} parentState={state}>
68 |
69 | )
70 | }
71 | ```
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | ## 属性详情
85 |
86 |
--------------------------------------------------------------------------------
/src/components/CheckBox/checkbox.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, PropsWithChildren, useMemo, CSSProperties } from "react";
2 | import classnames from "classnames";
3 |
4 | interface CheckBoxProps {
5 | /** 数据*/
6 | data: Array;
7 | /** 默认选中索引*/
8 | defaultIndexArr?: Array;
9 | /** 回调值 */
10 | callback?: (arr: Array) => void;
11 | /** 额外类名 */
12 | className?: string;
13 | /** 禁用索引 */
14 | disableIndex?: Array;
15 | /** 控制转移的onchange回调 */
16 | parentSetStateCallback?: (e: boolean[], index: number) => void;
17 | /**控制转移的state */
18 | parentState?: Array;
19 | /** 是否显示文字 */
20 | text?: boolean;
21 | /** 外层容器样式,有时可能需要把box-shadow设置none*/
22 | style?: CSSProperties;
23 | }
24 |
25 | function CheckBox(props: PropsWithChildren) {
26 | const {
27 | defaultIndexArr,
28 | callback,
29 | data,
30 | className,
31 | disableIndex,
32 | parentSetStateCallback,
33 | parentState,
34 | style,
35 | text
36 | } = props;
37 | const classes = classnames("bigbear-checkbox-wrapper", className);
38 |
39 | const disableRef = useMemo(() => {
40 | let arr = new Array(data.length).fill(false);
41 | if (disableIndex) {
42 | disableIndex.forEach((v) => (arr[v] = true));
43 | }
44 | return arr;
45 | }, [data.length, disableIndex]);
46 | const initArr = useMemo(() => {
47 | let arr = new Array(data.length).fill(false);
48 | if (defaultIndexArr) {
49 | defaultIndexArr.forEach((v) => (arr[v] = true));
50 | }
51 | return arr;
52 | }, [data.length, defaultIndexArr]);
53 | const [state, setState] = useState>(initArr);
54 | return (
55 |
56 | {data.map((value, index) => {
57 | const judgeStateIndex = parentState ? parentState[index] : state[index];
58 | return (
59 |
65 | {
70 | if (parentState) {
71 | if (parentSetStateCallback)
72 | parentSetStateCallback(parentState, index);
73 | if (callback) callback(parentState);
74 | } else {
75 | if (!disableRef[index]) {
76 | state[index] = !state[index];
77 | setState([...state]);
78 | if (callback) callback(state);
79 | }
80 | }
81 | }}
82 | >
83 |
88 | {text ? {value} : null}
89 |
90 | );
91 | })}
92 |
93 | );
94 | }
95 |
96 | CheckBox.defaultProps = {
97 | data: [],
98 | text: true
99 | };
100 |
101 | export default CheckBox;
102 |
--------------------------------------------------------------------------------
/src/components/CheckBox/index.tsx:
--------------------------------------------------------------------------------
1 | import CheckBox from "./checkbox";
2 |
3 | export default CheckBox;
4 |
--------------------------------------------------------------------------------
/src/components/Divider/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-divider{
2 | clear:both;
3 | width: 100%;
4 | display: flex;
5 | margin:$divider-margin;
6 | line-height:$divider-line-height;
7 | white-space: nowrap;
8 | align-items: center;
9 | .bigbear-divider-inner {
10 | width: 100%;
11 | height: $divider-height;
12 | border-radius: 50px;
13 | @include ringwrapper($white,$neu-whiteshadow1,$neu-whiteshadow2);
14 | &.bigbear-divider-el{
15 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
16 | }
17 | }
18 | .bigbear-divider-span{
19 | display: inline-block;
20 | padding: 0 1em;
21 | font-weight: $font-weight-bold;
22 |
23 | @include neu-textshadow($neu-whiteshadow1,$neu-whiteshadow2);
24 | }
25 | &.bigbear-divider-center::before{
26 | position: relative;
27 | content:'';
28 | height: $divider-height;
29 | border-radius: 50px;
30 | width: 50%;
31 | margin-top: $divider-height / 2;
32 | @include ringwrapper($white,$neu-whiteshadow1,$neu-whiteshadow2);
33 | }
34 | &.bigbear-divider-center::after{
35 | position: relative;
36 | content:'';
37 | height: $divider-height;
38 | border-radius: 50px;
39 | width: 50%;
40 | margin-top: $divider-height / 2;
41 | @include ringwrapper($white,$neu-whiteshadow1,$neu-whiteshadow2);
42 | }
43 |
44 | &.bigbear-divider-left::after{
45 | position: relative;
46 | content:'';
47 | height: $divider-height;
48 | border-radius: 50px;
49 | width: 100%;
50 | margin-top: $divider-height / 2;
51 | @include ringwrapper($white,$neu-whiteshadow1,$neu-whiteshadow2);
52 | }
53 | &.bigbear-divider-right::before{
54 | position: relative;
55 | content:'';
56 | height: $divider-height;
57 | border-radius: 50px;
58 | width:100%;
59 | margin-top: $divider-height / 2;
60 | @include ringwrapper($white,$neu-whiteshadow1,$neu-whiteshadow2);
61 | }
62 |
63 | &.bigbear-divider-elevate::after{
64 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
65 | }
66 | &.bigbear-divider-elevate::before{
67 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
68 | }
69 | }
--------------------------------------------------------------------------------
/src/components/Divider/divider.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import Divider from './divider'
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | # Divider 分割线
11 |
12 |
13 | ## 基本使用
14 |
15 | 两种类型,一种凸起,一种凹下。
16 |
17 |
18 |
19 |
20 |
21 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
22 |
23 |
24 |
25 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
26 |
27 |
28 |
29 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
30 |
31 |
32 |
33 |
34 |
35 | ## 可以传文字并且设定方位
36 |
37 |
38 |
39 |
40 |
41 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
42 |
43 |
Text
44 |
45 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
46 |
47 |
Text
48 |
49 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
60 |
61 |
Text
62 |
63 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
64 |
65 |
Text
66 |
67 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
78 |
79 |
Text
80 |
81 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
82 |
83 |
Text
84 |
85 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
86 |
87 |
88 |
89 |
90 |
91 |
92 | ## 属性详情
93 |
94 |
95 |
--------------------------------------------------------------------------------
/src/components/Divider/divider.tsx:
--------------------------------------------------------------------------------
1 | import React, { PropsWithChildren, CSSProperties, DOMAttributes } from "react";
2 | import classnames from "classnames";
3 |
4 | interface DividerProps extends DOMAttributes {
5 | /** 是否凸起 */
6 | elevate?: boolean;
7 | /** 文字方位 */
8 | direction: "center" | "left" | "right";
9 | /** 额外类名 */
10 | className?: string;
11 | /** 容器样式*/
12 | style?: CSSProperties;
13 | }
14 |
15 | function Divider(props: PropsWithChildren) {
16 | const { className, style, direction, children, elevate, ...restProps } = props;
17 | const classes = classnames("bigbear-divider", className, {
18 | [`bigbear-divider-${direction}`]: children,
19 | "bigbear-divider-elevate": elevate
20 | });
21 | const innerspan = classnames("bigbear-divider-span", className, {
22 | "bigbear-divider-elevate": elevate
23 | });
24 | return (
25 |
26 | {children &&
{children} }
27 | {!children && (
28 |
33 | )}
34 |
35 | );
36 | }
37 |
38 | Divider.defaultProps = {
39 | elevate: true,
40 | direction: "center"
41 | };
42 | export default Divider;
43 |
--------------------------------------------------------------------------------
/src/components/Divider/index.tsx:
--------------------------------------------------------------------------------
1 | import Divider from "./divider";
2 |
3 | export default Divider;
4 |
--------------------------------------------------------------------------------
/src/components/Form/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-form{
2 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
3 |
4 | padding:20px;
5 | .bigbear-form-validate{
6 | color:$red;
7 | font-size: 13px;
8 | margin-bottom: $input-margin;
9 | margin-top: $input-margin;
10 | }
11 | }
--------------------------------------------------------------------------------
/src/components/Form/form.example.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import useForm from "../../hooks/useForm";
3 | import Input from "../Input";
4 | import CheckBox from "../CheckBox";
5 | import Button from "../Button";
6 | import Form from "./form";
7 |
8 | function FormExample() {
9 | const [handleSubmit, callbackObj, validate, blurObj] = useForm([
10 | {
11 | name: "input1",
12 | validate: [{ validate: (e) => e !== "", message: "用户名不能为空" }]
13 | },
14 | {
15 | name: "input2",
16 | validate: [
17 | { validate: (e) => e !== "", message: "密码不为空" },
18 | {
19 | validate: (e) => e.length > 2 && e.length < 7,
20 | message: "密码必须大于2位或者小于7位"
21 | }
22 | ]
23 | },
24 | {
25 | name: "checkbox",
26 | validate: [
27 | {
28 | validate: (e) => e.filter((v: boolean) => v).length === 3,
29 | message: "必须3个都选上"
30 | }
31 | ]
32 | }
33 | ]);
34 | const onSubmit = (data: any) => console.log(data);
35 | return (
36 |
73 | );
74 | }
75 |
76 | export default FormExample;
77 |
--------------------------------------------------------------------------------
/src/components/Form/form.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import FormExample from './form.example';
3 | import Form from './form'
4 | import { linkTo } from '@storybook/addon-links'
5 | import Button from '../Button'
6 |
7 |
8 |
9 |
10 |
11 | # Form 表单
12 |
13 |
14 |
15 | ## 基本使用
16 |
17 | Form组件就是个壳,需要结合useForm使用,useForm相比于传统封装方法灵活性更大,性能更好。
18 |
19 | 跳转至useForm
20 |
21 |
22 |
23 |
24 |
25 |
28 | Login example
29 |
30 |
31 |
34 | Register example
35 |
36 |
37 | ```jsx
38 | function FormExample(){
39 | const [handleSubmit,callbackObj,validate,blurObj]=useForm([
40 | {
41 | name:'input1',
42 | validate:[{validate:(e)=>e!=='',message:'用户名不能为空'}]
43 | },
44 | {
45 | name: 'input2',
46 | validate:[
47 | {validate:(e)=>e!=='',message:'密码不为空'},
48 | {validate:(e)=>e.length>2&&e.length<7,message:'密码必须大于2位或者小于7位'}
49 | ]
50 | },
51 | {
52 | name:'checkbox',
53 | validate:[{validate:(e)=>e.filter((v:boolean)=>v).length===3,message:'必须3个都选上'}],
54 | }])
55 | const onSubmit=(data:any)=>console.log(data)
56 | return(
57 |
77 | )
78 | }
79 | ```
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | ## 属性详情
90 |
91 |
--------------------------------------------------------------------------------
/src/components/Form/form.tsx:
--------------------------------------------------------------------------------
1 | import React, { PropsWithChildren, DOMAttributes } from "react";
2 | import classnames from "classnames";
3 |
4 | interface FormProps extends DOMAttributes {
5 | /** 额外类名*/
6 | className?: string;
7 | }
8 |
9 | function Form(props: PropsWithChildren) {
10 | const { children, className, ...restProps } = props;
11 | const classes = classnames("bigbear-form", className);
12 |
13 | return (
14 |
15 | {children && children}
16 |
17 | );
18 | }
19 |
20 | export default Form;
21 |
--------------------------------------------------------------------------------
/src/components/Form/index.tsx:
--------------------------------------------------------------------------------
1 | import Form from "./form";
2 |
3 | export default Form;
4 |
--------------------------------------------------------------------------------
/src/components/Grid/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-grid-row{
2 | display: flex;
3 | align-items: center;
4 | }
5 | .bigbear-grid-col{
6 | position: relative;
7 | }
8 |
9 |
10 | @each $key,$val in $percent {
11 | @media screen and (max-width:$media-width-xs) {
12 | .bigbear-col-xs-#{$key}{
13 | width: #{$val}#{"%"};
14 | }
15 | };
16 | }
17 | //有权重问题,分开循环
18 |
19 | @each $key,$val in $percent {
20 | @media screen and (min-width:$media-width-sm) {
21 | .bigbear-col-sm-#{$key}{
22 | width: #{$val}#{"%"};
23 | }
24 | };
25 | }
26 | @each $key,$val in $percent {
27 | @media screen and (min-width:$media-width-md) {
28 | .bigbear-col-md-#{$key}{
29 | width: #{$val}#{"%"};
30 | }
31 | };
32 |
33 | }
34 | @each $key,$val in $percent {
35 | @media screen and (min-width:$media-width-lg) {
36 | .bigbear-col-lg-#{$key}{
37 | width: #{$val}#{"%"};
38 | }
39 | };
40 | }
41 | @each $key,$val in $percent {
42 | @media screen and (min-width:$media-width-xl) {
43 | .bigbear-col-xl-#{$key}{
44 | width: #{$val}#{"%"};
45 | }
46 | };
47 | }
48 | @each $key,$val in $percent {
49 | @media screen and (min-width:$media-width-xxl) {
50 | .bigbear-col-xxl-#{$key}{
51 | width: #{$val}#{"%"};
52 | }
53 | };
54 | }
--------------------------------------------------------------------------------
/src/components/Grid/grid.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import Row,{Col} from './grid'
3 |
4 |
5 |
6 |
7 |
8 |
9 | # Grid 栅格
10 |
11 |
12 | ## 基本使用
13 |
14 |
15 | grid 是由Row与Col组成的12栅格布局。
16 |
17 | xs:<576px
18 | sm:≥576px
19 | md:≥768px
20 | lg:≥992px
21 | xl:≥1200px
22 | xxl:≥1600px
23 |
24 | Row是一行,col是行内元素。简单情况就小于576和大于576就可以了。一行相同种类数字之和等于12。
25 |
26 |
27 |
28 |
29 |
30 | 12
31 |
32 |
33 | 6
34 | 6
35 |
36 |
37 | 7
38 | 5
39 |
40 |
41 | 8
42 | 4
43 |
44 |
45 | 3
46 | 9
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | 屏幕大于992时 6,4,3分配,屏幕576-992之间2,2,8分配,小于576时1,1,1分配:
55 |
56 |
57 |
58 |
59 | lg6
60 | lg4
61 | lg2
62 |
63 |
64 |
65 |
66 |
67 | ## 属性详情
68 |
69 | ### Row属性
70 |
71 |
72 |
73 | ### Col属性
74 |
75 |
--------------------------------------------------------------------------------
/src/components/Grid/grid.tsx:
--------------------------------------------------------------------------------
1 | import React, { CSSProperties, PropsWithChildren, DOMAttributes } from "react";
2 | import classnames from "classnames";
3 |
4 | interface ColProps extends DOMAttributes {
5 | /** 额外类名*/
6 | className?: string;
7 | /** 样式*/
8 | style?: CSSProperties;
9 | /** xs:<576px*/
10 | xs?: number;
11 | /** sm:≥576px */
12 | sm?: number;
13 | /** md :≥768px */
14 | md?: number;
15 | /**lg:≥992px */
16 | lg?: number;
17 | /**xl:≥1200px*/
18 | xl?: number;
19 | /**xxl:≥1600px*/
20 | xxl?: number;
21 | }
22 |
23 | function Col(props: PropsWithChildren) {
24 | const { className, style, xxl, xs, sm, md, lg, xl, children, ...restProps } = props;
25 | const classes = classnames("bigbear-grid-col", className, {
26 | [`bigbear-col-xs-${xs}`]: typeof xs === "number",
27 | [`bigbear-col-sm-${sm}`]: typeof sm === "number",
28 | [`bigbear-col-md-${md}`]: typeof md === "number",
29 | [`bigbear-col-lg-${lg}`]: typeof lg === "number",
30 | [`bigbear-col-xl-${xl}`]: typeof xl === "number",
31 | [`bigbear-col-xxl-${xxl}`]: typeof xxl === "number"
32 | });
33 | return (
34 |
35 | {children}
36 |
37 | );
38 | }
39 |
40 | interface RowProps extends DOMAttributes {
41 | /** 额外类名*/
42 | className?: string;
43 | /** 样式*/
44 | style?: CSSProperties;
45 | }
46 |
47 | function Row(props: PropsWithChildren) {
48 | const { className, style, children, ...restProps } = props;
49 | const classes = classnames("bigbear-grid-row", className);
50 | return (
51 |
52 | {children}
53 |
54 | );
55 | }
56 |
57 | Row.Col = Col;
58 |
59 | export default Row;
60 |
61 | export { Col };
62 |
--------------------------------------------------------------------------------
/src/components/Grid/index.tsx:
--------------------------------------------------------------------------------
1 | import Row from "./grid";
2 |
3 | export default Row;
4 |
5 | const Col = Row.Col;
6 | export { Col };
7 |
--------------------------------------------------------------------------------
/src/components/I18n/_style.scss:
--------------------------------------------------------------------------------
1 | _
--------------------------------------------------------------------------------
/src/components/I18n/i18n.example.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import I18n, { Context } from "./i18n";
3 | import Button from "../Button";
4 | import Badge from "../Badge";
5 |
6 | const zh = {
7 | language: "语言",
8 | apple: "苹果"
9 | };
10 | const en = {
11 | language: "english language",
12 | apple: "english apple"
13 | };
14 | const combinedLibrary = {
15 | zh,
16 | en
17 | };
18 | export default function I18nExampleApp() {
19 | return (
20 |
21 |
22 |
23 | );
24 | }
25 | function Child() {
26 | let { state, toggle } = useContext(Context);
27 | return (
28 |
29 |
30 |
31 |
32 | {
34 | toggle("zh");
35 | }}
36 | >
37 | 切换中文
38 |
39 |
40 |
{
42 | toggle("en");
43 | }}
44 | >
45 | 切换英文
46 |
47 |
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/src/components/I18n/i18n.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import I18n from './i18n';
3 | import I18nExampleApp from './i18n.example'
4 |
5 |
6 |
7 |
8 |
9 | # I18n 国际化
10 |
11 |
12 | ## 基本使用
13 |
14 | 传入默认语言和library即可。
15 | 把组件放在根节点上。
16 | 需要使用国际化地方直接useContext即可。
17 | 类组件使用Context.Consumer拿到Context上的state。
18 |
19 | 默认名需要等于combinedLibrary的key名。
20 | 进行切换时,传入的也是combinedLibrary的键名。
21 |
22 |
23 |
24 |
25 |
26 | ```jsx
27 | const zh = {
28 | language: "语言",
29 | apple: "苹果"
30 | };
31 | const en = {
32 | language: "english language",
33 | apple: "english apple"
34 | };
35 | const combinedLibrary = {
36 | zh,
37 | en
38 | };
39 | export default function I18nExampleApp() {
40 | return (
41 |
42 |
43 |
44 | );
45 | }
46 | function Child() {
47 | let { state, toggle } = useContext(Context);
48 | return (
49 |
50 |
51 |
52 |
53 | {
55 | toggle("zh");
56 | }}
57 | >
58 | 切换中文
59 |
60 |
61 |
{
63 | toggle("en");
64 | }}
65 | >
66 | 切换英文
67 |
68 |
69 | );
70 | }
71 | ```
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | ## 属性详情
84 |
85 |
--------------------------------------------------------------------------------
/src/components/I18n/i18n.tsx:
--------------------------------------------------------------------------------
1 | import React, { PropsWithChildren, useState, useMemo } from "react";
2 |
3 | export interface I18nDataSource {
4 | [key: string]: string;
5 | }
6 |
7 | export interface I18nCombinedSource {
8 | [key: string]: I18nDataSource;
9 | }
10 |
11 | export interface contextType {
12 | state: I18nDataSource;
13 | toggle: (str: string) => void;
14 | }
15 |
16 | export let Context = React.createContext({ state: {}, toggle: () => {} });
17 |
18 | export interface I18nProps {
19 | /** 默认语言*/
20 | defaultLang: keyof I18nDataSource;
21 | /** 语言库 */
22 | library: I18nCombinedSource;
23 | }
24 |
25 | function I18n(props: PropsWithChildren) {
26 | const { defaultLang, library, children } = props;
27 | const [state, setState] = useState(library[defaultLang] || {});
28 | let toggle = useMemo(() => {
29 | return function(str: keyof I18nDataSource) {
30 | if (library[str]) {
31 | setState(library[str]);
32 | }
33 | };
34 | }, [library]);
35 | return {children} ;
36 | }
37 |
38 | export default I18n;
39 |
--------------------------------------------------------------------------------
/src/components/I18n/index.tsx:
--------------------------------------------------------------------------------
1 | import I18n from "./i18n";
2 |
3 | export default I18n;
4 |
--------------------------------------------------------------------------------
/src/components/Icon/_style.scss:
--------------------------------------------------------------------------------
1 | @each $key,$val in $theme-colors{
2 | .icon-#{$key}{
3 | color:#{nth($val,1)};
4 | }
5 | }
6 |
7 | @keyframes fa-spin{
8 | 0%{
9 | filter: blur(0px);
10 | transform:rotate(0deg)
11 | }
12 | 100%{
13 | filter: blur(0px);
14 | transform: rotate(360deg);}
15 | }
--------------------------------------------------------------------------------
/src/components/Icon/icon.test.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, RenderResult } from "@testing-library/react";
3 | import Icon, { ThemeProps } from "./icon";
4 | import { FontAwesomeIconProps } from "@fortawesome/react-fontawesome";
5 | import { IconProp } from "@fortawesome/fontawesome-svg-core";
6 |
7 | const testIcon = (icon: IconProp, theme: ThemeProps, size?: FontAwesomeIconProps["size"]) => {
8 | return ;
9 | };
10 |
11 | describe("test Icon component", () => {
12 | let wrapper: RenderResult;
13 | beforeEach(() => {
14 | wrapper = render(testIcon("coffee", "success", "5x"));
15 | });
16 | it("should render icon ", () => {
17 | let svg = wrapper.container.querySelector("svg");
18 | expect(svg).toBeTruthy();
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/src/components/Icon/index.tsx:
--------------------------------------------------------------------------------
1 | import Icon from "./icon";
2 |
3 | export default Icon;
4 |
--------------------------------------------------------------------------------
/src/components/Input/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-input-wrapper{
2 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
3 | align-items: center;
4 | display: flex;
5 | margin-bottom: $input-margin;
6 | overflow: hidden;
7 | position: relative;
8 | input{
9 | @include ringwrapper($white,$neu-whiteshadow1,$neu-whiteshadow2);
10 |
11 | background: transparent;
12 | border:none;
13 | color:$gray-7;
14 | flex-grow:1;
15 | flex-shrink: 0;
16 | font-size: $input-fontsize;
17 | height: $input-height;
18 | outline: none;
19 | padding:0 10px;
20 | width: 10px;
21 | }
22 | input:disabled{
23 | background-color: $gray-2;
24 | cursor: not-allowed;
25 | opacity: 0.5;
26 | }
27 | input::placeholder{
28 | color:$gray-5;
29 | }
30 | .bigbear-input-group-prepend{
31 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
32 |
33 | align-items: center;
34 | display: flex;
35 | flex-shrink: 1;
36 | height: $input-height;
37 | justify-content: flex-start;
38 | padding:0 10px;
39 | }
40 | .bigbear-input-group-append{
41 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
42 |
43 | align-items: center;
44 | display: flex;
45 | flex-shrink: 1;
46 | height: $input-height;
47 | justify-content: flex-end;
48 | padding:0 10px;
49 | }
50 | }
--------------------------------------------------------------------------------
/src/components/Input/index.tsx:
--------------------------------------------------------------------------------
1 | import Input from "./input";
2 |
3 | export default Input;
4 |
--------------------------------------------------------------------------------
/src/components/Input/input.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import Icon from '../Icon';
3 | import Input from './input';
4 | import Button from '../Button'
5 |
6 |
7 |
8 |
9 |
10 |
11 | # Input 输入框
12 |
13 |
14 | ## 基本用法
15 |
16 | input本身是受控组件,可以通过传递setValueCallback使得状态转移至父组件。并且还可以通过ref回调操作实例。
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
} append={append
}>
25 | }>
26 | password:}>
27 | console.log(e.target.value)} prepend='回调' >
28 |
29 |
30 | ref.current.focus()}>
31 | 按钮} append={ } >
32 | 高度height} append={ } >
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | ## 属性详情
41 |
42 |
--------------------------------------------------------------------------------
/src/components/Input/input.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | FC,
3 | InputHTMLAttributes,
4 | ReactElement,
5 | ChangeEvent,
6 | useState,
7 | useEffect,
8 | useRef
9 | } from "react";
10 | import classNames from "classnames";
11 |
12 | export interface InputProps extends InputHTMLAttributes {
13 | /**禁用 */
14 | disabled?: boolean;
15 | /**输入框前面内容 */
16 | prepend?: string | ReactElement;
17 | /**输入框后面内容 */
18 | append?: string | ReactElement;
19 | /**受控输入框的回调事件,用来取值 */
20 | callback?: (e: ChangeEvent) => void;
21 | /**ref回调,可以拿到输入框实例 */
22 | refcallback?: (e: any) => void;
23 | /**默认值 */
24 | defaultValue?: string;
25 | /**父组件接管受控组件,父组件传value属性做state */
26 | setValueCallback?: (v: string) => void;
27 | /** 父组件接管状态时传递 */
28 | value?: string | undefined;
29 | /** 组件高度*/
30 | height?: string;
31 | }
32 |
33 | export const Input: FC = (props: InputProps) => {
34 | const {
35 | disabled,
36 | prepend,
37 | append,
38 | style,
39 | callback,
40 | defaultValue,
41 | refcallback,
42 | setValueCallback,
43 | value,
44 | height,
45 | ...restProps
46 | } = props;
47 | const cnames = classNames("bigbear-input-wrapper", {
48 | "is-disabled": disabled,
49 | "input-group-append": !!append,
50 | "input-group-prepend": !!prepend
51 | });
52 | const [inputvalue, setValue] = useState(defaultValue || "");
53 | let ref = useRef(null);
54 | useEffect(() => {
55 | if (refcallback) refcallback(ref);
56 | }, [refcallback, setValueCallback]);
57 |
58 | return (
59 |
60 | {prepend && (
61 |
62 | {prepend}
63 |
64 | )}
65 |
{
72 | setValueCallback ? setValueCallback(e.target.value) : setValue(e.target.value);
73 | if (callback) callback(e);
74 | }}
75 | {...restProps}
76 | />
77 | {append && (
78 |
79 | {append}
80 |
81 | )}
82 |
83 | );
84 | };
85 |
86 | export default Input;
87 |
--------------------------------------------------------------------------------
/src/components/InputNumber/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-inputnumber-wrapper{
2 | position: relative;
3 | display: inline-block;
4 | text-align: center;
5 | .bigbear-inputnumber-next{
6 | display: inline-block;
7 | margin-left: 5px;
8 | }
9 | .bigbear-inputnumber-prev{
10 | display: inline-block;
11 | margin-right: 5px;
12 | }
13 | .bigbear-inputnumber-main{
14 | display: inline-block;
15 | vertical-align: middle;
16 | transition:all 0.2s ease-in-out;
17 | .bigbear-input-wrapper{
18 | margin:0;
19 | font-weight: $font-weight-bold;
20 | border-radius: 5px;
21 | .bigbear-input-inner{
22 | text-align: center;
23 | width: 5px;
24 | font-size: $font-size-sm;
25 | padding:0
26 | }
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/src/components/InputNumber/index.tsx:
--------------------------------------------------------------------------------
1 | import InputNumber from "./inputnumber";
2 |
3 | export default InputNumber;
4 |
--------------------------------------------------------------------------------
/src/components/InputNumber/inputnumber.example.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import InputNumber from "./inputnumber";
3 |
4 | export default function() {
5 | const [state, setState] = useState("0");
6 | return (
7 | console.log(e)}
11 | >
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/InputNumber/inputnumber.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import InputNumber from './inputnumber';
3 | import InputExample from './inputnumber.example'
4 |
5 |
6 |
7 |
8 |
9 | # InputNumber 数字输入框
10 |
11 |
12 | ## 基本使用
13 |
14 | 此组件基于Input组件封装而成。
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | ## 控制上下限
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | ## 步长与初始值
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | ## 按钮初始展开
40 |
41 |
42 | console.log(e)} >
43 |
44 |
45 |
46 | ## 按钮样式&宽度
47 |
48 |
49 | console.log(e)} >
54 |
55 |
56 |
57 |
58 | ## 父组件接管example
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | ```typescript
67 | export default function() {
68 | const [state, setState] = useState("0");
69 | return (
70 | console.log(e)}
74 | >
75 | );
76 | }
77 | ```
78 |
79 |
80 | ## 属性详情
81 |
82 |
--------------------------------------------------------------------------------
/src/components/Layout/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-layout{
2 | display: flex;
3 | flex:auto;
4 | flex-direction: column;
5 | margin:0;
6 | padding: 0;
7 | text-align: center;
8 | .bigbear-layout-header{
9 | height: $header-height;
10 | }
11 | .bigbear-layout-content{
12 | flex:auto;
13 | flex-basis: $content-basis;
14 | }
15 | .bigbear-layout-footer{
16 | font-size: $font-size-sm;
17 | padding: $footer-padding;
18 | }
19 | .bigbear-layout-sider{
20 | flex-basis:$aside-flex-basis;
21 | position: relative;
22 | }
23 | }
24 |
25 | .bigbear-layout.bigbear-layout-row{
26 | flex-direction: row;
27 | flex-wrap: wrap;
28 | }
29 | @media screen and (max-width: #{$content-basis + $aside-flex-basis + 20}) {
30 | .bigbear-layout-sider{
31 | flex-grow: 1;
32 | }
33 | }
34 |
35 |
36 | @each $key,$val in $theme-colors{
37 | .bigbear-layout-block-#{$key}{
38 | @include neufactory-noactive(nth($val,1),nth($val,3),nth($val,4));
39 |
40 | color:#{nth($val,2)};
41 | margin: 0;
42 | padding: 0;
43 | }
44 | .bigbear-layout-wrapper-#{$key}{
45 | @include ringwrapper(nth($val,1),nth($val,3),nth($val,4));
46 |
47 | color:#{nth($val,2)};
48 | margin: 0;
49 | padding:0;
50 | }
51 | .bigbear-layout-lineargradient-#{$key}{
52 | background:linear-gradient(145deg, nth($val,4),nth($val,1))
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/components/Layout/index.tsx:
--------------------------------------------------------------------------------
1 | import Layout from "./layout";
2 |
3 | export default Layout;
4 |
--------------------------------------------------------------------------------
/src/components/Layout/layout.tsx:
--------------------------------------------------------------------------------
1 | import React, { PropsWithChildren, CSSProperties, DOMAttributes } from "react";
2 | import classnames from "classnames";
3 |
4 | export interface LayoutItemProps extends DOMAttributes {
5 | /** 样式*/
6 | style?: CSSProperties;
7 | /** 类名*/
8 | className?: string;
9 | }
10 |
11 | export interface LayoutProps extends LayoutItemProps {
12 | /** 子元素是否横向排列 */
13 | row?: boolean;
14 | }
15 |
16 | function Layout(props: PropsWithChildren) {
17 | const { style, className, row, children, ...restProps } = props;
18 | const classes = classnames("bigbear-layout", className, {
19 | "bigbear-layout-row": row
20 | });
21 | return (
22 |
25 | );
26 | }
27 |
28 | function Header(props: PropsWithChildren) {
29 | const { style, className, children, ...restProps } = props;
30 | const classes = classnames("bigbear-layout-header", className);
31 | return (
32 |
35 | );
36 | }
37 |
38 | function Content(props: PropsWithChildren) {
39 | const { style, className, children, ...restProps } = props;
40 | const classes = classnames("bigbear-layout-content", className);
41 | return (
42 |
43 | {children}
44 |
45 | );
46 | }
47 |
48 | function Sider(props: PropsWithChildren) {
49 | const { style, className, children, ...restProps } = props;
50 | const classes = classnames("bigbear-layout-sider", className);
51 | return (
52 |
55 | );
56 | }
57 |
58 | function Footer(props: PropsWithChildren) {
59 | const { style, className, children, ...restProps } = props;
60 | const classes = classnames("bigbear-layout-footer", className);
61 | return (
62 |
65 | );
66 | }
67 |
68 | Layout.Header = Header;
69 | Layout.Content = Content;
70 | Layout.Sider = Sider;
71 | Layout.Footer = Footer;
72 |
73 | export default Layout;
74 |
--------------------------------------------------------------------------------
/src/components/List/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-list{
2 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
3 |
4 | list-style-type: none;
5 | padding: $list-padding;
6 | }
7 | .list-horizontal{
8 | align-items: center;
9 | display: flex;
10 | flex-wrap: wrap;
11 | justify-content: center;
12 | }
13 | .list-withoveractive{
14 | @include neufactory-hover($primary,$neu-blueshadow1,$neu-blueshadow2);
15 | &:hover,&:active{
16 | color:$white;
17 | }}
18 | .bigbear-list-item{
19 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
20 |
21 | margin:$list-item-margin;
22 | padding: $list-item-padding;
23 |
24 | }
--------------------------------------------------------------------------------
/src/components/List/index.tsx:
--------------------------------------------------------------------------------
1 | import List from "./list";
2 | export default List;
3 |
--------------------------------------------------------------------------------
/src/components/List/list.example.tsx:
--------------------------------------------------------------------------------
1 | export const data = [
2 | "Racing car sprays burning fuel into crowd.",
3 | "Japanese princess to wed commoner.",
4 | "Australian walks 100km after outback crash.",
5 | "Man charged over missing wedding girl.",
6 | "Los Angeles battles huge wildfires."
7 | ];
8 |
--------------------------------------------------------------------------------
/src/components/List/list.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import List from './list';
3 | import {data } from './list.example'
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | # List 列表
13 |
14 |
15 | 为了适应更多用法,类名可以单独抽离出来,需要的地方加上bigbear-list-item即可有样式
16 |
17 | ## 基本使用
18 |
19 | ```jsx
20 | export const data = [
21 | 'Racing car sprays burning fuel into crowd.',
22 | 'Japanese princess to wed commoner.',
23 | 'Australian walks 100km after outback crash.',
24 | 'Man charged over missing wedding girl.',
25 | 'Los Angeles battles huge wildfires.',
26 | ];
27 | ```
28 |
29 |
30 |
31 |
32 |
33 | {data}
34 |
35 |
36 |
37 |
38 | ## horizontal 横向
39 |
40 |
41 |
42 |
43 | {data}
44 |
45 |
46 |
47 |
48 |
49 | ## renderTemplate 使用自定义模板
50 |
51 |
52 |
53 |
54 |
55 | (console.log(e.target)}>{index}
{child}
56 | )}>{data}
57 |
58 |
59 |
60 |
61 | ## withHoverActive 可互动样式
62 |
63 |
64 |
65 |
66 | console.log(e.target)} >{data}
67 |
68 |
69 |
70 |
71 |
72 |
73 | ## 属性详情
74 |
75 |
--------------------------------------------------------------------------------
/src/components/List/list.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | FC,
3 | CSSProperties,
4 | MouseEvent,
5 | ReactNode,
6 | useRef,
7 | useEffect,
8 | DOMAttributes
9 | } from "react";
10 | import classNames from "classnames";
11 |
12 | export interface ListProps extends DOMAttributes {
13 | /** 水平或者垂直 */
14 | mode?: "horizontal" | "vertical";
15 | /** 是否加上hover与active */
16 | withHoverActive?: boolean;
17 | /**ul的click回调 */
18 | onSelect?: (e: MouseEvent) => void;
19 | /**模板进行渲染 */
20 | renderTemplate?: (child: ReactNode, index: number) => ReactNode;
21 | /** ul样式 */
22 | style?: CSSProperties;
23 | /** li样式*/
24 | listyle?: CSSProperties;
25 | /** ul额外类名 */
26 | className?: string;
27 | /** li额外类名 */
28 | liClassName?: string;
29 | /**ref回调*/
30 | refCallback?: (e: HTMLUListElement | null) => void;
31 | }
32 |
33 | export const List: FC = (props) => {
34 | const {
35 | className,
36 | mode,
37 | style,
38 | children,
39 | onSelect,
40 | renderTemplate,
41 | listyle,
42 | liClassName,
43 | withHoverActive,
44 | refCallback,
45 | ...restProps
46 | } = props;
47 | const classes = classNames("bigbear-list", className, {
48 | "list-vertical": mode === "vertical",
49 | "list-horizontal": mode === "horizontal"
50 | });
51 | const liclasses = classNames("bigbear-list-item", liClassName, {
52 | "list-withoveractive": withHoverActive
53 | });
54 | const handleClick = (e: React.MouseEvent) => {
55 | if (onSelect) onSelect(e);
56 | };
57 | const ulref = useRef(null);
58 | useEffect(() => {
59 | if (refCallback && ulref.current) {
60 | refCallback(ulref.current);
61 | }
62 | }, [refCallback]);
63 | return (
64 | handleClick(e)}
68 | ref={ulref}
69 | {...restProps}
70 | >
71 | {React.Children.map(children, (child, index) => {
72 | const res = renderTemplate ? (
73 |
74 | {renderTemplate(child, index)}
75 |
76 | ) : (
77 |
78 | {child}
79 |
80 | );
81 | return res;
82 | })}
83 |
84 | );
85 | };
86 | List.defaultProps = {
87 | mode: "vertical",
88 | withHoverActive: false
89 | };
90 |
91 | export default List;
92 |
--------------------------------------------------------------------------------
/src/components/Menu/index.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from "react";
2 | import Menu, { MenuProps } from "./menu";
3 | import SubMenu, { SubMenuProps } from "./submenu";
4 | import MenuItem, { MenuItemProps } from "./menuitem";
5 |
6 | export type IMenuComponent = FC & {
7 | MenuItem: FC;
8 | SubMenu: FC;
9 | };
10 | const TransMenu = Menu as IMenuComponent;
11 |
12 | TransMenu.MenuItem = MenuItem;
13 | TransMenu.SubMenu = SubMenu;
14 |
15 | export default TransMenu;
16 |
17 | export { SubMenu, MenuItem };
18 |
--------------------------------------------------------------------------------
/src/components/Menu/menu.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import Menu from './menu';
3 | import MenuItem from './menuitem'
4 | import { linkTo } from '@storybook/addon-links'
5 | import Button from '../Button'
6 |
7 |
8 |
9 |
10 |
11 | # Menu 菜单
12 |
13 |
14 | Menu需要包裹MenuItem进行使用
15 |
16 |
17 |
18 |
19 | ## 菜单类型 mode
20 |
21 |
22 | ### 横向 horizontal
23 |
24 |
25 |
26 | {console.log(index)}}>
27 | item1
28 | item2
29 | item3
30 |
31 |
32 |
33 |
34 |
35 |
36 | ### 纵向 vertical
37 |
38 |
39 |
40 | {console.log(index)}} mode='vertical' >
41 | item1
42 | item2
43 | item3
44 |
45 |
46 |
47 |
48 |
49 |
50 | ## 子项跳转链接
51 |
52 |
53 |
54 |
55 |
56 | 跳转至MenuItem
58 | 跳转至SubMenu
59 |
60 |
61 |
62 |
63 |
64 | ## 属性详情
65 |
66 |
--------------------------------------------------------------------------------
/src/components/Menu/menu.test.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, fireEvent, RenderResult, cleanup, wait } from "@testing-library/react";
3 | import Menu, { MenuProps } from "./menu";
4 | import MenuItem from "./menuitem";
5 | import SubMenu from "./submenu";
6 |
7 | const testProps: MenuProps = {
8 | defaultIndex: "0",
9 | onSelect: jest.fn(),
10 | className: "test"
11 | };
12 | const testVerProps: MenuProps = {
13 | defaultIndex: "0",
14 | mode: "vertical"
15 | };
16 | const testMenu = (props: MenuProps) => {
17 | return (
18 |
19 | active
20 | disabled
21 | yehuozhili
22 |
23 | s22
24 |
25 |
26 | );
27 | };
28 |
29 | let wrapper: RenderResult,
30 | menuElement: HTMLElement,
31 | activeElement: HTMLElement,
32 | disabledElement: HTMLElement;
33 | describe("test Menu and MenuItem component", () => {
34 | beforeEach(() => {
35 | wrapper = render(testMenu(testProps));
36 | menuElement = wrapper.getByTestId("test-menu");
37 | activeElement = wrapper.getByText("active");
38 | disabledElement = wrapper.getByText("disabled");
39 | });
40 | it("should render correct Menu and MenuItem based on default props", () => {
41 | expect(menuElement).toBeInTheDocument();
42 | expect(menuElement).toHaveClass("bigbear-menu");
43 | expect(menuElement.querySelectorAll(":scope > li").length).toEqual(4);
44 | expect(activeElement).toHaveClass("bigbear-menuitem isactive");
45 | expect(disabledElement).toHaveClass("bigbear-menuitem isdisabled");
46 | });
47 | it("click item should change active and call the right callback", () => {
48 | const thirdItem = wrapper.getByText("yehuozhili");
49 | fireEvent.click(thirdItem);
50 | expect(thirdItem).toHaveClass("isactive");
51 | expect(activeElement).not.toHaveClass("isactive");
52 | expect(testProps.onSelect).toHaveBeenCalledWith("2");
53 | fireEvent.click(disabledElement);
54 | expect(disabledElement).not.toHaveClass("isactive");
55 | expect(testProps.onSelect).not.toHaveBeenCalledWith("1");
56 | });
57 | it("should render vertical menu ", () => {
58 | cleanup();
59 | const wrapper = render(testMenu(testVerProps));
60 | const menuElement = wrapper.getByTestId("test-menu");
61 | expect(menuElement).toHaveClass("menu-vertical");
62 | });
63 | it("should show dropdown item when hover on submenu", async () => {
64 | expect(wrapper.queryByText("s22")).toEqual(null);
65 | const dropdownEle = wrapper.getByText("dss");
66 | fireEvent.mouseEnter(dropdownEle);
67 | await (() => {
68 | expect(wrapper.queryByText("dss")?.parentElement).toHaveClass("bigbear-menuopen");
69 | });
70 | fireEvent.mouseLeave(dropdownEle);
71 | await wait(() => {
72 | expect(wrapper.queryByText("dss")?.parentElement).not.toHaveClass("bigbear-menuopen");
73 | });
74 | });
75 | });
76 |
--------------------------------------------------------------------------------
/src/components/Menu/menu.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, createContext, useState, CSSProperties } from "react";
2 |
3 | import classNames from "classnames";
4 | import { MenuItemProps } from "./menuitem";
5 |
6 | type MenuMode = "horizontal" | "vertical";
7 | type SelectCallback = (selectedIndex: string) => void;
8 |
9 | export interface MenuProps {
10 | /**默认激活索引 */
11 |
12 | defaultIndex?: string;
13 | /** 类名 */
14 |
15 | className?: string;
16 | /** 模式 */
17 |
18 | mode?: MenuMode;
19 | /** 样式 */
20 |
21 | style?: CSSProperties;
22 | /** 回调函数 */
23 | onSelect?: SelectCallback;
24 | /** 用户自己逻辑,可以拿到点击索引和修改激活索引的方法,用于制作二次点击取消等效果*/
25 | customHandle?: (
26 | index: string,
27 | current: string | undefined,
28 | setActive: React.Dispatch>
29 | ) => void;
30 | }
31 |
32 | export const Menu: FC = (props) => {
33 | const { className, mode, customHandle, style, children, defaultIndex, onSelect } = props;
34 | const [currentActive, setActive] = useState(defaultIndex);
35 | const classes = classNames("bigbear-menu", className, {
36 | "menu-vertical": mode === "vertical",
37 | "menu-horizontal": mode === "horizontal"
38 | });
39 | const handleClick = (index: string) => {
40 | if (customHandle) {
41 | customHandle(index, currentActive, setActive);
42 | } else {
43 | setActive(index);
44 | }
45 | if (onSelect) onSelect(index);
46 | };
47 | const passedContext: IMenuContext = {
48 | index: currentActive ? currentActive : "0",
49 | onSelect: handleClick,
50 | mode: mode
51 | };
52 |
53 | return (
54 |
55 |
56 | {React.Children.map(children, (child, index) => {
57 | const childElement = child as React.FunctionComponentElement;
58 | if (childElement && childElement.type && childElement.type.displayName) {
59 | const displayName = childElement.type.displayName;
60 | if (displayName === "MenuItem" || displayName === "SubMenu") {
61 | return React.cloneElement(childElement, { index: index + "" });
62 | } else {
63 | console.error("menu children must be menuitem child or submenu child");
64 | }
65 | } else {
66 | console.error(
67 | 'menu child have not props names "type" and "displayName"',
68 | childElement
69 | );
70 | }
71 | })}
72 |
73 |
74 | );
75 | };
76 |
77 | interface IMenuContext {
78 | index: string;
79 | onSelect?: SelectCallback;
80 | mode?: MenuMode;
81 | }
82 |
83 | export const MenuContext = createContext({ index: "0" });
84 |
85 | // typescript react dockgen bug
86 | //const ForwardMenu = React.forwardRef(Menu as RefForwardingComponent)
87 |
88 | Menu.defaultProps = {
89 | defaultIndex: "-1",
90 | mode: "horizontal"
91 | };
92 |
93 | export default Menu;
94 |
--------------------------------------------------------------------------------
/src/components/Menu/menuitem.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import Menu from './menu';
3 | import MenuItem from './menuitem'
4 | import { linkTo } from '@storybook/addon-links'
5 | import Button from '../Button/'
6 | import SubMenu from './submenu'
7 |
8 |
9 |
10 |
11 |
12 |
13 | # MenuItem 菜单项
14 |
15 |
16 | MenuItem是Menu或者SubMenu的子项,使用方法见Menu和SubMenu。
17 |
18 |
19 |
20 |
21 | 跳转至Menu
22 | 跳转至SubMenu
23 |
24 |
25 |
26 |
27 |
28 | ## 属性详情
29 |
30 |
--------------------------------------------------------------------------------
/src/components/Menu/menuitem.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, FC, CSSProperties } from "react";
2 | import { MenuContext } from "./menu";
3 | import classNames from "classnames";
4 |
5 | export interface MenuItemProps {
6 | index?: string;
7 | disabled?: boolean;
8 | className?: string;
9 | style?: CSSProperties;
10 | /** 点了menuitem是否关闭submenu 只对horizontal模式有效*/
11 | close?: boolean;
12 | /** 延迟多久关闭,只在close为true生效*/
13 | delay?: number;
14 | setMenu?: React.Dispatch>;
15 | /** 点击后执行逻辑,通过menu回调也可以拿*/
16 | callback?: (index: string) => void;
17 | }
18 |
19 | export const MenuItem: FC = (props) => {
20 | const { index, disabled, className, style, children, close, setMenu, delay, callback } = props;
21 | const context = useContext(MenuContext);
22 | const classes = classNames("bigbear-menuitem", className, {
23 | isdisabled: disabled,
24 | isactive: context.index === index
25 | });
26 | const handleClick = () => {
27 | if (context.onSelect && !disabled && typeof index === "string") {
28 | context.onSelect(index);
29 | }
30 | if (close && setMenu && context.mode === "horizontal") {
31 | setTimeout(() => {
32 | setMenu(false);
33 | }, delay);
34 | }
35 | if (callback && !disabled && typeof index === "string") {
36 | callback(index);
37 | }
38 | };
39 | return (
40 |
41 | {children}
42 |
43 | );
44 | };
45 |
46 | MenuItem.displayName = "MenuItem";
47 |
48 | MenuItem.defaultProps = {
49 | close: true,
50 | delay: 300
51 | };
52 |
53 | export default MenuItem;
54 |
--------------------------------------------------------------------------------
/src/components/Message/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-message-top{
2 | left:50%;
3 | position: fixed;
4 | top:20px;
5 | transform: translateX(-50%);
6 | }
7 | .bigbear-message-lt{
8 | left: 20px;
9 | position: fixed;
10 | top:20px;
11 | }
12 |
13 | .bigbear-message-rt{
14 | position: fixed;
15 | right: 20px;
16 | top:20px;
17 | }
18 | .bigbear-message-lb{
19 | bottom: 20px;
20 | left:20px;
21 | position: fixed;
22 | }
23 | .bigbear-message-rb{
24 | bottom: 20px;
25 | position: fixed;
26 | right: 20px;
27 | }
28 |
29 | .bigbear-message{
30 | max-width: 300px;
31 | padding:$message-padding;
32 | z-index: 500;
33 | > span:nth-child(1){
34 | font-size: $font-size-sm;
35 | }
36 | > span:nth-child(2){
37 | font-size: $font-size-sm - 1;
38 | }
39 | .btn{
40 | font-size: $font-size-sm - 5;
41 | .bigbear-icon{
42 | vertical-align: middle;
43 | }
44 | }
45 | }
46 |
47 |
48 |
49 | .zoom-in-topmesssage-enter {
50 | opacity: 0;
51 | transform: scaleY(0) translateX(-50%);
52 | }
53 | .zoom-in-topmesssage-enter-active {
54 | opacity: 1;
55 | transform: scaleY(1) translateX(-50%);
56 | transform-origin: center top;
57 | transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1) 100ms, opacity 300ms cubic-bezier(0.23, 1, 0.32, 1) 100ms
58 | }
59 | .zoom-in-topmesssage-enter-done {
60 | opacity: 1;
61 | transform:scaleY(1) translateX(-50%);
62 | }
63 | .zoom-in-topmesssage-exit{
64 | opacity: 0;
65 | transform: scaleY(0) translateX(-50%);
66 | transform-origin:center top;
67 | transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1) 100ms, opacity 300ms cubic-bezier(0.23, 1, 0.32, 1) 100ms;
68 | }
69 |
--------------------------------------------------------------------------------
/src/components/Message/index.tsx:
--------------------------------------------------------------------------------
1 | import Message, { message, MessageProps } from "./message";
2 | import { FC } from "react";
3 |
4 | type TransMessageType = FC & {
5 | message: typeof message;
6 | };
7 | const TransMessage: TransMessageType = Message as TransMessageType;
8 | TransMessage.message = message;
9 |
10 | export default TransMessage;
11 |
--------------------------------------------------------------------------------
/src/components/Message/message.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import Message,{message} from './message'
3 | import Button from '../Button'
4 | import Icon from '../Icon'
5 |
6 |
7 |
8 |
9 |
10 | # Message 全局提示
11 |
12 |
13 | 此组件在Alert组件上做了层封装。
14 |
15 | ## directions 方位
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | ## message 函数调用
30 |
31 | 第一个参数文本,第二个参数是options,可以加上各种配置。
32 |
33 |
34 |
35 |
36 | message.success('成功的消息')}> success
37 | message.warning('警告的消息')}> warning
38 | message.danger('危险的消息')}> danger
39 | message.info('普通通知',{icon:undefined})}> info取消icon
40 | message.default('普通消息',{icon: })}> default+自定义图标
41 | message.primary('主题通知',{directions:'rb'})}> primary
42 | message.secondary('副标题通知',{directions:'lt'})}> secondary
43 | message.light('浅色通知',)}> light
44 | message.dark('深色通知')}> dark
45 |
46 |
47 |
48 |
49 |
50 |
51 | message.info('我是标题',{directions:'rb',description:'我是长文本我是长文本我是我是长文本我是长文本我是长文本我是长文本我是长文本长文本我是长文本我是长文本我是长文本'})}>传递长文本
52 |
53 |
54 |
55 | 不自动关闭需要设置autoclosedelay:0,close:true
56 |
57 |
58 |
59 | message.info('我是标题',{autoclosedelay:0,close:true,directions:'rb',description:'我是长文本我是长文本我是我是长文本我是长文本我是长文本我是长文本我是长文本长文本我是长文本我是长文本我是长文本'
60 | })}>持久化点击关闭
61 |
62 |
63 |
64 | ## 属性详情
65 |
66 |
67 |
--------------------------------------------------------------------------------
/src/components/Modal/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-modal-potral{
2 | bottom: 0;
3 | left:0;
4 | position: fixed;
5 | right: 0;
6 | top:0;
7 | z-index: 1000;
8 | .bigbear-modal-viewport{
9 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
10 |
11 | margin:0 auto;
12 | min-width: 320px;
13 | overflow: hidden;
14 | padding:$modal-padding;
15 | position: relative;
16 | top:100px;
17 | transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1);
18 | width: 30%;
19 | z-index: 1001;
20 | .bigbear-modal-title{
21 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
22 |
23 | font-size: $font-size-lg;
24 | font-weight: $font-weight-bold;
25 | padding: $modal-padding;
26 | .bigbear-icon{
27 | margin-right: 10px;
28 | }
29 | .bigbear-modal-closebtn{
30 | position: absolute;
31 | right: $modal-padding ;
32 | top:$modal-padding ;
33 | .bigbear-icon{
34 | margin-right: 0;
35 | }
36 | }
37 |
38 | }
39 | .bigbear-modal-children{
40 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
41 |
42 | padding:$modal-padding;
43 | }
44 | .bigbear-modal-confirm{
45 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
46 |
47 | display: flex;
48 | justify-content: flex-end;
49 | padding: $modal-padding;
50 | >button{
51 | margin-right: 10px;
52 | }
53 | }
54 |
55 | }
56 | .bigbear-modal-mask{
57 | background-color: rgba(0,0,0,.45);
58 | bottom: 0;
59 | left:0;
60 | position: fixed;
61 | right: 0;
62 | top:0;
63 | }
64 | }
65 |
66 |
67 | .bigbear-modal-animation-enter {
68 | opacity: 0;
69 | transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1) ;
70 | .bigbear-modal-viewport{
71 | transform: scale(0,0);
72 | }
73 | }
74 |
75 | .bigbear-modal-animation-enter-done {
76 | opacity: 1;
77 | transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1) ;
78 | .bigbear-modal-viewport{
79 | transform: scale(1,1);
80 | transform-origin: center top;
81 | }
82 | }
83 | .bigbear-modal-animation-exit {
84 | opacity: 1;
85 | transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1) ;
86 |
87 | }
88 | .bigbear-modal-animation-exit-active {
89 | opacity: 0;
90 | transform-origin:center top;
91 | transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1) ;
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/src/components/Modal/index.tsx:
--------------------------------------------------------------------------------
1 | import Modal from "./modal";
2 |
3 | export default Modal;
4 |
--------------------------------------------------------------------------------
/src/components/Modal/modal.example.tsx:
--------------------------------------------------------------------------------
1 | import Modal from "./modal";
2 | import { useState } from "react";
3 | import React from "react";
4 | import Button from "../Button";
5 | import Icon from "../Icon";
6 |
7 | export function ModalExample() {
8 | const [state, setState] = useState(false);
9 | return (
10 |
11 |
console.log(v)}>
12 | balabalabala
13 | balabalabalalalalallaa
14 |
15 |
setState(!state)}>基本使用
16 |
17 | );
18 | }
19 | export function ModalExample2() {
20 | const [state, setState] = useState(false);
21 | return (
22 |
23 |
console.log(v)}
28 | >
29 | balabalabala
30 | balabalabalalalalallaa
31 |
32 |
setState(!state)}>点击mask不关闭
33 |
34 | );
35 | }
36 |
37 | export function ModalExample3() {
38 | const [state, setState] = useState(false);
39 | return (
40 |
41 |
42 | balabalabala
43 | balabalabalalalalallaa
44 |
45 |
setState(!state)}>改变confirm按钮文本
46 |
47 | );
48 | }
49 |
50 | export function ModalExample4() {
51 | const [state, setState] = useState(false);
52 | return (
53 |
54 |
55 | balabalabala
56 | balabalabalalalalallaa
57 |
58 | setState(false)}>
59 | 自定义ok
60 |
61 | setState(false)}>
62 | 自定义取消
63 |
64 |
65 |
66 |
setState(!state)}>自定义按钮
67 |
68 | );
69 | }
70 |
71 | export function ModalExample5() {
72 | const [state, setState] = useState(false);
73 | return (
74 |
75 |
78 | 带上图标标题
79 |
80 | }
81 | setState={setState}
82 | visible={state}
83 | style={{ top: "40px", width: "800px" }}
84 | closeButton={false}
85 | >
86 | balabalabala
87 | balabalabalalalalallaa
88 |
89 |
setState(!state)}>自定义大小
90 |
91 | );
92 | }
93 |
--------------------------------------------------------------------------------
/src/components/MultiSelect/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-multiselect{
2 | position: relative;
3 | width: 100%;
4 | .bigbear-multiselect-display{
5 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
6 |
7 | display: flex;
8 | padding:5px;
9 | width: 100%;
10 | .bigbear-multiselect-displaytext{
11 | @include ringwrapper($white,$neu-whiteshadow1,$neu-whiteshadow2);
12 |
13 | border-radius: 5px;
14 | display:flex;
15 | flex-grow: 1;
16 | flex-wrap: wrap;
17 |
18 | .bigbear-alert{
19 | display: flex;
20 | flex-direction:row ;
21 | flex-wrap: nowrap;
22 | margin:2px;
23 | padding:3px;
24 | span:nth-child(1){
25 | align-items: center;
26 | display: flex;
27 | font-size: 14px;
28 | margin-right: 2px;
29 | }
30 | .btn{
31 | font-size: 14px;
32 | padding:1px 3px;
33 | position: static;
34 | }
35 | }
36 |
37 | }
38 | .bigbear-multiselect-icon{
39 | align-items: center;
40 | display: flex;
41 | margin-left: 5px;
42 | width: 10px;
43 | }
44 | }
45 | .bigbear-multiselect-options{
46 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
47 |
48 | cursor: pointer;
49 | font-weight: 700;
50 | overflow-y: auto;
51 | padding:10px;
52 | position: absolute;
53 | width: 100%;
54 | >div{
55 | @include neufactory($white,$neu-whiteshadow1,$neu-whiteshadow2);
56 | @include neufactory-hover($info,$neu-infoshadow1,$neu-infoshadow2);
57 |
58 | margin:5px;
59 | padding:5px;
60 | &:hover{
61 | color:$white;
62 | }
63 | }
64 | >.bigbear-multiselect-active{
65 | @include neufactory($primary,$neu-blueshadow1,$neu-blueshadow2);
66 |
67 | color:$white;
68 |
69 | }
70 | }
71 | &.disabled{
72 | cursor: not-allowed;
73 | opacity: .5;
74 | }
75 | }
--------------------------------------------------------------------------------
/src/components/MultiSelect/index.tsx:
--------------------------------------------------------------------------------
1 | import MultiSelect from "./multiselect";
2 |
3 | export default MultiSelect;
4 |
--------------------------------------------------------------------------------
/src/components/MultiSelect/multiselect.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import MultiSelect from './multiselect';
3 | import Icon from '../Icon'
4 |
5 |
6 |
7 |
8 |
9 | # MultiSelect 多选框
10 |
11 |
12 |
13 | ## 基本使用
14 |
15 | 多选框是在alert组件上进行的封装。
16 |
17 | 多选框封装不同于别的组件库,只要传数据就可以了,避免使用上过于复杂。
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | ## 默认值
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | ## 无图标
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | ## 超长数据
50 |
51 | option maxHeight可以设置style修改。
52 |
53 |
54 |
55 |
56 | y)} callback={(e)=>console.log(e)}>
57 |
58 |
59 |
60 |
61 | ## 禁用样式
62 |
63 |
64 |
65 | y)} disabled >
66 |
67 |
68 |
69 |
70 |
71 | ## 属性详情
72 |
73 |
--------------------------------------------------------------------------------
/src/components/Pagination/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-pagination-wrapper{
2 | display: flex;
3 | list-style: none;
4 | .bigbear-pagination-item{
5 | >button{
6 | @include neufactory-hover($primary,$neu-blueshadow1,$neu-blueshadow2);
7 |
8 | min-width: 32px;
9 | &:hover{
10 | color:$white;
11 | }
12 | }
13 |
14 | }
15 | }
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/components/Pagination/index.tsx:
--------------------------------------------------------------------------------
1 | import Pagination from "./pagination";
2 |
3 | export default Pagination;
4 |
--------------------------------------------------------------------------------
/src/components/Pagination/pagination.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import Pagination from './pagination';
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | # Pagination 分页
12 |
13 |
14 | ## 基本使用
15 |
16 | 传入总数即可使用,组件会除以每页条数得到需要展示的分页数,callback获取用户选了第几页。
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | ## 默认页 defaultCurrent
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | ## 设置长度 barMaxSize
33 |
34 |
35 |
36 | console.log(v)}>
39 |
40 |
41 |
42 |
43 |
44 | ## 属性详情
45 |
46 |
--------------------------------------------------------------------------------
/src/components/Popconfirm/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-modal-potral.bigbear-popconfirm{
2 | bottom: unset;
3 | left:unset;
4 | padding:0;
5 | position: absolute;
6 | right:unset;
7 | top:unset;
8 | white-space: nowrap;
9 |
10 | .bigbear-modal-viewport.bigbear-popconfirm{
11 | display: block;
12 | margin: 0;
13 | min-width: unset;
14 | padding:4px;
15 | position: unset;
16 | width: unset;
17 | .bigbear-modal-title.bigbear-popconfirm{
18 | font-size: $font-size-sm;
19 | >span{
20 | display: inline-block;
21 | margin-right: 20px;
22 | }
23 | }
24 | .bigbear-modal-confirm.bigbear-popconfirm{
25 | font-size: $font-size-sm;
26 | padding: 2px;
27 | }
28 | }
29 |
30 | }
31 | .bigbear-popconfirm-wrapper{
32 | display: inline-block;
33 | }
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/components/Popconfirm/index.tsx:
--------------------------------------------------------------------------------
1 | import Popconfirm from "./popconfirm";
2 |
3 | export default Popconfirm;
4 |
--------------------------------------------------------------------------------
/src/components/Popconfirm/popconfirm.example.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import Popconfirm from "./popconfirm";
3 | import Button from "../Button";
4 |
5 | export function PopconfirmExample() {
6 | const [state, setState] = useState(false);
7 | return (
8 |
9 |
setState(!state)}>父组件控制状态}
11 | title="balabalabalabala"
12 | visible={state}
13 | setState={setState}
14 | >
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/Progress/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-progress-wrapper{
2 | align-items: center;
3 | display:flex;
4 | padding:5px;
5 | width: 100%;
6 | .bigbear-progress-bar{
7 | @include ringwrapper($white,$neu-whiteshadow1,$neu-whiteshadow2);
8 |
9 | border-radius: $porgress-radius;
10 | flex:1;
11 | padding: 1px;
12 | position: relative;
13 | width: 100%;
14 | .bigbear-progress-inner{
15 | @include neufactory-noactive($primary,$neu-blueshadow1,$neu-blueshadow2);
16 |
17 | background-color: $primary;
18 | background-image: linear-gradient(to right, $primary, $info);
19 | border-radius: $porgress-radius;
20 | transition:all .4s cubic-bezier(.08,.82,.17,1) 0s ;
21 | &::before{
22 | animation: bigbear-progress-active 2.4s cubic-bezier(.23,1,.32,1) infinite;
23 | background: $white;
24 | border-radius: 10px;
25 | bottom: 0;
26 | content: '';
27 | left: 0;
28 | opacity: 0;
29 | position: absolute;
30 | right: 0;
31 | top:0;
32 | }
33 |
34 | }
35 | }
36 | .bigbear-progress-number{
37 | @include neu-textshadow($neu-whiteshadow1,$neu-whiteshadow2);
38 |
39 | display: inline-block;
40 | font-weight: $font-weight-bold;
41 | margin-left: 10px;
42 | min-width: 55px;
43 | padding: 5px;
44 | text-align: center;
45 | }
46 | }
47 |
48 | @keyframes bigbear-progress-active{
49 | 0% { opacity: 0.1;
50 | width: 0;
51 | }
52 | 20% { opacity: 0.5;
53 | width: 0;
54 | }
55 | 100% { opacity: 0;
56 | width: 100%;
57 | }
58 | }
59 |
60 |
61 | .bigbear-progress-circle{
62 | @include ringwrapper($white,$neu-whiteshadow1,$neu-whiteshadow2);
63 |
64 | border-radius: 50%;
65 | display: inline-block;
66 | position: relative;
67 | .bigbear-progress-circle-inner{
68 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
69 |
70 | border-radius: 50%;
71 | display: inline-block;
72 | font-weight: $font-weight-bold;
73 | left: 50%;
74 | position: absolute;
75 | text-align: center;
76 | top:50%;
77 | transform: translateX(-50%) translateY(-50%);
78 | }
79 | }
--------------------------------------------------------------------------------
/src/components/Progress/index.tsx:
--------------------------------------------------------------------------------
1 | import Progress from "./progress";
2 |
3 | export default Progress;
4 |
--------------------------------------------------------------------------------
/src/components/Progress/progress.example.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import Button from "../Button";
3 | import Progress from "./progress";
4 |
5 | export function ProgressExample() {
6 | const [count, setCount] = useState(30);
7 | return (
8 |
9 |
setCount(count + 1)}>+1
10 |
setCount(count - 1)}>-1
11 |
setCount(count + 10)}>+10
12 |
setCount(count - 10)}>-10
13 |
14 |
15 | );
16 | }
17 |
18 | export function Progresscircle() {
19 | const [count, setCount] = useState(1);
20 | return (
21 |
22 |
setCount(count + 1)}>+1
23 |
setCount(count - 1)}>-1
24 |
setCount(count + 10)}>+10
25 |
setCount(count - 10)}>-10
26 |
27 |
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/src/components/Progress/progress.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import Progress from './progress';
3 | import {ProgressExample,Progresscircle}from './progress.example';
4 | import Icon from '../Icon'
5 |
6 |
7 |
8 |
9 |
10 |
11 | # Progress 进度条
12 |
13 |
14 | ## 基本使用
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | ## 可操作
24 |
25 | ```tsx
26 | export function ProgressExample(){
27 | const [count,setCount]=useState(30)
28 | return (
29 |
30 |
setCount(count+1)}>+1
31 |
setCount(count-1)}>-1
32 |
setCount(count+10)}>+10
33 |
setCount(count-10)}>-10
34 |
35 |
36 | )
37 | }
38 | ```
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | ## 环形进度条
48 |
49 | ```tsx
50 | export function Progresscircle(){
51 | const [count,setCount]=useState(1)
52 | return (
53 |
54 |
setCount(count+1)}>+1
55 |
setCount(count-1)}>-1
56 |
setCount(count+10)}>+10
57 |
setCount(count-10)}>-10
58 |
59 |
60 | )
61 | }
62 | ```
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | ## 不要文字
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | ## 自定义文本
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | }>
95 |
96 |
97 |
98 | ## 属性详情
99 |
100 |
--------------------------------------------------------------------------------
/src/components/Progress/progress.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useMemo, useEffect, ReactNode } from "react";
2 |
3 | interface ProgressType {
4 | /** 传入数字*/
5 | count: number;
6 | /** 是否要末尾计数文本*/
7 | countNumber?: boolean;
8 | /** 环状不生效 进度条高度*/
9 | height?: number;
10 | /** 是否是环状*/
11 | cicrle?: boolean;
12 | /** 环状才生效 环状大小*/
13 | size?: number;
14 | /**自定义环状进度条文本内容 */
15 | circleText?: ReactNode;
16 | /** 自定义长条进度条文本内容*/
17 | progressText?: ReactNode;
18 | }
19 |
20 | function Progress(props: ProgressType) {
21 | const { count, countNumber, height, cicrle, size, circleText, progressText } = props;
22 | const [state, setState] = useState(0);
23 | const [dasharray, setdashArray] = useState("");
24 | useMemo(() => {
25 | if (count < 0) {
26 | setState(0);
27 | } else if (count > 100) {
28 | setState(100);
29 | } else {
30 | setState(count);
31 | }
32 | }, [count]);
33 | useEffect(() => {
34 | if (cicrle) {
35 | let percent = state / 100;
36 | let perimeter = Math.PI * 2 * 170; //周长
37 | let dasharray = perimeter * percent + " " + perimeter * (1 - percent);
38 | setdashArray(dasharray);
39 | }
40 | }, [cicrle, state]);
41 | const render = useMemo(() => {
42 | if (cicrle) {
43 | return (
44 |
45 |
52 |
53 |
60 |
61 |
62 |
63 |
64 |
72 |
88 |
89 |
97 | {circleText ? circleText : `${state}%`}
98 |
99 |
100 | );
101 | } else {
102 | return (
103 |
104 |
110 | {countNumber && (
111 |
115 | {progressText ? progressText : `${state}%`}
116 |
117 | )}
118 |
119 | );
120 | }
121 | }, [cicrle, circleText, countNumber, dasharray, height, progressText, size, state]);
122 |
123 | return <>{render}>;
124 | }
125 |
126 | Progress.defaultProps = {
127 | countNumber: true,
128 | cicrle: false,
129 | size: 100
130 | };
131 |
132 | export default Progress;
133 |
--------------------------------------------------------------------------------
/src/components/Radio/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-radio-wrapper{
2 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
3 |
4 | display: inline-block;
5 | padding: 5px;
6 | .bigbear-radio-label {
7 | cursor: pointer;
8 | margin:0;
9 | margin-left:5px;
10 | margin-right: 5px;
11 | position: relative;
12 | .bigbear-radio-input{
13 | opacity: 0;
14 | position: absolute;
15 | }
16 | .bigbear-radio-dot {
17 | @include square(20px);
18 | @include ringwrapper($white,$neu-whiteshadow1,$neu-whiteshadow2);
19 |
20 | border-radius: 50%;
21 | display: inline-block;
22 | position: relative;
23 | vertical-align: sub;
24 |
25 | }
26 | .bigbear-radio-dot::after{
27 | @include square(10px);
28 | @include neufactory-noactive($primary,$neu-blueshadow1,$neu-blueshadow2);
29 |
30 | border-radius:50%;
31 | content: '';
32 | left:5px;
33 | position: absolute;
34 | top:5px;
35 | transform: scale(0);
36 | transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;
37 | }
38 | .bigbear-radio-dot.radio-active::after{
39 | @include square(10px);
40 | @include neufactory-noactive($primary,$neu-blueshadow1,$neu-blueshadow2);
41 |
42 | border-radius:50%;
43 | content: '';
44 | left:5px;
45 | position: absolute;
46 | top:5px;
47 | transform: scale(1);
48 | transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;
49 | }
50 | .bigbear-radio-value{
51 | padding:5px;
52 | text-shadow: 1px 1px 4px $neu-whiteshadow1, -1px -1px 4px $neu-whiteshadow2;
53 | }
54 | .radio-active ~ .bigbear-radio-value{
55 | color:$primary;
56 | }
57 | &.radio-disabled{
58 | cursor: not-allowed;
59 | opacity: 0.5;
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/src/components/Radio/index.tsx:
--------------------------------------------------------------------------------
1 | import Radio from "./radio";
2 |
3 | export default Radio;
4 |
--------------------------------------------------------------------------------
/src/components/Radio/radio.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import Radio from './radio';
3 | import Icon from '../Icon'
4 |
5 |
6 |
7 |
8 |
9 | # Radio 单选按钮
10 |
11 |
12 |
13 | ## 基本使用
14 |
15 | 不同于别的组件库封装,直接传个数组就可以使用,大大节约代码量。
16 |
17 |
18 |
19 |
20 | console.log(v)}>
21 |
22 |
23 |
24 | ## 默认选中 defaultIndex
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | ## 禁用 disableIndex
34 |
35 | 禁用传入想禁的序号即可,不用一个个组件写disabled了。
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | ## 属性详情
46 |
47 |
--------------------------------------------------------------------------------
/src/components/Radio/radio.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, PropsWithChildren, useMemo } from "react";
2 | import classnames from "classnames";
3 |
4 | interface RadioProps {
5 | /** 数据*/
6 | data: Array;
7 | /** 默认选中索引*/
8 | defaultIndex?: number;
9 | /** 回调值 */
10 | callback?: (arr: Array) => void;
11 | /** 额外类名 */
12 | className?: string;
13 | /** 禁用索引 */
14 | disableIndex?: Array;
15 | }
16 |
17 | function Radio(props: PropsWithChildren) {
18 | const { defaultIndex, callback, data, className, disableIndex } = props;
19 | const disableRef = useMemo(() => {
20 | let arr = new Array(data.length).fill(false);
21 | if (disableIndex) {
22 | disableIndex.forEach((v) => (arr[v] = true));
23 | }
24 | return arr;
25 | }, [data.length, disableIndex]);
26 |
27 | const classes = classnames("bigbear-radio-wrapper", className);
28 | const [state, setState] = useState(
29 | new Array(data.length).fill(false).map((v, i) => (i === defaultIndex ? true : v))
30 | );
31 | return (
32 |
33 | {data.map((value, index) => {
34 | return (
35 |
41 | {
46 | if (!disableRef[index]) {
47 | let newState = new Array(data.length).fill(false);
48 | newState[index] = true;
49 | setState(newState);
50 | if (callback) callback(newState);
51 | }
52 | }}
53 | onChange={() => {}}
54 | >
55 |
58 | {value}
59 |
60 | );
61 | })}
62 |
63 | );
64 | }
65 |
66 | Radio.defaultProps = {
67 | data: []
68 | };
69 |
70 | export default Radio;
71 |
--------------------------------------------------------------------------------
/src/components/Select/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-select{
2 | cursor: pointer;
3 | display: inline-block;
4 | margin: 10px;
5 | position: relative;
6 | vertical-align: bottom;
7 | width: 120px;
8 | .bigbear-select-display{
9 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
10 |
11 | align-items: center;
12 | display: flex;
13 | height: 30px;
14 | justify-content: space-between;
15 | padding-left: 10px;
16 | padding-right: 10px;
17 | .bigbear-select-displaytext{
18 | @include ringwrapper($white,$neu-whiteshadow1,$neu-whiteshadow2);
19 |
20 | flex-grow: 1;
21 | height: 80%;
22 | overflow: hidden;
23 | padding-left: 2px;
24 | padding-right: 2px;
25 | width: 100%;
26 | }
27 | .bigbear-select-icon{
28 | margin-left: 5px;
29 | }
30 | }
31 | .bigbear-select-options{
32 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
33 |
34 | left:10px;
35 | position: absolute;
36 | z-index: 200;
37 | >div{
38 | @include neufactory-hover($primary,$neu-blueshadow1,$neu-blueshadow2);
39 |
40 | cursor: pointer;
41 | height: 30px;
42 | padding-left:10px;
43 | padding-right: 10px;
44 | &:hover{
45 | color:$white;
46 | }
47 | }
48 | }
49 | &.disabled{
50 | cursor: not-allowed;
51 | opacity: .5;
52 | }
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/src/components/Select/index.tsx:
--------------------------------------------------------------------------------
1 | import Select from "./select";
2 |
3 | export default Select;
4 |
--------------------------------------------------------------------------------
/src/components/Select/select.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import Select from './select';
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | # Select 选择框
13 |
14 |
15 |
16 | ## 基本使用
17 |
18 | 如果长文字显示不全且想显示全的,可以自行设定select的宽或者调整字体大小。
19 |
20 |
21 |
22 |
23 | console.log(state)}>
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | ## 使用模板
32 |
33 | css会将option下的div选中设置样式,如果不想用默认样式,可以不用div做第一级子标签。
34 |
35 | 使用模板渲染,记得选中时使用setState设置值,之后使用setOpen关闭选框。
36 |
37 |
38 |
39 |
40 |
{setState(`${index}:${item}`);setOpen(false)}} key={index}>{index}:{item}
}>
43 |
44 |
45 |
46 |
47 |
48 |
49 | ## 属性详情
50 |
51 |
--------------------------------------------------------------------------------
/src/components/Select/select.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | useState,
3 | ReactNode,
4 | useRef,
5 | PropsWithChildren,
6 | useEffect,
7 | CSSProperties
8 | } from "react";
9 | import useClickOutside from "../../hooks/useClickOutside";
10 | import Icon from "../Icon";
11 | import Transition from "../Transition/index";
12 |
13 | interface SelectProps {
14 | /** 选框中数据 */
15 | data: Array;
16 | /** 使用模板渲染,setState是设置展示元素的方法 setOpen控制开关*/
17 | renderTemplate?: (
18 | item: string,
19 | index: number,
20 | setState: React.Dispatch>,
21 | setOpen: React.Dispatch>
22 | ) => ReactNode;
23 | /** 展示右侧的图标 */
24 | icon?: ReactNode;
25 | /** 选框中初始值 */
26 | defaultValue?: string;
27 | /** 下拉动画时间*/
28 | timeout?: number;
29 | /**选择的回调值 */
30 | callback?: (v: string) => void;
31 | /** 禁用*/
32 | disabled?: boolean;
33 | /** 外层容器样式*/
34 | style?: CSSProperties;
35 | /** 内层容器样式 */
36 | innerStyle?: CSSProperties;
37 | }
38 |
39 | function Select(props: PropsWithChildren) {
40 | const {
41 | icon,
42 | defaultValue,
43 | timeout,
44 | renderTemplate,
45 | callback,
46 | data,
47 | disabled,
48 | style,
49 | innerStyle
50 | } = props;
51 | const [state, setState] = useState(defaultValue!);
52 | const [open, setOpen] = useState(false);
53 | const ref = useRef(null);
54 | const nodeRef = useRef(null);
55 | useClickOutside(ref, () => setOpen(false));
56 | useEffect(() => {
57 | if (callback) callback(state);
58 | }, [callback, state]);
59 | return (
60 |
61 |
{
64 | if (!disabled) setOpen(!open);
65 | }}
66 | >
67 |
68 | {state}
69 |
70 | {icon ?
{icon}
: null}
71 |
72 |
73 |
74 | {data.map((item, index) => {
75 | let renderRes = renderTemplate ? (
76 | renderTemplate(item, index, setState, setOpen)
77 | ) : (
78 |
{
80 | setState(item);
81 | setOpen(false);
82 | }}
83 | key={index}
84 | >
85 | {" "}
86 | {item}
87 |
88 | );
89 | return renderRes;
90 | })}
91 |
92 |
93 |
94 | );
95 | }
96 |
97 | Select.defaultProps = {
98 | icon: ,
99 | defaultValue: "",
100 | timeout: 300,
101 | data: [],
102 | disabled: false
103 | };
104 |
105 | export default Select;
106 |
--------------------------------------------------------------------------------
/src/components/Switch/_style.scss:
--------------------------------------------------------------------------------
1 |
2 | .bigbear-switch-label{
3 | cursor: pointer;
4 | position: relative;
5 | input{
6 | opacity: 0;
7 | }
8 | }
9 |
10 | .bigbear-switch-label-disabled{
11 | cursor: not-allowed;
12 | opacity: 60%;
13 | }
14 |
15 |
16 | .bigbear-switch-label-size-default{
17 | height: $switch-height-default;
18 | width: $switch-width-default;
19 | input:checked~ .bigbear-switch-btn-size-default{
20 | left:calc(100% - #{$switch-height-default})
21 | }
22 | }
23 | .bigbear-switch-check-size-default{
24 | border-radius: $switch-border-radius-default;
25 | }
26 | .bigbear-switch-btn-size-default{
27 | height: $switch-height-default;
28 | width: $switch-height-default;
29 | }
30 |
31 |
32 | .bigbear-switch-label-size-lg{
33 | height: $switch-height-lg;
34 | width: $switch-width-lg;
35 | input:checked~ .bigbear-switch-btn-size-lg{
36 | left:calc(100% - #{$switch-height-lg})
37 | }
38 | }
39 | .bigbear-switch-check-size-lg{
40 | border-radius: $switch-border-radius-lg;
41 | }
42 | .bigbear-switch-btn-size-lg{
43 | height: $switch-height-lg;
44 | width: $switch-height-lg;
45 | }
46 |
47 |
48 |
49 |
50 | @each $key,$val in $theme-colors{
51 | .bigbear-switch-check-#{$key}{
52 | @include switch-neu(nth($val, 1),nth($val,3),nth($val,4));
53 |
54 | color:#{nth($val,2)};
55 | height: 100%;
56 | left: 0;
57 | padding: 1px;
58 | position: absolute;
59 | top: 0;
60 | width: 100%;
61 | }
62 | }
63 | @each $key,$val in $theme-colors{
64 | .bigbear-switch-btn-#{$key}{
65 | @include neufactory-noactive(nth($val, 1),nth($val,3),nth($val,4));
66 |
67 | border-radius: 50%;
68 | display: inline-block;
69 | left:0;
70 | position: absolute;
71 | transition:all .36s cubic-bezier(.78,.14,.15,.86);
72 | }
73 |
74 | }
75 |
76 | .bigbear-switch-label-size-sm{
77 | height: $switch-height-sm;
78 | width: $switch-width-sm;
79 | input:checked~ .bigbear-switch-btn-size-sm{
80 | left:calc(100% - #{$switch-height-sm})
81 | }
82 | }
83 | .bigbear-switch-check-size-sm{
84 | border-radius: $switch-border-radius-sm;
85 | padding: 0;
86 | }
87 | .bigbear-switch-btn-size-sm{
88 | height: $switch-height-sm;
89 | width: $switch-height-sm;
90 | }
91 |
--------------------------------------------------------------------------------
/src/components/Switch/index.tsx:
--------------------------------------------------------------------------------
1 | import Switch from "./switch";
2 |
3 | export default Switch;
4 |
--------------------------------------------------------------------------------
/src/components/Switch/switch.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import Button from '../Button'
3 | import Switch from './switch'
4 |
5 |
6 |
7 |
8 |
9 | # Switch 开关
10 |
11 |
12 | ## 基本使用
13 |
14 | 底座和按钮都可以根据喜好换颜色!
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | ## 大小 switchSize
31 |
32 | 有大中小3种
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | ## 禁用 disabled
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | ## 属性详情
79 |
80 |
81 |
--------------------------------------------------------------------------------
/src/components/Switch/switch.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, PropsWithChildren } from "react";
2 | import classNames from "classnames";
3 |
4 | export interface SwitchProps {
5 | /** 底座颜色*/
6 | bottomType?:
7 | | "primary"
8 | | "default"
9 | | "danger"
10 | | "secondary"
11 | | "success"
12 | | "info"
13 | | "light"
14 | | "warning"
15 | | "dark";
16 | /** 按钮颜色*/
17 | btnType?:
18 | | "primary"
19 | | "default"
20 | | "danger"
21 | | "secondary"
22 | | "success"
23 | | "info"
24 | | "light"
25 | | "warning"
26 | | "dark";
27 | /** 大小*/
28 | switchSize?: "default" | "sm" | "lg";
29 | /** 禁用*/
30 | disabled: boolean;
31 | /** 默认开关*/
32 | defaultState: boolean;
33 | /** 选择后回调 */
34 | callback?: (v: boolean) => void;
35 | /** 改变状态前回调,需要返回状态值 */
36 | beforeChange?: (v: boolean) => boolean;
37 | }
38 |
39 | export function Switch(props: PropsWithChildren) {
40 | const {
41 | bottomType,
42 | btnType,
43 | switchSize,
44 | disabled,
45 | defaultState,
46 | callback,
47 | beforeChange
48 | } = props;
49 | const [checked, setChecked] = useState(defaultState);
50 | const labelClassName = classNames("bigbear-switch-label", {
51 | [`bigbear-switch-label-${bottomType}`]: bottomType,
52 | [`bigbear-switch-label-size-${switchSize}`]: switchSize,
53 | [`bigbear-switch-label-disabled`]: disabled
54 | });
55 | const switchCheckName = classNames("bigbear-switch-check", {
56 | [`bigbear-switch-check-${bottomType}`]: bottomType,
57 | [`bigbear-switch-check-size-${switchSize}`]: switchSize
58 | });
59 | const btnClassName = classNames("bigbear-switch-btn", {
60 | [`bigbear-switch-btn-${btnType}`]: btnType,
61 | [`bigbear-switch-btn-size-${switchSize}`]: switchSize
62 | });
63 | return (
64 |
65 | {
70 | let value = !checked;
71 | if (beforeChange) value = beforeChange(value);
72 | setChecked(value);
73 | if (callback) callback(value);
74 | }}
75 | >
76 |
77 |
78 |
79 | );
80 | }
81 |
82 | Switch.defaultProps = {
83 | bottomType: "default",
84 | btnType: "default",
85 | switchSize: "default",
86 | disabled: false,
87 | defaultState: false
88 | };
89 |
90 | export default Switch;
91 |
--------------------------------------------------------------------------------
/src/components/Table/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-table-wrapper{
2 | @include ringwrapper($white,$neu-whiteshadow1,$neu-whiteshadow2);
3 |
4 | padding:5px;
5 | width: 100%;
6 | .bigbear-table-table{
7 | width: 100%;
8 | .bigbear-table-head{
9 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
10 |
11 | border-bottom: 1px solid $neu-whiteshadow1;
12 | padding:10px;
13 | width: 100%;
14 | .bigbear-table-title{
15 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
16 |
17 | padding:10px;
18 | position: relative;
19 | text-align: center;
20 | .bigbear-table-icon{
21 | cursor: pointer;
22 | position: absolute;
23 | right: 10px;
24 | }
25 |
26 | }
27 | }
28 | .bigbear-table-data{
29 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
30 | .bigbear-table-data-row{
31 | &:hover{
32 | background-color: #fafafa;
33 | >td{
34 | background: none;
35 | box-shadow: none;
36 | }
37 | }
38 | .bigbear-table-data-item{
39 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
40 | text-align: center;
41 | padding-top: 10px;
42 | padding-bottom: 10px;
43 | }
44 | }
45 | }
46 | }
47 |
48 | }
49 | .bigbear-table-container{
50 | .bigbear-pagination-wrapper{
51 | justify-content: flex-end;
52 | margin-bottom: 10px;
53 | margin-top: 10px;
54 | .bigbear-pagination-down{
55 | margin-right: 10px;
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/components/Table/index.tsx:
--------------------------------------------------------------------------------
1 | import Table from "./table";
2 |
3 | export default Table;
4 |
--------------------------------------------------------------------------------
/src/components/Transition/index.tsx:
--------------------------------------------------------------------------------
1 | import Transition from "./transition";
2 | export default Transition;
3 |
--------------------------------------------------------------------------------
/src/components/Transition/transition.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import Transition from './transition';
3 |
4 |
5 |
6 |
7 |
8 | # Transition 过渡动画
9 |
10 |
11 | ```jsx
12 |
17 | {...your code}
18 |
19 | ```
20 |
21 | ## 注意,严格模式警告需要传递nodeRef消除
22 |
23 | ```jsx
24 | const MyComponent = () => {
25 | const nodeRef = React.useRef(null)
26 | return (
27 |
28 | Fade
29 |
30 | )
31 | }
32 | ```
33 |
34 |
35 | ## 属性详情
36 |
37 | unmountOnExit boolean 退出时卸载组件
38 |
39 |
--------------------------------------------------------------------------------
/src/components/Transition/transition.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from "react";
2 | import { CSSTransition } from "react-transition-group";
3 | import { TransitionProps } from "react-transition-group/Transition";
4 |
5 | export type AnimationName =
6 | | "zoom-in-top"
7 | | "zoom-in-left"
8 | | "zoom-in-bottom"
9 | | "zoom-in-right"
10 | | "zoom-in-allscale";
11 |
12 | type InnerProps = TransitionProps & {
13 | /** 需要自行添加css,动画名-enter,-enter-active,exit,-exit-active,原理就是改变类名产生动画效果 */
14 | classNames?: string;
15 | };
16 |
17 | export type TransitionProp = InnerProps & {
18 | /** 动画名称,需要别的动画设置classnames */
19 | animation?: AnimationName;
20 | /** 是否需要用div包一层,防止和已有元素的transition属性发生冲突 */
21 | wrapper?: boolean;
22 | };
23 |
24 | export const Transition: FC = (props) => {
25 | const { children, classNames, animation, wrapper, ...restProps } = props;
26 | return (
27 |
28 | {wrapper ? {children}
: children}
29 |
30 | );
31 | };
32 |
33 | Transition.defaultProps = {
34 | unmountOnExit: true,
35 | appear: true
36 | };
37 |
38 | export default Transition;
39 |
--------------------------------------------------------------------------------
/src/components/Upload/_style.scss:
--------------------------------------------------------------------------------
1 | .bigbear-upload-list{
2 | padding: 10px;
3 | .bigbear-upload-li{
4 | list-style-type: none;
5 | .bigbear-alert > span:nth-child(1){
6 | font-size: $font-size-sm;
7 | }
8 | .bigbear-progress-wrapper{
9 | @include neufactory-noactive($white,$neu-whiteshadow1,$neu-whiteshadow2);
10 | border:none;
11 | }
12 | }
13 | }
14 | .bigbear-alert.upload-success{
15 | color:$success;
16 | }
17 | .bigbear-alert.upload-failed{
18 | color:$danger;
19 | }
20 | .bigbear-upload-input{
21 | margin-bottom: 5px;
22 | }
23 | .bigbear-upload-showchoose{
24 | margin-top: 10px;
25 | margin-bottom: 10px;
26 | }
27 | .bigbear-upload-btn{
28 | display: inline-block;
29 | }
30 |
31 | .bigbear-upload-imageli{
32 | display: inline-block;
33 | .bigbear-avatar {
34 | text-align: center;
35 | line-height:$avatar-size-lg - $avatar-padding-lg*2;
36 | }
37 | }
--------------------------------------------------------------------------------
/src/components/Upload/index.tsx:
--------------------------------------------------------------------------------
1 | import Upload from "./upload";
2 |
3 | export default Upload;
4 |
--------------------------------------------------------------------------------
/src/components/Upload/upload.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import Upload from './upload';
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | # Upload 上传
11 |
12 |
13 |
14 | ## 基本使用
15 |
16 |
17 |
18 | console.log(e)} failCallback={(e)=>console.log(e)} >
19 |
20 |
21 |
22 | ## 上传进度
23 |
24 | type='progress'可以下方附加进度信息。
25 |
26 | 注意uid需要唯一
27 |
28 |
29 |
30 | 'avatar'}
31 | defaultProgressBar={[{
32 | filename: 'hello.md',
33 | percent: 100,
34 | status: "success",
35 | uid: '111',
36 | size: 1222,
37 | raw: null
38 | },{
39 | filename: 'hello2.md',
40 | percent: 0,
41 | status: "failed",
42 | uid: '1112',
43 | size: 12222,
44 | raw: null
45 | },{
46 | filename: 'hello3.md',
47 | percent: 40,
48 | status: "upload",
49 | uid: '1113',
50 | size: 12222,
51 | raw: null
52 | },{
53 | filename: 'hello4.md',
54 | percent: 80,
55 | status: "upload",
56 | uid: '11132',
57 | size: 12222,
58 | raw: null
59 | }
60 | ]}
61 | type={'progress'}
62 | successCallback={(e)=>console.log(e)} failCallback={(e)=>console.log(e)} >
63 |
64 |
65 |
66 |
67 | ## 确认上传
68 |
69 | confirm 为 true 可进行分段上传
70 |
71 |
72 |
73 | 'avatar'} confirm={true} successCallback={(e)=>console.log(e)} failCallback={(e)=>console.log(e)} >
74 |
75 |
76 |
77 |
78 | ## 图片回显
79 |
80 | 文件验证需要自行添加
81 |
82 | 传入uploadNumber可以控制最大上传数量
83 |
84 | raw是File,img是src,img或者raw必传其中一个可以显示.
85 |
86 | 如果不想点击删除,可以通过onRemove来改变点击删除事件。
87 |
88 |
89 |
90 | 'avatar'} confirm={true} type='img'
91 | uploadNumber={3}
92 | defaultProgressBar={[{
93 | filename: 'hello.md',
94 | percent: 100,
95 | status: "success",
96 | uid: '111',
97 | size: 1222,
98 | raw: null,
99 | img:'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png'
100 | },{
101 | filename: 'hello2.md',
102 | percent: 0,
103 | status: "failed",
104 | uid: '1112',
105 | size: 12222,
106 | raw: null
107 | },{
108 | filename: 'hello3.md',
109 | percent: 40,
110 | status: "upload",
111 | uid: '1113',
112 | size: 12222,
113 | raw: null
114 | },{
115 | filename: 'hello4.md',
116 | percent: 80,
117 | status: "upload",
118 | uid: '11132',
119 | size: 12222,
120 | raw: null
121 | }
122 | ]}
123 | successCallback={(e)=>console.log(e)} failCallback={(e)=>console.log(e)} >
124 |
125 |
126 |
127 |
128 |
129 | 'avatar'} type='img'
130 | successCallback={(e)=>console.log(e)} failCallback={(e)=>console.log(e)} >
131 |
132 |
133 |
134 |
135 |
--------------------------------------------------------------------------------
/src/components/Upload/uploadlist.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo, memo } from "react";
2 | import Icon from "../Icon";
3 | import { ProgressBar } from "./upload";
4 | import Progress from "../Progress";
5 | import Alert from "../Alert";
6 | import Avatar from "../Avatar";
7 | import { UpdateFilist } from "./upload";
8 |
9 | interface UploadListProps {
10 | flist: ProgressBar[];
11 | onRemove: (item: ProgressBar) => void;
12 | }
13 |
14 | function UploadList(props: UploadListProps) {
15 | const { flist, onRemove } = props;
16 | return (
17 |
18 | {flist.map((item) => {
19 | return (
20 |
21 | }
24 | title={item.filename}
25 | close={true}
26 | initiativeCloseCallback={() => onRemove(item)}
27 | >
28 | {(item.status === "upload" || item.status === "ready") && (
29 |
30 | )}
31 |
32 | );
33 | })}
34 |
35 | );
36 | }
37 | const MemoUploadList = memo(UploadList);
38 |
39 | export default MemoUploadList;
40 |
41 | interface imageListProps extends UploadListProps {
42 | setFlist: React.Dispatch>;
43 | }
44 |
45 | export function ImageList(props: imageListProps) {
46 | const { flist, onRemove, setFlist } = props;
47 | useMemo(() => {
48 | if (flist) {
49 | flist.forEach((item) => {
50 | if (item.raw && !item.img) {
51 | const reader = new FileReader();
52 | reader.addEventListener("load", () => {
53 | UpdateFilist(setFlist, item, {
54 | img: reader.result || "error"
55 | });
56 | });
57 | reader.readAsDataURL(item.raw);
58 | }
59 | });
60 | }
61 | }, [flist, setFlist]);
62 | return (
63 |
64 | {flist.map((item) => {
65 | return (
66 |
{
70 | onRemove(item);
71 | }}
72 | >
73 |
74 | {item.status === "success" && (
75 |
79 | )}
80 | {(item.status === "upload" || item.status === "ready") && (
81 |
82 |
83 |
84 | )}
85 | {item.status === "failed" && (
86 |
87 |
88 |
89 | )}
90 |
91 |
92 | );
93 | })}
94 |
95 | );
96 | }
97 | export const MemoImageList = memo(ImageList);
98 |
--------------------------------------------------------------------------------
/src/components/VirtualList/_style.scss:
--------------------------------------------------------------------------------
1 | .virtual-container{
2 | display: flex;
3 | overflow: hidden;
4 | padding-bottom: 20px;
5 |
6 | }
7 | .virtual-custom-item{
8 | width: 100%;
9 | }
--------------------------------------------------------------------------------
/src/components/VirtualList/index.tsx:
--------------------------------------------------------------------------------
1 | import VirtualList from "./virtuallist";
2 |
3 | export default VirtualList;
4 |
--------------------------------------------------------------------------------
/src/components/VirtualList/virtuallist.example.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState, useEffect } from "react";
2 | import VirtualList from "./virtuallist";
3 |
4 | function MyVirtualList() {
5 | const cRef = useRef(null);
6 | const [state, setState] = useState(null);
7 | useEffect(() => {
8 | setState(cRef.current);
9 | }, []);
10 | return (
11 |
12 |
13 | {new Array(100).fill(1).map((x, y) => (
14 |
15 | {y}
16 |
17 | ))}
18 |
19 |
20 | );
21 | }
22 | export default MyVirtualList;
23 |
24 | function DoubleVirtual() {
25 | const cRef = useRef(null);
26 | const [state, setState] = useState(null);
27 | useEffect(() => {
28 | setState(cRef.current);
29 | }, []);
30 | return (
31 |
32 |
40 | {new Array(100).fill(1).map((x, y) => (
41 |
44 | ))}
45 |
46 |
47 | );
48 | }
49 |
50 | export { DoubleVirtual };
51 |
--------------------------------------------------------------------------------
/src/components/VirtualList/virtuallist.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import MyVirtualList,{DoubleVirtual} from './virtuallist.example'
3 | import VirtualList from './virtuallist'
4 |
5 |
6 |
7 |
8 |
9 | # VirtualList 虚拟列表
10 |
11 |
12 |
13 | ## 基本使用
14 |
15 | scrollDom使用ref或者document.querySelector选择都行,注意获取顺序问题。
16 |
17 | children必须是数组类型的虚拟dom,而不是封装成单一的虚拟dom(比如组件库里List封装方式),否则无效
18 |
19 | 可视范围可能需要传动态的值,效果不好都可以传大一点。
20 |
21 | 监听scroll做了节流,节流期间停止滚动就会停止渲染,可以设置节流时间。
22 |
23 | 如果有误差想调整滚动条高度,可以设置scrollbar,增大或者减少滚动条高度。
24 |
25 | ```jsx
26 | function MyVirtualList(){
27 | const cRef = useRef(null)
28 | const [state,setState]=useState(null)
29 | useEffect(()=>{
30 | setState(cRef.current)
31 | },[])
32 | return (
33 |
34 |
35 | { new Array(100).fill(1).map((x,y)=>(
36 | {y}
37 | ))}
38 |
39 |
40 | )
41 | }
42 | ```
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | ## 多行元素
52 |
53 |
54 | ```jsx
55 | function DoubleVirtual(){
56 | const cRef = useRef(null)
57 | const [state,setState]=useState(null)
58 | useEffect(()=>{
59 | setState(cRef.current)
60 | },[])
61 | return (
62 |
63 |
64 | { new Array(100).fill(1).map((x,y)=>(
65 |
68 | ))}
69 |
70 |
71 | )
72 | }
73 | ```
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | ## 属性详情
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/hooks/useClickOutside.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 |
3 |
4 |
5 | # useClickOutside 组件外点击判定hook
6 |
7 |
8 |
9 | 组件内引入,第一个参数为ref实例,也就是要判定的dom,第二个参数为如果在组件外点击需要执行的函数。
10 |
11 | ```jsx
12 | useClickOutside(Ref,()=>setDropDown([]))
13 | ```
--------------------------------------------------------------------------------
/src/hooks/useClickOutside.tsx:
--------------------------------------------------------------------------------
1 | import { RefObject, useEffect } from "react";
2 |
3 | function useClickOutside(ref: RefObject, handler: Function) {
4 | useEffect(() => {
5 | const listener = (event: MouseEvent) => {
6 | if (!ref.current || ref.current.contains(event.target as Node)) {
7 | return;
8 | }
9 | handler(event);
10 | };
11 | document.addEventListener("click", listener);
12 | return () => document.removeEventListener("click", listener);
13 | }, [ref, handler]);
14 | }
15 | export default useClickOutside;
16 |
--------------------------------------------------------------------------------
/src/hooks/useControlReverse.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 |
3 |
4 |
5 | # useControlReverse 父组件接管受控组件状态hook
6 |
7 |
8 |
9 | 第一个参数为原state,第二个参数为父组件state,第三个参数为原setState,最后个参数为父组件setState
10 |
11 | ```jsx
12 | function useControlReverse(value: T, replaceValue: T | undefined, setState: F, replaceSetState: F | undefined): [T, F]
13 | ```
--------------------------------------------------------------------------------
/src/hooks/useControlReverse.tsx:
--------------------------------------------------------------------------------
1 | import { useMemo } from "react";
2 |
3 | function useControlReverse(
4 | value: T,
5 | replaceValue: T | undefined,
6 | setState: F,
7 | replaceSetState: F | undefined
8 | ): [T, F] {
9 | const val = useMemo(() => {
10 | let val = value;
11 | if (replaceValue !== undefined) {
12 | val = replaceValue;
13 | }
14 | return val;
15 | }, [value, replaceValue]);
16 | const set = useMemo(() => {
17 | let set = setState;
18 | if (replaceSetState !== undefined) {
19 | set = replaceSetState;
20 | }
21 | return set;
22 | }, [setState, replaceSetState]);
23 |
24 | return [val, set];
25 | }
26 |
27 | export default useControlReverse;
28 |
--------------------------------------------------------------------------------
/src/hooks/useDebounce.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 |
3 |
4 |
5 |
6 | # useDebounce 组件防抖hook
7 |
8 |
9 |
10 | ## 使用方式
11 |
12 | 组件内引入,第一个参数为组件内state,第二个参数为需要延迟时间。依赖项需改为返回值。
13 |
14 | ```jsx
15 | const debouncedValue=useDebounce(state,delay)
16 | ```
--------------------------------------------------------------------------------
/src/hooks/useDebounce.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | function useDebounce(value: any, delay = 300) {
4 | const [debounceValue, setDebouncedValue] = useState(value);
5 | useEffect(() => {
6 | const handler = setTimeout(() => {
7 | setDebouncedValue(value);
8 | }, delay);
9 | return () => {
10 | clearTimeout(handler);
11 | };
12 | }, [value, delay]);
13 | return debounceValue;
14 | }
15 | export default useDebounce;
16 |
--------------------------------------------------------------------------------
/src/hooks/useForm.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import FormExample from '../components/Form/form.example';
3 | import { linkTo } from '@storybook/addon-links'
4 | import Button from '../components/Button'
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | # useForm 表单验证器
13 |
14 |
15 |
16 | ## 基本使用
17 |
18 | 传入参数为数组,需要name和validate
19 |
20 | validate是数组,每个对象必须有validate属性和message属性,其中validate需要返回布尔值,true为通过,false会显示设定的message。
21 |
22 | useForm有4个返回值:
23 |
24 | 第一个返回值可以获取useForm里维护的状态,用于最后提交表单
25 |
26 | 第二个返回值需要搭配前面传入的name组成函数放入受控组件的onchange回调中,同时传入需要维护的值。
27 |
28 | 第三个返回值用来返回验证结果,可以输出到页面或者进行判定。
29 |
30 | 第四个返回值和第二个相似,需要搭配传入的name组成函数放入受控组件的onBlur回调中,用于对dirty状态进行检测,如果不需要dirty判定可以不用。
31 |
32 | 跳转至Form
33 |
34 |
35 |
36 |
37 |
38 |
41 | Login example
42 |
43 |
44 |
47 | Register example
48 |
49 |
50 | ```jsx
51 | function FormExample(){
52 | const [handleSubmit,callbackObj,validate,blurObj]=useForm([
53 | {
54 | name:'input1',
55 | validate:[{validate:(e)=>e!=='',message:'用户名不能为空'}]
56 | },
57 | {
58 | name: 'input2',
59 | validate:[
60 | {validate:(e)=>e!=='',message:'密码不为空'},
61 | {validate:(e)=>e.length>2&&e.length<7,message:'密码必须大于2位或者小于7位'}
62 | ]
63 | },
64 | {
65 | name:'checkbox',
66 | validate:[{validate:(e)=>e.filter((v:boolean)=>v).length===3,message:'必须3个都选上'}],
67 | }])
68 | const onSubmit=(data:any)=>console.log(data)
69 | return(
70 |
90 | )
91 | }
92 | ```
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/src/hooks/useForm.tsx:
--------------------------------------------------------------------------------
1 | import { useMemo, useState } from "react";
2 |
3 | interface ValidateType {
4 | validate: (e: any) => boolean;
5 | message: string;
6 | }
7 |
8 | interface UseFormProps {
9 | name: string;
10 | validate?: Array;
11 | }
12 |
13 | interface UserData {
14 | [key: string]: any;
15 | }
16 | interface BlurDataType {
17 | [key: string]: boolean;
18 | }
19 | interface FnObjType {
20 | [key: string]: (e: any) => void;
21 | }
22 | interface ValidataType {
23 | [key: string]: string[];
24 | }
25 |
26 | type UseFormType = [(fn: any) => void, FnObjType, ValidataType, FnObjType];
27 |
28 | function useForm(args: UseFormProps[]): UseFormType {
29 | const [state, setState] = useState();
30 | const [validata, setValidata] = useState(() =>
31 | args.reduce((p, n) => {
32 | p[n.name] = [];
33 | return p;
34 | }, {} as ValidataType)
35 | );
36 | const [blurData, setBlurData] = useState({});
37 | const returnObj = useMemo(() => {
38 | let obj: FnObjType = {};
39 | let blurobj: FnObjType = {};
40 | args.forEach((o) => {
41 | obj[o.name] = (e: any) => {
42 | if (o.validate) {
43 | let resArr: string[] = [];
44 | o.validate.forEach((v) => {
45 | let sign = v.validate(e);
46 | if (!sign) {
47 | //true验证过
48 | resArr.push(v.message);
49 | }
50 | });
51 | setValidata({ ...validata, ...{ [o.name]: resArr } });
52 | }
53 | setState({ ...state, ...{ [o.name]: e } });
54 | };
55 | blurobj[o.name] = (e: any) => {
56 | if (blurData && blurData[o.name]) {
57 | } else {
58 | setBlurData({ ...blurData, ...{ [o.name]: true } });
59 | if (o.validate) {
60 | let resArr: string[] = [];
61 | o.validate.forEach((v) => {
62 | let sign = v.validate(e);
63 | if (!sign) {
64 | //true验证过
65 | resArr.push(v.message);
66 | }
67 | });
68 | setValidata({ ...validata, ...{ [o.name]: resArr } });
69 | }
70 | }
71 | };
72 | });
73 | return [obj, blurobj];
74 | }, [args, blurData, state, validata]);
75 | const handleSubmit = (fn: any) => {
76 | fn(state);
77 | };
78 | return [handleSubmit, returnObj[0], validata, returnObj[1]];
79 | }
80 |
81 | export default useForm;
82 |
--------------------------------------------------------------------------------
/src/hooks/useStopScroll.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 |
3 |
4 |
5 |
6 | # useStopScroll 停止滚动hook
7 |
8 |
9 |
10 | ## 使用方式
11 |
12 | 组件内引入,第一个参数为组件内state,第二个参数为需要延迟时间,第三个参数open,true为使用false为关闭。
13 |
14 | state为true则隐藏滚动条,false则恢复滚动。
15 |
16 |
17 | ```jsx
18 | useStopScroll(state)
19 | ```
--------------------------------------------------------------------------------
/src/hooks/useStopScroll.tsx:
--------------------------------------------------------------------------------
1 | function useStopScroll(state: boolean, delay: number, open?: boolean) {
2 | if (open) {
3 | let width = window.innerWidth - document.body.clientWidth;
4 | if (state) {
5 | document.body.style.overflow = "hidden";
6 | document.body.style.width = `calc(100% - ${width}px)`;
7 | } else {
8 | //等动画渲染
9 | setTimeout(() => {
10 | document.body.style.overflow = "auto";
11 | document.body.style.width = `100%`;
12 | }, delay);
13 | }
14 | }
15 | }
16 |
17 | export default useStopScroll;
18 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | export { default as Button } from "./components/Button";
2 | export { default as Icon } from "./components/Icon";
3 | export { default as Transition } from "./components/Transition";
4 | export { default as Menu } from "./components/Menu";
5 | export { SubMenu, MenuItem } from "./components/Menu";
6 | export { default as Message } from "./components/Message";
7 | export { default as Alert } from "./components/Alert";
8 | export { default as Input } from "./components/Input";
9 | export { default as AutoComplete } from "./components/AutoComplete";
10 | export { default as List } from "./components/List";
11 | export { default as Avatar } from "./components/Avatar";
12 | export { default as VirtualList } from "./components/VirtualList";
13 | export { default as Badge } from "./components/Badge";
14 | export { default as Switch } from "./components/Switch";
15 | export { default as Select } from "./components/Select";
16 | export { default as MultiSelect } from "./components/MultiSelect";
17 | export { default as Radio } from "./components/Radio";
18 | export { default as CheckBox } from "./components/CheckBox";
19 | export { default as Form } from "./components/Form";
20 | export { default as Pagination } from "./components/Pagination";
21 | export { default as Carousel } from "./components/Carousel";
22 | export { default as Table } from "./components/Table";
23 | export { default as Modal } from "./components/Modal";
24 | export { default as Progress } from "./components/Progress";
25 | export { default as Popconfirm } from "./components/Popconfirm";
26 | export { default as Layout } from "./components/Layout";
27 | export { default as Divider } from "./components/Divider";
28 | export { default as Row } from "./components/Grid";
29 | export { Col } from "./components/Grid";
30 | export { default as Upload } from "./components/Upload";
31 | export { default as InputNumber } from "./components/InputNumber";
32 | export { default as Card } from "./components/Card";
33 | export { default as I18n } from "./components/I18n";
34 |
35 | export { default as useClickOutside } from "./hooks/useClickOutside";
36 | export { default as useDebounce } from "./hooks/useDebounce";
37 | export { message } from "./components/Message/message";
38 | export { default as useForm } from "./hooks/useForm";
39 |
--------------------------------------------------------------------------------
/src/page/login.example.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Form, Input, Button, message, Icon, useForm } from "../index";
3 | import { linkTo } from "@storybook/addon-links";
4 |
5 | function Login() {
6 | const [handleSubmit, callbackObj, validate, blurObj] = useForm([
7 | {
8 | name: "username",
9 | validate: [{ validate: (e) => e !== "", message: "用户名不能为空" }]
10 | },
11 | {
12 | name: "password",
13 | validate: [
14 | { validate: (e) => e !== "", message: "密码不为空" },
15 | {
16 | validate: (e) => e.length > 2 && e.length < 7,
17 | message: "密码必须大于2位或者小于7位"
18 | }
19 | ]
20 | }
21 | ]);
22 | const onSubmit = (data: any) => {
23 | if (
24 | validate.username.length === 0 &&
25 | validate.password.length === 0 &&
26 | data &&
27 | data.username &&
28 | data.password
29 | ) {
30 | console.log(data);
31 | message.info("进入提交流程");
32 | } else {
33 | message.danger("验证失败", {});
34 | }
35 | };
36 | return (
37 | <>
38 |
42 | 用户登录
43 |
44 |
45 |
102 | >
103 | );
104 | }
105 | export default Login;
106 |
--------------------------------------------------------------------------------
/src/page/login.stories.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Props ,Preview } from '@storybook/addon-docs/blocks';
2 | import LoginPage from './login.example';
3 |
4 |
5 |
6 |
7 |
8 |
9 | # Login Page 登录页
10 |
11 |
12 |
13 |
18 |
19 |
20 | ```typescript
21 | import React from "react";
22 | import { Form, Input, Button, message, Icon, useForm } from "../index";
23 |
24 | function Login() {
25 | const [handleSubmit, callbackObj, validate, blurObj] = useForm([
26 | {
27 | name: "username",
28 | validate: [{ validate: (e) => e !== "", message: "用户名不能为空" }]
29 | },
30 | {
31 | name: "password",
32 | validate: [
33 | { validate: (e) => e !== "", message: "密码不为空" },
34 | {
35 | validate: (e) => e.length > 2 && e.length < 7,
36 | message: "密码必须大于2位或者小于7位"
37 | }
38 | ]
39 | }
40 | ]);
41 | const onSubmit = (data: any) => {
42 | if (
43 | validate.username.length === 0 &&
44 | validate.password.length === 0 &&
45 | data &&
46 | data.username &&
47 | data.password
48 | ) {
49 | console.log(data);
50 | message.info("进入提交流程");
51 | } else {
52 | message.danger("验证失败", {});
53 | }
54 | };
55 | return (
56 | <>
57 |
61 | 用户登录
62 |
63 |
64 |
117 | >
118 | );
119 | }
120 | export default Login;
121 |
122 |
123 | ```
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/src/stories/Welcome.stories.mdx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # bigbear-ui
5 | bigbear-ui是个人制作的拟物化小型轻量级ui库
6 |
7 | 
8 | 
9 | 
10 | [](https://travis-ci.com/yehuozhili/bigbear-ui)
11 | [](https://coveralls.io/github/yehuozhili/bigbear-ui?branch=master)
12 |
13 |
14 | ✨ 特性
15 |
16 |
17 | - 📕 详细的文档与介绍
18 | - 🎨 使用富有特色的Neumorphism拟物化风格
19 | - 📦 开箱即用的高质量 React 组件
20 | - 🔥 使用 TypeScript 开发,提供完整的类型定义文件
21 |
22 |
23 |
24 |
25 | ## 安装
26 | 使用 npm 或 yarn 安装
27 |
28 | ```
29 | $ npm install bigbear-ui --save
30 | ```
31 |
32 | ## 引入样式
33 |
34 | ```
35 | import 'bigbear-ui/dist/index.css';
36 | ```
37 | ## 导入组件
38 |
39 | ```
40 | import {componentName} from 'bigbear-ui';
41 | ```
42 |
43 | ## 在线文档
44 |
45 | https://yehuozhili.github.io/bigbear-ui/
46 |
47 |
48 |
49 | ## 本地文档
50 |
51 | 下载代码,npm安装,使用`npm run storybook`即可获得本地文档。
52 | ```
53 | git clone https://github.com/yehuozhili/bigbear-ui.git
54 | npm install
55 | npm run storybook
56 | ```
57 | ## 使用scss
58 |
59 | scss放入bigbear-ui/dist/esm/styles/index.scss。scss导入了css可以不用导了。
60 | ```
61 | @import "bigbear-ui/dist/esm/styles/index.scss";
62 | ```
63 |
64 | ## 使用bigbear-ui-cli
65 |
66 | 目前暂时只制作了一个模板供下载。如果需要react-router+redux+thunk以及mock数据可以使用此模板快速开发。
67 |
68 |
69 | https://www.npmjs.com/package/bigbear-ui-cli
70 |
71 | ```
72 | npm i bigbear-ui-cli -g
73 | ```
74 |
75 | ## 项目demo
76 |
77 | http://94.191.80.37:6698/#/
78 |
79 |
80 |
81 |
82 | ## 制作初衷
83 |
84 | 制作一个属于自己的组件库应该是每个前端人员都有的梦想,有时候自己写出某些好的组件也想记录下来。
85 |
86 |
87 |
88 | ## 设计理念
89 |
90 | 新拟物风格早就存在,但是这种风格受限性很强,特别是对于背景色的要求,因为只有通过背景色制造的高光和加深才能制作出完美的凸起和凹下。
91 |
92 | 最初想法可能是做个浅色的风格和一个深色的风格,但是后来觉得,这样定制化过强,大部分时候,场景都是比较复杂的,也并不需要特别完美的定制效果,于是我将阴影效果进行改造,做出个比较通用的效果。
93 |
94 | 这种风格最适合做小工具,同时组件库体积又小,避免占太多空间。
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/src/styles/_animation.scss:
--------------------------------------------------------------------------------
1 | @include zoom-animation('top', scaleY(0), scaleY(1), center top);
2 | @include zoom-animation('left', scale(.45, .45), scale(1, 1), top left);
3 | @include zoom-animation('right', scale(.45, .45), scale(1, 1), top right);
4 | @include zoom-animation('bottom', scaleY(0), scaleY(1), center bottom);
5 | @include zoom-animation('allscale',scaleY(0,0),scale(1, 1),center);
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/styles/_color.scss:
--------------------------------------------------------------------------------
1 |
2 | @each $key,$val in $main-colors {
3 | .bigbear-background-#{$key}{
4 | background-color: $val;
5 | }
6 | .bigbear-color-#{$key}{
7 | color:$val
8 | }
9 | }
10 | @each $key,$val in $neutral-colors {
11 | .bigbear-background-#{$key}{
12 | background-color: $val;
13 | }
14 | .bigbear-color-#{$key}{
15 | color:$val
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | @import './mixin/index';
2 | @import "variables";
3 | @import "reboot";
4 | @import "./animation";
5 | @import "./color";
6 |
7 | //button
8 | @import "../components/Button/style";
9 |
10 | //menu
11 | @import "../components/Menu/style";
12 |
13 | //icon
14 | @import "../components/Icon/style";
15 |
16 | //alert
17 | @import "../components/Alert/style";
18 |
19 | //message
20 | @import "../components/Message/style";
21 |
22 | //input
23 | @import "../components/Input/style";
24 |
25 | //autocomplete
26 | @import "../components/AutoComplete/style";
27 |
28 | //List
29 | @import "../components/List/style";
30 |
31 | //avatar
32 | @import "../components/Avatar/style";
33 |
34 | //virtuallist
35 | @import "../components/VirtualList/style";
36 |
37 | //badge
38 | @import "../components/Badge/style";
39 |
40 | //switch
41 | @import "../components/Switch/style";
42 |
43 | //select
44 | @import "../components/Select/style";
45 |
46 | //multiselect
47 | @import "../components/MultiSelect/style";
48 |
49 | //radio
50 | @import "../components/Radio/style";
51 |
52 | //checkbox
53 | @import "../components/CheckBox/style";
54 |
55 | //form
56 | @import "../components/Form/style";
57 |
58 | //Pagination
59 | @import "../components/Pagination/style";
60 |
61 | //carousel
62 | @import "../components/Carousel/style";
63 |
64 | //table
65 | @import "../components/Table/style";
66 |
67 | //modal
68 | @import "../components/Modal/style";
69 |
70 | //progress
71 | @import "../components/Progress/style";
72 |
73 | //popconfirm
74 | @import "../components/Popconfirm/style";
75 |
76 | //layout&colors
77 | @import "../components/Layout/style";
78 |
79 | //divider
80 | @import "../components/Divider/style";
81 |
82 | //grid
83 | @import "../components/Grid/style";
84 |
85 | //upload
86 | @import "../components/Upload/style";
87 |
88 | //inputnumber
89 | @import "../components/InputNumber/style";
90 |
91 | //card
92 | @import "../components/Card/style";
--------------------------------------------------------------------------------
/src/styles/mixin/_animatemixin.scss:
--------------------------------------------------------------------------------
1 | @mixin zoom-animation(
2 | $direction: 'top',
3 | $scaleStart: scaleY(0),
4 | $scaleEnd: scaleY(1),
5 | $origin: center top,
6 | ) {
7 | .zoom-in-#{$direction}-enter {
8 | opacity: 0;
9 | transform: $scaleStart;
10 | }
11 | .zoom-in-#{$direction}-enter-active {
12 | opacity: 1;
13 | transform: $scaleEnd;
14 | transform-origin: $origin;
15 | transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1) 100ms, opacity 300ms cubic-bezier(0.23, 1, 0.32, 1) 100ms
16 | }
17 | .zoom-in-#{$direction}-appear {
18 | opacity: 0;
19 | transform: $scaleStart;
20 | }
21 | .zoom-in-#{$direction}-appear-active {
22 | opacity: 1;
23 | transform: $scaleEnd;
24 | transform-origin: $origin;
25 | transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1) 100ms, opacity 300ms cubic-bezier(0.23, 1, 0.32, 1) 100ms
26 | }
27 | .zoom-in-#{$direction}-exit {
28 | opacity: 1;
29 | }
30 | .zoom-in-#{$direction}-exit-active {
31 | opacity: 0;
32 | transform: $scaleStart;
33 | transform-origin: $origin;
34 | transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1) 100ms, opacity 300ms cubic-bezier(0.23, 1, 0.32, 1) 100ms;
35 | }
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/src/styles/mixin/_clearfix.scss:
--------------------------------------------------------------------------------
1 | @mixin clearfix() {
2 | &::before {
3 | content: '';
4 | display: table;
5 | }
6 | &::after {
7 | clear: both;
8 | content: '';
9 | display: table;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/styles/mixin/_size.scss:
--------------------------------------------------------------------------------
1 | @mixin size($width,$height) {
2 | height: $height;
3 | width: $width;
4 | }
5 | @mixin square($size) {
6 | @include size($size,$size);
7 | }
8 | @mixin absolut-middle() {
9 | left: 50%;
10 | position: absolute;
11 | transform: translateX(-50%);
12 | }
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "target": "es5",
5 | "module": "esnext",
6 | "declaration": true,
7 | "jsx": "react",
8 | "moduleResolution": "node",
9 | "allowSyntheticDefaultImports": true
10 | },
11 | "include": [
12 | "src",
13 | ],
14 | "exclude": [
15 | "src/**/*.stories.tsx",
16 | "src/**/*.test.tsx",
17 | "src/**/*.example.tsx"
18 | ]
19 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noEmit": true,
20 | "jsx": "react"
21 | },
22 | "include": [
23 | "src"
24 | ]
25 | }
--------------------------------------------------------------------------------