├── .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 | ![npm](https://img.shields.io/npm/v/bigbear-ui) 5 | ![npm bundle size](https://img.shields.io/bundlephobia/min/bigbear-ui) 6 | ![npm](https://img.shields.io/npm/dt/bigbear-ui) 7 | [![Build Status](https://travis-ci.com/yehuozhili/bigbear-ui.svg?branch=master)](https://travis-ci.com/yehuozhili/bigbear-ui) 8 | [![Coverage Status](https://coveralls.io/repos/github/yehuozhili/bigbear-ui/badge.svg?branch=master)](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}}> 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 | 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 | 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 |
39 | 40 |
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 |
87 | 88 |
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 | 48 | 49 |
50 |
点击按钮控制角标数字增减,99以上会变成99+:
51 | 52 |   53 | 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 | 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 | 89 |   90 | 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(); 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(); 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(); 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(); 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 | 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 | 70 | 73 | 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 |
81 | 82 |
{idText}
83 |
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 | 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 |
37 | callbackObj.input1(e.target.value)} 40 | onBlur={(e: React.FocusEvent) => { 41 | blurObj.input1(e.target.value); 42 | }} 43 | > 44 |
{validate.input1.map((v: string) => v)}
45 | callbackObj.input2(e.target.value)} 49 | onBlur={(e: React.FocusEvent) => { 50 | blurObj.input2(e.target.value); 51 | }} 52 | > 53 |
54 | {validate.input2.map((v: string, i: number) => ( 55 |
{v}
56 | ))} 57 |
58 | callbackObj.checkbox(e)} 61 | > 62 |
{validate.checkbox.map((v: string) => v)}
63 |

64 | 72 | 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 | 20 | 21 |
22 | 23 |
24 | 25 | 30 | 31 | 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 |
58 | callbackObj.input1(e.target.value)} 59 | onBlur={(e:React.FocusEvent)=>{blurObj.input1(e.target.value)}} > 60 | 61 |
{validate.input1.map((v:string)=>v)}
62 | callbackObj.input2(e.target.value)} 63 | onBlur={(e:React.FocusEvent)=>{blurObj.input2(e.target.value)}} 64 | > 65 | 66 |
{validate.input2.map((v:string,i:number)=>
{v}
)}
67 | callbackObj.checkbox(e)}> 69 | 70 |
{validate.checkbox.map((v:string)=>v)}
71 |

72 | 76 | 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 | 39 |
40 | 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 | 60 |
61 | 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 |
23 | {children} 24 |
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 |
33 | {children} 34 |
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 |
63 | {children} 64 |
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 | 58 | 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 | 22 | 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 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
    46 |
    47 |
    48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 不自动关闭需要设置autoclosedelay:0,close:true 56 | 57 | 58 | 59 | 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 | 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 | 33 |
    34 | ); 35 | } 36 | 37 | export function ModalExample3() { 38 | const [state, setState] = useState(false); 39 | return ( 40 |
    41 | 42 |

    balabalabala

    43 |

    balabalabalalalalallaa

    44 |
    45 | 46 |
    47 | ); 48 | } 49 | 50 | export function ModalExample4() { 51 | const [state, setState] = useState(false); 52 | return ( 53 |
    54 | 55 |

    balabalabala

    56 |

    balabalabalalalalallaa

    57 |
    58 | 61 | 64 |
    65 |
    66 | 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 | 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 | 10 | 11 | 12 | 13 | 14 |
    15 | ); 16 | } 17 | 18 | export function Progresscircle() { 19 | const [count, setCount] = useState(1); 20 | return ( 21 |
    22 | 23 | 24 | 25 | 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 | 31 | 32 | 33 | 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 | 55 | 56 | 57 | 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 |
    105 |
    109 |
    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 | 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 | 24 | 25 | 26 | 27 |
    28 |
    29 |
    30 | 31 | ## 使用模板 32 | 33 | css会将option下的div选中设置样式,如果不想用默认样式,可以不用div做第一级子标签。 34 | 35 | 使用模板渲染,记得选中时使用setState设置值,之后使用setOpen关闭选框。 36 | 37 | 38 | 39 |
    40 | 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 | 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 |
    42 |
    {y}
    43 |
    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 |
    66 |
    {y}
    67 |
    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 | 33 | 34 |
    35 | 36 |
    37 | 38 | 43 | 44 | 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 |
    71 | callbackObj.input1(e.target.value)} 72 | onBlur={(e:React.FocusEvent)=>{blurObj.input1(e.target.value)}} > 73 | 74 |
    {validate.input1.map((v:string)=>v)}
    75 | callbackObj.input2(e.target.value)} 76 | onBlur={(e:React.FocusEvent)=>{blurObj.input2(e.target.value)}} 77 | > 78 | 79 |
    {validate.input2.map((v:string,i:number)=>
    {v}
    )}
    80 | callbackObj.checkbox(e)}> 82 | 83 |
    {validate.checkbox.map((v:string)=>v)}
    84 |

    85 | 89 | 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 |
    46 |
    55 | BIGBEAR-UI 56 |
    57 | } 59 | callback={(e) => callbackObj.username(e.target.value)} 60 | onBlur={(e: React.FocusEvent) => { 61 | blurObj.username(e.target.value); 62 | }} 63 | placeholder="用户名" 64 | > 65 |
    66 | {validate.username.map((v: string) => v)} 67 |
    68 | } 70 | type="password" 71 | callback={(e) => callbackObj.password(e.target.value)} 72 | onBlur={(e: React.FocusEvent) => { 73 | blurObj.password(e.target.value); 74 | }} 75 | placeholder="密码" 76 | > 77 |
    78 | {validate.password.map((v: string, i: number) => ( 79 |
    {v}
    80 | ))} 81 |
    82 | 83 |
    84 | 93 | 100 |
    101 | 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 |
    14 |
    15 | 16 |
    17 |
    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 |
    65 |
    74 | BIGBEAR-UI 75 |
    76 | } 78 | callback={(e) => callbackObj.username(e.target.value)} 79 | onBlur={(e: React.FocusEvent) => { 80 | blurObj.username(e.target.value); 81 | }} 82 | placeholder="用户名" 83 | > 84 |
    85 | {validate.username.map((v: string) => v)} 86 |
    87 | } 89 | type="password" 90 | callback={(e) => callbackObj.password(e.target.value)} 91 | onBlur={(e: React.FocusEvent) => { 92 | blurObj.password(e.target.value); 93 | }} 94 | placeholder="密码" 95 | > 96 |
    97 | {validate.password.map((v: string, i: number) => ( 98 |
    {v}
    99 | ))} 100 |
    101 | 102 |
    103 | 112 | 115 |
    116 | 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 | ![npm](https://img.shields.io/npm/v/bigbear-ui) 8 | ![npm bundle size](https://img.shields.io/bundlephobia/min/bigbear-ui) 9 | ![npm](https://img.shields.io/npm/dt/bigbear-ui) 10 | [![Build Status](https://travis-ci.com/yehuozhili/bigbear-ui.svg?branch=master)](https://travis-ci.com/yehuozhili/bigbear-ui) 11 | [![Coverage Status](https://coveralls.io/repos/github/yehuozhili/bigbear-ui/badge.svg?branch=master)](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 | } --------------------------------------------------------------------------------