├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .github
└── release.yml
├── .gitignore
├── .npmrc
├── .prettierignore
├── .prettierrc.js
├── .stylelintignore
├── .stylelintrc.js
├── .yarnrc
├── LICENSE
├── README-zh_CN.md
├── README.md
├── TODO.md
├── babel.config.js
├── build
├── build-components.js
├── build-css-non-rem.js
├── build-css-var.js
├── build-entry.js
├── build-icons.js
├── build-lib.js
├── build-mkdir.js
├── build-style-entry.js
├── build-style.js
├── utils
│ ├── color-palette
│ │ ├── index.js
│ │ ├── tinycolor.js
│ │ └── util.js
│ ├── cssVariable.json
│ ├── less2json.js
│ └── lessConfig.js
├── version.js
├── webpack.base.js
├── webpack.build.js
└── webpack.site.dev.js
├── docs
├── config.ts
├── mobile
│ ├── App.tsx
│ ├── components
│ │ ├── demo-title
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── mobile-card
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── mobile-header
│ │ │ └── index.tsx
│ │ └── nav-menu.tsx
│ ├── index.tsx
│ ├── routes.ts
│ ├── style
│ │ └── index.less
│ ├── template.html
│ └── tracking.tsx
├── static
│ ├── code.svg
│ ├── i18n.svg
│ ├── logo.svg
│ ├── menu_icons
│ │ ├── brand.svg
│ │ ├── brand_active.svg
│ │ ├── changelog.svg
│ │ ├── changelog_active.svg
│ │ ├── comp.svg
│ │ ├── comp_active.svg
│ │ ├── i18n.svg
│ │ ├── i18n_active.svg
│ │ ├── intro.svg
│ │ ├── intro_active.svg
│ │ ├── quickstart.svg
│ │ ├── quickstart_active.svg
│ │ ├── theme.svg
│ │ └── theme_active.svg
│ ├── pointer.svg
│ ├── send.svg
│ └── theme.svg
├── type.ts
└── utils
│ └── index.ts
├── package.json
├── postcss.config.js
├── src
├── common
│ ├── popup
│ │ ├── context.tsx
│ │ ├── index.tsx
│ │ └── overlay.tsx
│ └── touch.tsx
├── components
│ ├── action-sheet
│ │ ├── action-sheet-item.tsx
│ │ ├── action-sheet.tsx
│ │ ├── createActionSheet.tsx
│ │ ├── demo
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── badge
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── button
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── calendar
│ │ ├── demo
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── cascader-sliding
│ │ ├── index.less
│ │ └── index.tsx
│ ├── cascader
│ │ ├── demo
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── cell
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── checkbox
│ │ ├── checkbox.tsx
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── group.tsx
│ │ ├── index.less
│ │ ├── index.tsx
│ │ └── types.tsx
│ ├── col
│ │ ├── index.less
│ │ └── index.tsx
│ ├── collapse
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── date-time-picker
│ │ ├── demo
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── dialog
│ │ ├── demo
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── dropdown-item
│ │ ├── index.less
│ │ └── index.tsx
│ ├── dropdown
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── field
│ │ ├── demo
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── header-tab
│ │ ├── index.less
│ │ └── index.tsx
│ ├── header-tabs
│ │ ├── index.less
│ │ └── index.tsx
│ ├── header
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── icon
│ │ ├── all.ts
│ │ ├── index.less
│ │ ├── index.tsx
│ │ └── svg
│ │ │ ├── arrow-down.svg
│ │ │ ├── arrow-left.svg
│ │ │ ├── arrow-right.svg
│ │ │ ├── arrow-up.svg
│ │ │ ├── attention.svg
│ │ │ ├── check-one.svg
│ │ │ ├── check.svg
│ │ │ ├── circle.svg
│ │ │ ├── close-one.svg
│ │ │ ├── close.svg
│ │ │ ├── default.svg
│ │ │ ├── error.svg
│ │ │ ├── filter.svg
│ │ │ ├── info.svg
│ │ │ ├── loading.svg
│ │ │ ├── partial-check.svg
│ │ │ ├── question-mark.svg
│ │ │ ├── radio-uncheck.svg
│ │ │ ├── radio.svg
│ │ │ ├── square-check-one.svg
│ │ │ ├── square-uncheck.svg
│ │ │ ├── success.svg
│ │ │ ├── triangular.svg
│ │ │ └── warning.svg
│ ├── image-preview
│ │ ├── demo
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── infinite-scroll
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── loading
│ │ ├── demo
│ │ │ └── index.tsx
│ │ ├── icon
│ │ │ ├── circle.svg
│ │ │ └── default.svg
│ │ ├── index.less
│ │ └── index.tsx
│ ├── locale-context
│ │ └── index.tsx
│ ├── modal
│ │ ├── confirm.tsx
│ │ ├── demo
│ │ │ └── index.tsx
│ │ ├── index.tsx
│ │ └── modal-dialog.tsx
│ ├── notice-bar
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── overlay
│ │ ├── index.less
│ │ └── index.tsx
│ ├── picker-column
│ │ ├── demo
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── picker
│ │ ├── demo
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── popup-drill-down-item
│ │ └── index.tsx
│ ├── popup-drill-down
│ │ ├── context.ts
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── popup
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── progress
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── pull-refresh
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── radio
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── group.tsx
│ │ ├── icon.tsx
│ │ ├── index.less
│ │ ├── index.tsx
│ │ ├── radio.tsx
│ │ └── types.tsx
│ ├── row
│ │ ├── RowContext.tsx
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── search
│ │ ├── demo
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── slider
│ │ ├── demo
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── sticky
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── swipe-cell
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── swipe
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── switch
│ │ ├── demo
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── tab
│ │ ├── index.less
│ │ └── index.tsx
│ ├── table
│ │ ├── algorithm.ts
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── empty-content.tsx
│ │ ├── index.less
│ │ ├── index.tsx
│ │ ├── indicator.tsx
│ │ ├── sort-icon.tsx
│ │ └── types.ts
│ ├── tabs
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ ├── index.tsx
│ │ └── utils.ts
│ ├── tag
│ │ ├── demo
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── toast
│ │ ├── Toast.tsx
│ │ ├── demo
│ │ │ └── index.tsx
│ │ ├── icon
│ │ │ ├── error.svg
│ │ │ ├── loading.svg
│ │ │ ├── success.svg
│ │ │ └── warning.svg
│ │ ├── index.less
│ │ └── index.tsx
│ ├── toolbar
│ │ ├── index.less
│ │ └── index.tsx
│ └── tree
│ │ ├── demo
│ │ └── index.tsx
│ │ ├── index.less
│ │ └── index.tsx
├── hooks
│ ├── callback
│ │ ├── const-callback.ts
│ │ ├── index.ts
│ │ └── ref-callback.ts
│ ├── click
│ │ └── index.ts
│ ├── const.ts
│ ├── dom
│ │ ├── index.ts
│ │ └── resize.ts
│ ├── drag
│ │ └── index.ts
│ ├── event.ts
│ ├── index.ts
│ ├── input
│ │ ├── composition-input.ts
│ │ └── index.ts
│ ├── state
│ │ ├── controlled.ts
│ │ ├── index.ts
│ │ └── record.ts
│ ├── timer.ts
│ └── touch
│ │ ├── index.ts
│ │ └── touch.ts
├── icon
│ ├── arrow-left.svg
│ ├── arrow-right.svg
│ ├── attention.svg
│ ├── check-one.svg
│ ├── check.svg
│ ├── close-one.svg
│ ├── close.svg
│ └── info.svg
├── index.less
├── index.tsx
├── locale
│ ├── en-US.ts
│ ├── ja.ts
│ └── zh-CN.ts
├── props-type.ts
├── public-api
│ └── index.ts
├── style
│ ├── animation.less
│ ├── color-palette
│ │ ├── palette.less
│ │ └── tinyColor.less
│ ├── common.less
│ ├── compat.less
│ ├── hairline.less
│ ├── index.less
│ ├── mixins.less
│ ├── mixins
│ │ ├── hairline.less
│ │ ├── radius.less
│ │ └── shadow.less
│ ├── rem.less
│ ├── reset.less
│ ├── root.less
│ ├── var.config.json
│ └── var.less
└── utils
│ ├── const.ts
│ ├── create
│ ├── bem.ts
│ ├── createBEM.ts
│ ├── i18n.ts
│ └── index.ts
│ ├── dom
│ ├── event.ts
│ ├── raf.ts
│ ├── scroll.ts
│ └── unit.js
│ ├── format
│ ├── date.ts
│ └── string.ts
│ ├── index.ts
│ ├── math.ts
│ ├── motion.ts
│ ├── struct
│ ├── array.ts
│ └── tree.ts
│ └── types.ts
├── ssr
├── .npmrc
├── .yarnrc
├── build
│ ├── webpack.client.js
│ └── webpack.server.js
├── package.json
├── src
│ ├── client
│ │ ├── App.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ └── server
│ │ └── index.tsx
├── tsconfig.json
└── yarn.lock
├── tsconfig.json
├── typings
└── index.d.ts
├── workflows
└── build.yml
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | end_of_line = lf
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [*.md]
11 | trim_trailing_whitespace = false
12 |
13 | [*.ts]
14 | indent_size = 2
15 |
16 | [*.tsx]
17 | indent_size = 2
18 |
19 | [*.js]
20 | indent_size = 2
21 |
22 | [*.jsx]
23 | indent_size = 2
24 |
25 | [*.css]
26 | indent_size = 2
27 |
28 | [Makefile]
29 | indent_style = tab
30 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # /node_modules/* and /bower_components/* in the project root are ignored by default
2 |
3 | # Ignore build files
4 | build/*
5 | es/*
6 | lib/*
7 | ssr/**
8 | babel.config.js
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['eslint:recommended', 'plugin:prettier/recommended'],
3 | env: {
4 | browser: true,
5 | commonjs: true,
6 | es6: true,
7 | },
8 | parserOptions: {
9 | ecmaFeatures: {
10 | experimentalObjectRestSpread: true,
11 | },
12 | sourceType: 'module',
13 | },
14 | rules: {
15 | indent: ['error', 2],
16 | 'linebreak-style': ['error', 'unix'],
17 | quotes: ['error', 'single'],
18 | semi: 'off',
19 | 'react/prop-types': [2, { ignore: ['children'] }],
20 | },
21 | settings: {
22 | react: {
23 | version: 'detect',
24 | },
25 | },
26 | overrides: [
27 | {
28 | files: ['*.tsx', '*.ts'],
29 | parserOptions: {
30 | ecmaFeatures: {
31 | jsx: true,
32 | },
33 | },
34 | extends: [
35 | 'eslint:recommended',
36 | 'plugin:@typescript-eslint/recommended',
37 | // https://www.npmjs.com/package/@typescript-eslint/eslint-plugin
38 | // "plugin:@typescript-eslint/recommended-requiring-type-checking"
39 | 'plugin:react/recommended',
40 | 'plugin:prettier/recommended',
41 | ],
42 | plugins: ['@typescript-eslint', 'react', 'react-hooks'],
43 | rules: {
44 | '@typescript-eslint/no-explicit-any': 0,
45 | '@typescript-eslint/ban-ts-ignore': 0,
46 | 'react-hooks/rules-of-hooks': 'error',
47 | '@typescript-eslint/no-non-null-assertion': 0,
48 | '@typescript-eslint/no-empty-function': 0,
49 | 'react-hooks/exhaustive-deps': 'warn',
50 | },
51 | },
52 | ],
53 | };
54 |
--------------------------------------------------------------------------------
/.github/release.yml:
--------------------------------------------------------------------------------
1 | # .github/release.yml
2 |
3 | changelog:
4 | exclude:
5 | labels:
6 | - ignore-for-release
7 | authors:
8 | - octocat
9 | categories:
10 | - title: Breaking Changes 🛠
11 | labels:
12 | - Semver-Major
13 | - breaking-change
14 | - title: Exciting New Features 🎉
15 | labels:
16 | - Semver-Minor
17 | - enhancement
18 | - title: Other Changes
19 | labels:
20 | - "*"
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log*
2 | .cache
3 | .DS_Store
4 | .history
5 | .idea
6 | .vscode
7 |
8 | # npm
9 | node_modules
10 | package-lock.json
11 |
12 | # dist file
13 | es
14 | lib
15 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry= https://registry.npmjs.org
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Ignore artifacts:
2 | build
3 | ssr
4 | es
5 | lib
6 | babel.config.js
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 100,
3 | tabWidth: 2,
4 | useTabs: false,
5 | semi: true,
6 | singleQuote: true,
7 | jsxSingleQuote: false,
8 | trailingComma: 'all',
9 | bracketSpacing: true,
10 | rangeStart: 0,
11 | rangeEnd: Infinity,
12 | proseWrap: 'preserve',
13 | arrowParens: 'avoid',
14 | htmlWhitespaceSensitivity: 'css',
15 | endOfLine: 'lf',
16 | quoteProps: 'as-needed',
17 | vueIndentScriptAndStyle: false,
18 | };
19 |
--------------------------------------------------------------------------------
/.stylelintignore:
--------------------------------------------------------------------------------
1 | # npm
2 | node_modules
3 |
4 | # src/style
5 | src/style/color-palette
6 | src/style/compat.less
--------------------------------------------------------------------------------
/.stylelintrc.js:
--------------------------------------------------------------------------------
1 | /**
2 | * We give up using stylelint-prettier. Because stylelint-prettier just runs Prettier.
3 | * And Prettier always lowercasing units. It will transfer 'PX' to 'px', then px2rem will be down.
4 | * https://github.com/stylelint/stylelint/issues/4048
5 | * Comparing star number of eslint-plugin-prettier and styleling-prettier, stylelint-prettier is not valued.
6 | */
7 | module.exports = {
8 | "extends": [
9 | "stylelint-config-standard",
10 | "stylelint-config-rational-order"
11 | ],
12 | "rules": {
13 | "order/properties-order": [],
14 | "at-rule-no-unknown": null,
15 | "number-leading-zero": null,
16 | "function-calc-no-invalid": null,
17 | "no-descending-specificity": null,
18 | "declaration-colon-newline-after": null,
19 | "font-family-no-missing-generic-family-keyword": null,
20 | "value-keyword-case": null,
21 | "unit-case": null
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.yarnrc:
--------------------------------------------------------------------------------
1 | "registry" "https://registry.yarnpkg.com"
--------------------------------------------------------------------------------
/README-zh_CN.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | OKeeDesign Mobile React
7 |
8 | OKeeDesign Mobile React 是基于 OKeeDesign 设计体系的移动端组件库。
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | [English](./README.md) | 简体中文
18 |
19 | ## 特性
20 |
21 | * 提供 36 个高质量组件,覆盖移动端各类场景
22 | * 支持国际化,支持 3 种语言
23 | * 支持TypeScript
24 | * 支持主题定制
25 | * 支持按需引入
26 |
27 | ## 快速预览
28 |
29 | 了解更多信息,请参考[快速上手](https://okee.oceanengine.com/mobile/react/#/zh-CN/intro)。可扫下方二维码,在线体验组件功能。
30 |
31 |
32 |
33 | ## 本地运行 node 8+
34 |
35 | ```
36 | yarn
37 | yarn dev
38 | ```
39 |
40 | ## 快速上手
41 |
42 | ```
43 | npm install @okee-uikit/m-react
44 | ```
45 |
46 | 全局引入
47 |
48 | ```javascript
49 | import react from 'react';
50 | import '@okee-uikit/m-react/lib/index.css';
51 | import { Button } from '@okee-uikit/m-react';
52 | ```
53 |
54 | ## 开源协议
55 | OKeeDesign Mobile React 基于MIT协议,可自由参与开源。
56 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | OKeeDesign Mobile React
7 |
8 | OKeeDesign Mobile React is a mobile side component library based on OKeeDesign.
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | English | [简体中文](./README-zh_CN.md)
18 |
19 | ## Features
20 |
21 | * Provide 36 highly qualified components, covering different situations on mobile side
22 | * Support internationalization, support 3 different languages
23 | * Support TypeScript
24 | * Support customizing theme
25 | * Support importing on demand
26 |
27 | ## Quick Preview
28 |
29 | For more information, please refer to [Quick Start](https://okee.oceanengine.com/mobile/react/#/zh-CN/intro). Scan QR code below to experience components function.
30 |
31 |
32 |
33 | ## Run Locally node 8+
34 |
35 | ```
36 | yarn
37 | yarn start
38 | ```
39 |
40 | ## Quick Start
41 |
42 | ```
43 | npm install @okee-uikit/m-react
44 | ```
45 |
46 | Global Import
47 |
48 | ```javascript
49 | import React from 'react';
50 | import '@okee-uikit/m-react/lib/index.css';
51 | import { Button } from '@okee-uikit/m-react';
52 | ```
53 |
54 | ## License
55 | OKeeDesign Mobile React MIT licensed.
56 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 |
2 | - [ ] 完善 github README
3 | - [ ] 完善 issue 提交表单及流程
4 | - [ ] 处理新的 lint 规则引入的警告和报错
5 | - [ ] 单元测试
6 | - [ ] 对默认的全局样式进行文档补充
7 | - [ ] 优化打包、发布流程
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function (api) {
2 | const { BABEL_MODULE } = process.env
3 | const useESModules = BABEL_MODULE !== 'commonjs'
4 |
5 | api && api.cache(false)
6 | return {
7 | presets: [
8 | [
9 | '@babel/preset-env',
10 | {
11 | loose: true,
12 | modules: useESModules ? false : 'commonjs'
13 | }
14 | ],
15 | '@babel/preset-typescript',
16 | '@babel/preset-react'
17 | ],
18 | plugins: ['transform-class-properties']
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/build/build-components.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Compile components
3 | */
4 | const fs = require('fs-extra');
5 | const path = require('path');
6 | const babel = require('@babel/core');
7 |
8 | const esDir = path.join(__dirname, '../es');
9 | const libDir = path.join(__dirname, '../lib');
10 | const babelConfig = {
11 | configFile: path.join(__dirname, '../babel.config.js')
12 | };
13 |
14 | const scriptRegExp = /\.(ts|tsx|js|jsx)$/;
15 | const isDeclaration = path => /(\.d\.ts)$/.test(path);
16 | const isCode = path => !/(demo|test|\.md|\.mdx|\.d\.ts)$/.test(path);
17 | const isDir = dir => fs.lstatSync(dir).isDirectory();
18 | const isScript = path => scriptRegExp.test(path);
19 |
20 | function compile(dir) {
21 | const files = fs.readdirSync(dir);
22 |
23 | files.forEach(file => {
24 | const filePath = path.join(dir, file);
25 |
26 | // save .d.ts
27 | if (isDeclaration(file)) {
28 | return
29 | }
30 |
31 | // remove unnecessary files
32 | if (!isCode(file)) {
33 | return fs.removeSync(filePath);
34 | }
35 |
36 | // scan dir
37 | if (isDir(filePath)) {
38 | return compile(filePath);
39 | }
40 |
41 | // compile js or ts
42 | if (isScript(file)) {
43 | const { code } = babel.transformFileSync(filePath, babelConfig);
44 | fs.removeSync(filePath);
45 | fs.outputFileSync(filePath.replace(scriptRegExp, '.js'), code);
46 | }
47 | });
48 | }
49 |
50 | compile(esDir);
51 |
52 | process.env.BABEL_MODULE = 'commonjs';
53 | compile(libDir);
54 |
--------------------------------------------------------------------------------
/build/build-css-non-rem.js:
--------------------------------------------------------------------------------
1 | /**
2 | * build for css no font-size
3 | */
4 | const fs = require('fs');
5 | const path = require('path');
6 | const less = require('less');
7 | const postcss = require('postcss');
8 | const postcssrc = require('postcss-load-config');
9 | const csso = require('csso');
10 |
11 | const lessIndexPath = path.join(__dirname, '..', 'es/index.less');
12 |
13 | async function compileLess (lessPath) {
14 | return less.render(
15 | fs.readFileSync(lessPath, 'utf-8'),
16 | {
17 | filename: lessPath,
18 | paths: [path.resolve(__dirname, '..', 'node_modules')],
19 | javascriptEnabled: true,
20 | noIeCompat: true,
21 | modifyVars: {
22 | '@useDefaultFontSize': false
23 | }
24 | }
25 | )
26 | }
27 |
28 | async function compilePostcss (code, path) {
29 | const postcssConfig = await postcssrc();
30 | postcssConfig.plugins.splice(1, 1);
31 | return postcss(postcssConfig.plugins).process(code, { from: path })
32 | }
33 |
34 | async function compile() {
35 | let code = await compileLess(lessIndexPath);
36 | code = await compilePostcss(code.css, lessIndexPath);
37 | code = await csso.minify(code.css).css;
38 | fs.writeFileSync(lessIndexPath.replace('index.less', 'index.non-rem.css'), code);
39 | fs.writeFileSync(lessIndexPath.replace('es/index.less', 'lib/index.non-rem.css'), code);
40 | }
41 |
42 | compile();
43 |
--------------------------------------------------------------------------------
/build/build-css-var.js:
--------------------------------------------------------------------------------
1 | /**
2 | * build for css variables
3 | */
4 | const fs = require('fs');
5 | const path = require('path');
6 | const less = require('less');
7 | const postcss = require('postcss');
8 | const postcssrc = require('postcss-load-config');
9 | const csso = require('csso');
10 | const LessVarMark = require('@okee-uikit/less-var-mark');
11 | const { lessVars, lessMap } = require('./utils/lessConfig');
12 |
13 | const lessIndexPath = path.join(__dirname, '..', 'es/index.less');
14 |
15 | async function compileLess (lessPath) {
16 | return less.render(
17 | fs.readFileSync(lessPath, 'utf-8'),
18 | {
19 | filename: lessPath,
20 | paths: [path.resolve(__dirname, '..', 'node_modules')],
21 | javascriptEnabled: true,
22 | noIeCompat: true,
23 | modifyVars: lessVars,
24 | plugins: [
25 | new LessVarMark(lessVars, lessMap)
26 | ]
27 | }
28 | )
29 | }
30 |
31 | async function compilePostcss (code, path) {
32 | const postcssConfig = await postcssrc();
33 | postcssConfig.plugins.push(require('@okee-uikit/postcss-var'))
34 | return postcss(postcssConfig.plugins).process(code, { from: path })
35 | }
36 |
37 | async function compile() {
38 | let code = await compileLess(lessIndexPath);
39 | code = await compilePostcss(code.css, lessIndexPath);
40 | code = await csso.minify(code.css).css;
41 | fs.writeFileSync(lessIndexPath.replace('index.less', 'index.css-variable.css'), code);
42 | fs.writeFileSync(lessIndexPath.replace('es/index.less', 'lib/css_variable.css'), code);
43 | fs.writeFileSync(lessIndexPath.replace('es/index.less', 'lib/index.css-variable.css'), code);
44 | }
45 |
46 | compile();
47 |
--------------------------------------------------------------------------------
/build/build-entry.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 自动生成全局样式入口文件
3 | * index.less && index.tsx
4 | */
5 | const fs = require('fs');
6 | const path = require('path');
7 | const uppercamelize = require('uppercamelcase');
8 | const packageJson = require('../package.json');
9 |
10 | const commonPath = '../src/components';
11 | const components = fs.readdirSync(path.resolve(__dirname, commonPath));
12 |
13 | const styleList = components
14 | .filter(name => fs.existsSync(
15 | path.resolve(__dirname, `${commonPath}/${name}/index.less`)
16 | ))
17 | .map(name => `@import './components/${name}/index';`);
18 |
19 | fs.writeFileSync(path.join(__dirname, '../src/index.less'),
20 | `// This file is auto gererated by build/build-entry.js
21 | @import './style/index';
22 | @import './style/root';
23 | ${styleList.join('\n')}
24 | `
25 | );
26 |
27 | const version = process.env.VERSION || packageJson.version;
28 | const exportList = components.map(name =>
29 | `export { default as ${uppercamelize(name)} } from './components/${name}/index';`
30 | );
31 |
32 | fs.writeFileSync(path.join(__dirname, '../src/index.tsx'),
33 | `// This file is auto gererated by build/build-entry.js
34 | const version = '${version}';
35 | export default { version };
36 | export * from './props-type';
37 | ${exportList.join('\n')}
38 | `
39 | );
40 |
--------------------------------------------------------------------------------
/build/build-icons.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 自动生成 SVG 图标引入文件
3 | */
4 | const fs = require('fs');
5 | const path = require('path');
6 | const uppercamelize = require('uppercamelcase');
7 |
8 | const iconDir = path.resolve(__dirname, '../src/components/icon');
9 | const svgDir = path.resolve(iconDir, 'svg');
10 |
11 | const icons = fs.readdirSync(svgDir).filter(f => f.endsWith('.svg'));
12 |
13 | const content = [
14 | '/* eslint-disable */',
15 | '// this file is auto generated by build/build-icons.js',
16 | '',
17 | 'export interface IconMap {',
18 | ...icons.map(file => ` ${uppercamelize(file.replace(/\.svg$/, ''))}: string;`),
19 | '}',
20 | '',
21 | 'export const allIcons: IconMap = {',
22 | ...icons.map(
23 | file =>
24 | ` ${uppercamelize(file.replace(/\.svg$/, ''))}: \`${fs.readFileSync(path.resolve(svgDir, file))}\`,`,
25 | ),
26 | '};',
27 | '',
28 | ].join('\n');
29 |
30 | fs.writeFileSync(path.resolve(iconDir, 'all.ts'), content);
31 |
--------------------------------------------------------------------------------
/build/build-lib.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Build npm lib
3 | */
4 | const shell = require('shelljs');
5 | const signale = require('signale');
6 |
7 | const { Signale } = signale;
8 | const tasks = [
9 | 'npm run lint',
10 | 'npm run build:gen-files',
11 | 'npm run build:mkdir',
12 | 'npm run build:declaration',
13 | 'node build/build-components.js --color',
14 | 'node build/build-style.js --color',
15 | 'node build/build-style-entry.js --color',
16 | 'node build/build-css-var.js',
17 | 'node build/build-css-non-rem.js',
18 | 'cross-env NODE_ENV=production webpack --color --config build/webpack.build.js',
19 | 'cross-env NODE_ENV=production webpack -p --color --config build/webpack.build.js',
20 | ];
21 |
22 | tasks.forEach(task => {
23 | signale.start(task);
24 | const interactive = new Signale({ interactive: true });
25 | interactive.pending(task);
26 | shell.exec(`${task} --silent`);
27 | interactive.success(task);
28 | });
29 |
--------------------------------------------------------------------------------
/build/build-mkdir.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 创建产出目录
3 | */
4 | const fs = require('fs-extra');
5 | const path = require('path');
6 |
7 | const esDir = path.join(__dirname, '../es');
8 | const libDir = path.join(__dirname, '../lib');
9 | const srcDir = path.join(__dirname, '../src');
10 |
11 | // clear dir
12 | fs.emptyDirSync(esDir);
13 | fs.emptyDirSync(libDir);
14 |
15 | // copy dir
16 | fs.copySync(srcDir, esDir);
17 | fs.copySync(srcDir, libDir);
18 |
--------------------------------------------------------------------------------
/build/build-style.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs-extra');
3 | const glob = require('fast-glob');
4 | const less = require('less');
5 | const csso = require('csso');
6 | const postcss = require('postcss');
7 | const postcssrc = require('postcss-load-config');
8 |
9 | /**
10 | * 主题定制相关
11 | */
12 | let modifyVars = {};
13 |
14 | async function compileLess(lessCodes, paths) {
15 | const outputs = await Promise.all(
16 | lessCodes.map((source, index) => {
17 | return less.render(source, {
18 | paths: [path.resolve(__dirname, 'node_modules')],
19 | filename: paths[index],
20 | javascriptEnabled: true,
21 | modifyVars
22 | })
23 | }
24 | )
25 | ).catch(error => {
26 | console.error('compileLess', error)
27 | });
28 | return outputs.map(item => item.css);
29 | }
30 |
31 | async function compilePostcss(cssCodes, paths) {
32 | const postcssConfig = await postcssrc();
33 | const outputs = await Promise.all(
34 | cssCodes.map((css, index) =>
35 | postcss(postcssConfig.plugins).process(css, { from: paths[index] })
36 | )
37 | ).catch(error => {
38 | console.error('compilePostcss', error)
39 | });
40 |
41 | return outputs.map(item => item.css);
42 | }
43 |
44 | async function compileCsso(cssCodes) {
45 | return cssCodes.map(css => csso.minify(css).css);
46 | }
47 |
48 | async function dest(output, paths) {
49 | await Promise.all(
50 | output.map((css, index) => fs.writeFile(paths[index].replace('.less', '.css'), css))
51 | ).catch(error => {
52 | console.error('dest', error)
53 | });
54 | }
55 |
56 | // compile component css
57 | async function compile() {
58 | let codes;
59 |
60 | const paths = await glob(
61 | ['./es/**/*.less', './lib/**/*.less'],
62 | { absolute: true }
63 | );
64 |
65 | codes = await Promise.all(
66 | paths.map(path => fs.readFile(path, 'utf-8'))
67 | ).catch(error => {
68 | console.error('compile', error)
69 | });
70 |
71 | codes = await compileLess(codes, paths);
72 | codes = await compilePostcss(codes, paths);
73 | codes = await compileCsso(codes);
74 |
75 | await dest(codes, paths);
76 | }
77 |
78 | compile();
79 |
--------------------------------------------------------------------------------
/build/utils/color-palette/index.js:
--------------------------------------------------------------------------------
1 | const { colorMix, colorPalette } = require('./util');
2 |
3 | const grayScaleLimit = 11;
4 |
5 | let presetPrimaryColors = {
6 | blue: '#338AFF',
7 | green: '#6ABF40',
8 | red: '#F65656',
9 | yellow: '#FFA900',
10 | cyan: '#10D5D5'
11 | };
12 | let presetPalettes = {};
13 |
14 | // 生成色阶
15 | function generate(color){
16 | const colors = [];
17 | for (let i = 0; i < 11; i++) {
18 | colors.push(i === 5 ? color : colorPalette(color, i + 1))
19 | }
20 | return colors;
21 | }
22 |
23 | // 生成预设值
24 | function generatePreset(){
25 | // 彩色
26 | Object.keys(presetPrimaryColors).forEach((key) => {
27 | presetPalettes[key]= generate(presetPrimaryColors[key])
28 | });
29 |
30 | // 浅灰色
31 | presetPalettes['light-gray'] = [
32 | '#FFFFFF',
33 | '#FAFAFA',
34 | '#F5F5F5',
35 | '#F0F0F0',
36 | '#EBEBEB',
37 | '#E0E0E0',
38 | '#D6D6D6',
39 | '#C1C1C1',
40 | '#999999',
41 | '#666666',
42 | '#333333',
43 | ];
44 |
45 | // 深灰色
46 | presetPalettes['dark-gray'] = [];
47 | for(let j = 1; j <= grayScaleLimit; j++) {
48 | let colorValue = colorMix(presetPrimaryColors['blue'], j);
49 | presetPalettes['dark-gray'].unshift(colorValue)
50 | }
51 |
52 | // 深灰色-原色
53 | presetPalettes['dark-gray-origin'] = [];
54 | for(let j = 1; j <= grayScaleLimit; j++) {
55 | let colorValue = colorMix(presetPrimaryColors['blue'], j, false);
56 | presetPalettes['dark-gray-origin'].unshift(colorValue)
57 | }
58 | }
59 |
60 | generatePreset();
61 |
62 | // 输出colorMap对象
63 | module.exports = { generate, presetPalettes };
--------------------------------------------------------------------------------
/build/utils/color-palette/util.js:
--------------------------------------------------------------------------------
1 | const tinycolor = require('./tinycolor');
2 |
3 | const colorMix = function(color, index, mix = true){
4 | const darkestL = 8;
5 | const midL = 24;
6 | const lightL = 56;
7 | const darkStep = 2;
8 | const midStep = 4;
9 | const lightStep = 20;
10 | let grayL;
11 | if(index <= 9 ){
12 | grayL = (darkestL + darkStep * (index - 1)) / 100;
13 | }else if(index > 9 && index <= 12){
14 | grayL = (midL + midStep * (index - 9)) / 100;
15 | }else if(index > 12){
16 | grayL = (lightL + lightStep * (index - 13)) / 100;
17 | }
18 |
19 | const curGray = tinycolor({
20 | h: 0,
21 | s: 0,
22 | l: grayL
23 | }).toHexString();
24 |
25 | if(index >= 12 || !mix){
26 | return curGray;
27 | }else{
28 | const mixColor = tinycolor.mix(color, curGray, 96).toHexString();
29 | return mixColor;
30 | }
31 | };
32 |
33 |
34 | const colorPalette = function(color, index) {
35 | const darkestL = 16;
36 | const lightestL = 96;
37 | const hsl = tinycolor(color).toHsl();
38 | const curLight = hsl.l * 100;
39 |
40 | let light;
41 | const darkStep = Math.max((curLight - darkestL) / 5, 0);
42 | const lightStep = Math.max((lightestL - curLight) /5, 0);
43 | if(index === 6){
44 | light = hsl.l;
45 | }else if(index < 6){
46 | light = (curLight + (6 - index)*lightStep)/100;
47 | }else if(index > 6){
48 | light = (curLight - (index - 6)*darkStep)/100;
49 | }
50 |
51 | return tinycolor({
52 | h: Math.round(hsl.h),
53 | s: hsl.s,
54 | l: light
55 | }).toHexString();
56 | }
57 |
58 | function mixGray(color, gray) {
59 | return tinycolor.mix(color, curGray, 96)
60 | }
61 |
62 | exports.colorMix = colorMix;
63 | exports.colorPalette = colorPalette;
64 | exports.mixGray = mixGray;
65 |
--------------------------------------------------------------------------------
/build/utils/cssVariable.json:
--------------------------------------------------------------------------------
1 | {
2 | "animation-duration-base": "0.3s",
3 | "animation-duration-fast": "0.2s",
4 | "blue": "#0278ff",
5 | "cyan": "#00d2d5",
6 | "orange": "#fec038",
7 | "red": "#ff6767",
8 | "white": "#fff",
9 | "black": "#000",
10 | "primary-color": "#0278ff",
11 | "success-color": "#00d2d5",
12 | "warning-color": "#fec038",
13 | "danger-color": "#ff6767",
14 | "gray-1": "#fafafa",
15 | "gray-2": "#f7f7f7",
16 | "gray-3": "#eeeeee",
17 | "gray-4": "#dcdcdc",
18 | "gray-5": "#c1c1c1",
19 | "text-color-1": "#000000",
20 | "text-color-2": "#333333",
21 | "text-color-3": "#666666",
22 | "text-color-4": "#999999",
23 | "text-color-5": "#c1c1c1",
24 | "text-color-link": "#0278ff",
25 | "background-color-base": "#fff",
26 | "background-color-active": "rgba(153, 153, 153, 0.1)",
27 | "primary-color-1": "#0068de",
28 | "primary-color-2": "#0278ff",
29 | "primary-color-3": "#3191ff",
30 | "primary-color-4": "#8ec2ff",
31 | "primary-color-5": "#ebf4ff",
32 | "success-color-1": "#00b8bb",
33 | "success-color-2": "#00d2d5",
34 | "success-color-3": "#0dfbff",
35 | "success-color-4": "#7cfdff",
36 | "success-color-5": "#ebffff",
37 | "warning-color-1": "#feb10b",
38 | "warning-color-2": "#fec038",
39 | "warning-color-3": "#fecb5c",
40 | "warning-color-4": "#ffe2a3",
41 | "warning-color-5": "#fff9eb",
42 | "danger-color-1": "#ff3030",
43 | "danger-color-2": "#ff6767",
44 | "danger-color-3": "#ff8181",
45 | "danger-color-4": "#ffb6b6",
46 | "danger-color-5": "#ffebeb",
47 |
48 | "picker-confirm-btn-color": "#000"
49 | }
--------------------------------------------------------------------------------
/build/utils/less2json.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const { generate } = require('./color-palette');
4 |
5 | const ignoreKeys = ['primary-color-', 'success-color-', 'warning-color-', 'danger-color-'];
6 |
7 | function checkKey(key) {
8 | return !ignoreKeys.some(item => key.search(item) > -1);
9 | }
10 |
11 | function less2json(varPath) {
12 | const json = {};
13 | const less = fs.readFileSync(varPath).toString();
14 | const lines = less.split('\n');
15 |
16 | lines.forEach(line => {
17 | if (line.indexOf('@') > -1) {
18 | let key = line.split(':')[0];
19 | if (key && checkKey(key)) {
20 | key = key.replace('@', '').trim();
21 | let value = line.split(':')[1];
22 | if (value) {
23 | value = value.split('//')[0].split(';')[0].trim();
24 | json[key] = value;
25 | }
26 | }
27 | }
28 | });
29 |
30 | const baseColorKeys = ['primary-color', 'success-color', 'warning-color', 'danger-color'];
31 |
32 | baseColorKeys.forEach(key => {
33 | let value = json[key];
34 | if (value.indexOf('@') > -1) {
35 | value = value.substring(1);
36 | json[key] = json[value.trim()];
37 | }
38 | });
39 |
40 | baseColorKeys.forEach(key => {
41 | var color = json[key];
42 | if (color) {
43 | const colors = generate(color);
44 | const colorIndex = [6, 5, 4, 2, 0];
45 | colorIndex.forEach((index, i) => {
46 | json[`${key}-${i + 1}`] = colors[index];
47 | });
48 | }
49 | });
50 |
51 | Object.keys(json).forEach(key => {
52 | let valueArr = json[key].split(' ');
53 | valueArr.forEach((value, index) => {
54 | if (value[0] === '@') {
55 | value = value.substring(1);
56 | if (json[value]) {
57 | valueArr[index] = json[value];
58 | }
59 | }
60 | });
61 |
62 | json[key] = valueArr.join(' ');
63 | });
64 |
65 | // var.config.json
66 | const configJsonPath = path.join(__dirname, '../../src/style/var.config.json');
67 | fs.writeFileSync(configJsonPath, JSON.stringify(json));
68 | }
69 | module.exports = less2json;
70 |
--------------------------------------------------------------------------------
/build/utils/lessConfig.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 主题定制演示
3 | */
4 | const fs = require('fs');
5 | const path = require('path');
6 | const LessVarMark = require('@okee-uikit/less-var-mark');
7 | const less2json = require('./less2json');
8 |
9 | const varJsonPath = path.join(__dirname, '../../src/style/var.config.json');
10 | const rootLessPath = path.join(__dirname, '../../src/style/root.less');
11 | const lessVarPath = path.join(__dirname, '../../src/style/var.less');
12 |
13 | const cssVariableJson = require('./cssVariable.json');
14 |
15 | let lessVars = {};
16 | let lessMap = {};
17 |
18 | // 清空 var.config.json
19 | fs.writeFileSync(varJsonPath, '{}');
20 |
21 | // 设置 var.config.json
22 | less2json(lessVarPath);
23 |
24 | // 获取 var.config.json
25 | const configJson = require(varJsonPath);
26 |
27 | Object.keys(cssVariableJson).forEach((key) => {
28 | lessVars[key] = configJson[key];
29 | });
30 |
31 | // 清空 root.less
32 | fs.writeFileSync(rootLessPath, '//');
33 |
34 | // 写入root.less
35 | const rootJson = {};
36 | Object.keys(lessVars).forEach(key => {
37 | rootJson[`--${key}`] = lessVars[key];
38 | });
39 | fs.writeFileSync(
40 | rootLessPath,
41 | `
42 | /* stylelint-disable */
43 | :root${JSON.stringify(rootJson)}
44 | `
45 | .replace('}', ';}')
46 | .replace(/,/g, ';')
47 | .replace(/"/g, ''),
48 | );
49 |
50 | // 设置 lessMap
51 | const lessVarMark = new LessVarMark();
52 | const lessVarJson = lessVarMark.variables2Json(fs.readFileSync(`${lessVarPath}`, 'utf-8'));
53 | lessMap = lessVarMark.getVariablesMap(lessVars, lessVarJson);
54 |
55 | module.exports = {
56 | lessVars,
57 | lessMap,
58 | };
59 |
--------------------------------------------------------------------------------
/build/version.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | let conf;
4 | try {
5 | conf = require('../package.json');
6 | } catch (e) {
7 | console.log(e);
8 | }
9 |
10 | process.stdout.write(conf.version);
--------------------------------------------------------------------------------
/build/webpack.base.js:
--------------------------------------------------------------------------------
1 | /**
2 | * webpack basic config
3 | */
4 | const path = require('path');
5 |
6 | module.exports = {
7 | resolve: {
8 | extensions: [".ts", ".tsx", ".js", ".jsx", ".json", '.css', 'less']
9 | },
10 | module: {
11 | rules: [{
12 | test: /\.(ts|tsx|js|jsx)$/,
13 | exclude: /node_modules/,
14 | use: {
15 | loader: 'babel-loader',
16 | options: {
17 | rootMode: 'upward'
18 | }
19 | }
20 | },
21 | {
22 | test: /\.(css|less)$/,
23 | sideEffects: true,
24 | include: /(node_modules)/,
25 | use: [
26 | 'style-loader',
27 | 'css-loader',
28 | {
29 | loader: 'less-loader',
30 | options: {
31 | paths: [path.resolve(__dirname, 'node_modules')],
32 | javascriptEnabled: true,
33 | noIeCompat: true,
34 | },
35 | },
36 | ],
37 | },
38 | {
39 | test: /\.(css|less)$/,
40 | sideEffects: true,
41 | exclude: /(node_modules)/,
42 | use: [
43 | 'style-loader',
44 | 'css-loader',
45 | 'postcss-loader',
46 | {
47 | loader: 'postcss-loader',
48 | options: {
49 | ident: 'postcss',
50 | plugins: [require('@okee-uikit/postcss-var')],
51 | },
52 | },
53 | {
54 | loader: 'less-loader',
55 | options: {
56 | paths: [path.resolve(__dirname, 'node_modules')],
57 | javascriptEnabled: true,
58 | noIeCompat: true,
59 | },
60 | },
61 | ],
62 | },
63 | {
64 | test: /\.svg$/,
65 | loader: 'url-loader'
66 | }]
67 | }
68 | };
69 |
--------------------------------------------------------------------------------
/build/webpack.build.js:
--------------------------------------------------------------------------------
1 | /**
2 | * webpack build entry
3 | */
4 | const path = require('path');
5 | const merge = require('webpack-merge');
6 | const config = require('./webpack.base');
7 |
8 | const isMinify = process.argv.indexOf('-p') !== -1;
9 |
10 | delete config.serve;
11 |
12 | module.exports = merge(config, {
13 | mode: 'production',
14 | entry: './es/index.js',
15 | output: {
16 | path: path.join(__dirname, '../lib'),
17 | library: 'okui',
18 | libraryTarget: 'umd',
19 | umdNamedDefine: true,
20 | filename: isMinify ? '[name].min.js' : '[name].js',
21 | globalObject: 'typeof self !== \'undefined\' ? self : this'
22 | },
23 | externals: {
24 | 'react': 'React',
25 | 'react-dom': 'ReactDOM'
26 | },
27 | performance: false,
28 | optimization: {
29 | minimize: isMinify
30 | },
31 | });
32 |
--------------------------------------------------------------------------------
/build/webpack.site.dev.js:
--------------------------------------------------------------------------------
1 | /**
2 | * webpack mobile
3 | */
4 | const path = require('path');
5 | const merge = require('webpack-merge');
6 | const HtmlWebpackPlugin = require('html-webpack-plugin');
7 |
8 | const config = require('./webpack.base');
9 |
10 | function resolve (dir) {
11 | return path.join(__dirname, '..', dir);
12 | }
13 |
14 | module.exports = merge(config, {
15 | mode: 'development',
16 | entry: {
17 | 'index': resolve('docs/mobile/index'),
18 | },
19 | output: {
20 | path: resolve('/docs/dist'),
21 | publicPath: '/',
22 | chunkFilename: 'async_[name].js'
23 | },
24 | devtool: 'cheap-module-eval-source-map',
25 | devServer: {
26 | host: '0.0.0.0',
27 | port: 8094,
28 | },
29 | plugins: [
30 | new HtmlWebpackPlugin({
31 | template: 'docs/mobile/template.html',
32 | filename: 'index.html',
33 | inject: false
34 | })
35 | ],
36 | });
37 |
--------------------------------------------------------------------------------
/docs/mobile/components/demo-title/index.less:
--------------------------------------------------------------------------------
1 | .dome-title {
2 | display: flex;
3 | align-items: center;
4 | color: #333333;
5 | padding: 16px 0;
6 | .svg-icon-pointer {
7 | width: 20px;
8 | height: 20px;
9 | margin-right: 8px;
10 | }
11 | &-word {
12 | font-weight: 500;
13 | font-size: 16px;
14 | line-height: 24px;
15 | }
16 | }
--------------------------------------------------------------------------------
/docs/mobile/components/demo-title/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import pointerSvg from '../../../static/pointer.svg'
3 | import './index.less'
4 |
5 | interface DemoTitleProps {
6 | title: string;
7 | }
8 | const DemoTitle: FC = (props: DemoTitleProps) => {
9 |
10 | const { title } = props
11 |
12 | return (
13 |
14 |

15 |
{title}
16 |
17 | )
18 | }
19 |
20 | export { DemoTitle }
--------------------------------------------------------------------------------
/docs/mobile/components/mobile-card/index.less:
--------------------------------------------------------------------------------
1 | .demo-cell {
2 | // margin-top: 16px;
3 | &-content-slot {
4 | padding: 16px 0;
5 | }
6 | &-header {
7 | &-title {
8 | display: flex;
9 | justify-content: space-between;
10 | align-items: center;
11 | font-size: 14px;
12 | line-height: 22px;
13 | &__left {
14 | color: #C1C1C1;
15 | font-family: PingFang SC;
16 | font-style: normal;
17 | font-weight: normal;
18 | }
19 | }
20 | &-line {
21 | margin-top: 9px;
22 | height: 1px;
23 | background-color: #ebebeb;
24 | position: relative;
25 | &::before {
26 | display: inline-block;
27 | content: '';
28 | width: 32px;
29 | height: 3px;
30 | background: #3337c5;
31 | position: absolute;
32 | top: -1px;
33 | }
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/docs/mobile/components/mobile-card/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import './index.less'
3 |
4 | interface DemoCellProps {
5 | title: string;
6 | children: any;
7 | style?: object;
8 | className?: string;
9 | }
10 | const MobileCard: FC = (props: DemoCellProps) => {
11 |
12 | const { title } = props
13 | const centerEle = props.children || ''
14 | const className = `${props.className || ''} demo-cell-content-slot`
15 | return (
16 |
17 |
18 |
19 | { title }
20 |
21 |
22 |
23 | { centerEle }
24 |
25 |
26 | )
27 | }
28 |
29 | export { MobileCard }
--------------------------------------------------------------------------------
/docs/mobile/components/mobile-header/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Header } from '../../../../src/index'
3 |
4 | interface DemoHeaderProps {
5 | title: string;
6 | onClickLeft: () => void;
7 | }
8 |
9 | class DemoHeader extends React.Component {
10 |
11 | render(): JSX.Element {
12 | return (
13 |
14 | { this.props.title }
15 |
16 | )
17 |
18 | }
19 | }
20 |
21 | export default DemoHeader
--------------------------------------------------------------------------------
/docs/mobile/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import { Route } from 'react-router'
4 | import { HashRouter } from 'react-router-dom'
5 |
6 | import App from './App'
7 | import routes, { RouteType } from './routes'
8 |
9 | import { LocaleContext } from '../../src/index'
10 | import local from '../../src/locale/zh-CN'
11 |
12 | import Stats from 'stats.js';
13 |
14 | ReactDOM.render(
15 |
16 |
17 |
18 | {
19 | routes.map((route: RouteType, index: number) => (
20 |
25 | ))
26 | }
27 |
28 |
29 | ,
30 | document.getElementById('root')
31 | )
32 |
33 |
34 |
35 | // const stats = new Stats();
36 | // stats.showPanel( 1 ); // 0: fps, 1: ms, 2: mb, 3+: custom
37 | // document.body.appendChild( stats.dom );
38 |
39 | // function animate() {
40 |
41 | // stats.begin();
42 |
43 | // // monitored code goes here
44 |
45 | // stats.end();
46 |
47 | // requestAnimationFrame( animate );
48 |
49 | // }
50 |
51 | // requestAnimationFrame( animate );
--------------------------------------------------------------------------------
/docs/mobile/routes.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 路由
3 | */
4 | import { importAll, ImportMap, RequireContext } from '../utils'
5 | import config, { Lang } from '../config'
6 | import { NavsItem } from '../type'
7 |
8 | export interface RouteType {
9 | component: any;
10 | path: string;
11 | }
12 |
13 | const routes: RouteType[] = []
14 | const componentMap: ImportMap = {}
15 | const packages: RequireContext = require.context(
16 | '../../src/components',
17 | true,
18 | /demo\/index\.tsx$/
19 | )
20 |
21 | importAll(componentMap, packages)
22 |
23 |
24 | function findNav (nav: NavsItem, lang: string): void {
25 | if (nav.list) {
26 | nav.list.forEach((item) => findNav(item, lang))
27 | } else {
28 | let { path } = nav
29 |
30 | if (path) {
31 | path = path.replace('/', '')
32 |
33 | const module = componentMap[`./${path}/demo/index.tsx`]
34 | // @ts-ignore
35 | const component = module ? module.default : null
36 |
37 | if (component) {
38 | routes.push({
39 | path: `/${lang}/${path}`,
40 | component
41 | })
42 | }
43 | }
44 | }
45 | }
46 |
47 | Object.keys(config).forEach((key: string) => {
48 | const lang = key as Lang
49 | const navs = config[lang].navs || []
50 |
51 | navs.forEach((nav) => {
52 | findNav(nav, lang)
53 | })
54 | })
55 |
56 | export default routes
57 |
--------------------------------------------------------------------------------
/docs/mobile/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | OKee-Mobile-React-Docs
15 |
16 |
29 |
30 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/docs/mobile/tracking.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * 导航守卫
3 | */
4 | import { useEffect } from 'react'
5 | import { RouteComponentProps, withRouter } from 'react-router'
6 |
7 | export default withRouter((props: RouteComponentProps) => {
8 | useEffect(() => {
9 | // @ts-ignore
10 | window.collectEvent('predefinePageView', {
11 | route: window.location.hash.slice(1),
12 | type: '0', // PC端窗口 type 为 0
13 | })
14 | }, [location.hash])
15 |
16 | return null
17 | });
18 |
19 |
--------------------------------------------------------------------------------
/docs/static/i18n.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/docs/static/menu_icons/comp.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/docs/static/menu_icons/comp_active.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/docs/static/menu_icons/i18n.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/docs/static/menu_icons/i18n_active.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/docs/static/menu_icons/intro.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/docs/static/menu_icons/intro_active.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/docs/static/menu_icons/quickstart.svg:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/docs/static/menu_icons/quickstart_active.svg:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/docs/static/menu_icons/theme.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/docs/static/menu_icons/theme_active.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/docs/static/pointer.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/docs/static/send.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/docs/type.ts:
--------------------------------------------------------------------------------
1 | export interface Anchor {
2 | id: string;
3 | label: string;
4 | }
5 | export interface NavsItem {
6 | label: string;
7 | path?: string;
8 | activeIcon?: string;
9 | defaultIcon?: string;
10 | anchors?: Anchor[];
11 | list?: NavsItem[];
12 | }
13 |
14 | export interface ByDocsConfig {
15 | logo?: string;
16 | navs?: NavsItem[];
17 | onLinkTo?: (item: NavsItem) => string | void;
18 | anchorPos?: 'left' | 'right';
19 | }
20 | export declare type Config = ByDocsConfig;
--------------------------------------------------------------------------------
/docs/utils/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 工具函数
3 | */
4 | /**
5 | * this interface from '@types/webpack-env'
6 | */
7 |
8 |
9 | const ua: string = navigator.userAgent;
10 | export const isMobile: boolean = /ios|iphone|ipod|ipad|android/i.test(ua);
11 | export interface RequireContext {
12 | keys(): string[];
13 | (id: string): any;
14 | (id: string): T;
15 | resolve(id: string): string;
16 | id: string;
17 | }
18 |
19 | export interface ImportMap {
20 | [key: string]: RequireContext;
21 | }
22 |
23 | // https://webpack.docschina.org/guides/dependency-management/
24 | export function importAll(map: ImportMap, r: RequireContext): void {
25 | r.keys().forEach(key => {
26 | map[key] = r(key)
27 | })
28 | }
29 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | 'autoprefixer': {},
4 | 'postcss-px2rem': {
5 | remUnit: 20
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/common/popup/context.tsx:
--------------------------------------------------------------------------------
1 | import { useMemo, useRef } from 'react';
2 |
3 | export type MountElem = HTMLElement | (() => HTMLElement);
4 |
5 | export type OverlayConfig = {
6 | zIndex: number;
7 | className?: string;
8 | customStyle?: string | object[] | object;
9 | mountElem?: MountElem;
10 | };
11 |
12 | export interface StackItem {
13 | close: () => void;
14 | config: OverlayConfig;
15 | }
16 |
17 | export const context = {
18 | zIndex: 2000,
19 | lockCount: 0,
20 | stack: [] as StackItem[],
21 |
22 | get top(): StackItem {
23 | return this.stack[this.stack.length - 1];
24 | },
25 | };
26 |
27 | export interface UseZIndexOptions {
28 | update?: boolean;
29 | }
30 | export function useZIndex(props: UseZIndexOptions, ...deps: any[]): number {
31 | const { update = true } = props;
32 |
33 | const zIndexRef = useRef(0);
34 | return useMemo(() => {
35 | if (!zIndexRef.current || update) {
36 | zIndexRef.current = context.zIndex++;
37 | }
38 | return zIndexRef.current;
39 | // eslint-disable-next-line react-hooks/exhaustive-deps
40 | }, [update, ...deps]);
41 | }
42 |
--------------------------------------------------------------------------------
/src/common/touch.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Touch
3 | */
4 | export type TouchFunction = (event: TouchEvent) => void;
5 |
6 | export default class Touch {
7 | static MIN_DISTANCE = 5;
8 | static getDirection = function (x: number, y: number): string {
9 | if (x > y && x > Touch.MIN_DISTANCE) {
10 | return 'horizontal';
11 | }
12 |
13 | if (y > x && y > Touch.MIN_DISTANCE) {
14 | return 'vertical';
15 | }
16 |
17 | return '';
18 | };
19 |
20 | public startX: number;
21 | public startY: number;
22 | public deltaX: number;
23 | public deltaY: number;
24 | public offsetX: number;
25 | public offsetY: number;
26 | public direction: string;
27 |
28 | constructor() {
29 | this.startX = 0;
30 | this.startY = 0;
31 | this.deltaX = 0;
32 | this.deltaY = 0;
33 | this.offsetX = 0;
34 | this.offsetY = 0;
35 | this.direction = '';
36 | }
37 |
38 | resetTouchStatus: () => void = () => {
39 | this.direction = '';
40 | this.deltaX = 0;
41 | this.deltaY = 0;
42 | this.offsetX = 0;
43 | this.offsetY = 0;
44 | };
45 |
46 | touchStart: TouchFunction = (event: TouchEvent) => {
47 | this.resetTouchStatus();
48 | this.startX = event.touches[0].clientX;
49 | this.startY = event.touches[0].clientY;
50 | };
51 |
52 | touchMove: TouchFunction = (event: TouchEvent) => {
53 | const touch = event.touches[0];
54 | this.deltaX = touch.clientX - this.startX;
55 | this.deltaY = touch.clientY - this.startY;
56 | this.offsetX = Math.abs(this.deltaX);
57 | this.offsetY = Math.abs(this.deltaY);
58 | this.direction = this.direction || Touch.getDirection(this.offsetX, this.offsetY);
59 | };
60 | }
61 |
--------------------------------------------------------------------------------
/src/components/action-sheet/action-sheet-item.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import createBEM, { prefix } from '../../utils/create/createBEM';
3 | const bem = createBEM('action-sheet');
4 |
5 | export interface ActionSheetItemType {
6 | name: string;
7 | subname?: string;
8 | loading?: boolean;
9 | active?: boolean;
10 | disabled?: boolean;
11 | className?: string;
12 | callback?: (item: ActionSheetItemType) => void;
13 | }
14 |
15 | export interface ActionSheetItemProps {
16 | item: ActionSheetItemType;
17 | index: number;
18 | onClick: (item: ActionSheetItemType, index: number) => void;
19 | }
20 |
21 | export default function ActionSheetItem(props: ActionSheetItemProps): JSX.Element {
22 | const { onClick, item, index } = props;
23 | const disabled = item.disabled || item.loading;
24 |
25 | function onClickOption(event: React.MouseEvent): void {
26 | event.stopPropagation();
27 |
28 | if (item.disabled || item.loading) {
29 | return;
30 | }
31 |
32 | if (item.callback) {
33 | item.callback(item);
34 | }
35 | onClick(item, index);
36 | }
37 |
38 | // loading态的样式icon待实现
39 | const OptionContent = item.loading ? (
40 |
41 | ) : (
42 | [
43 |
44 | {item.name}
45 | ,
46 | item.subname && (
47 |
48 | {item.subname}
49 |
50 | ),
51 | ]
52 | );
53 |
54 | return (
55 |
61 | {OptionContent}
62 |
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/src/components/action-sheet/createActionSheet.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 |
4 | import { ActionSheetOptions } from './index';
5 | import ActionSheet, { ActionSheetProps } from './action-sheet';
6 |
7 | export default function CreateActionSheet(options: ActionSheetOptions): void {
8 | if (typeof window === 'undefined') {
9 | return;
10 | }
11 |
12 | const div: HTMLElement = document.createElement('div');
13 | document.body.appendChild(div);
14 |
15 | function destroy(): void {
16 | const unmountResult = ReactDOM.unmountComponentAtNode(div);
17 | if (unmountResult && div.parentNode) {
18 | div.parentNode.removeChild(div);
19 | }
20 | }
21 |
22 | function render(options: ActionSheetProps): void {
23 | ReactDOM.render(, div);
24 | }
25 |
26 | function close(): void {
27 | /* eslint-disable @typescript-eslint/no-use-before-define */
28 | currentOptions = Object.assign({}, currentOptions, {
29 | value: false,
30 | afterClose: destroy,
31 | });
32 |
33 | render(currentOptions);
34 | }
35 |
36 | let currentOptions = {
37 | ...options,
38 | close,
39 | value: true,
40 | };
41 |
42 | render(currentOptions);
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/action-sheet/index.tsx:
--------------------------------------------------------------------------------
1 | import CreateActionSheet from './createActionSheet';
2 | import { ActionSheetItemType } from './action-sheet-item';
3 |
4 | import ActionSheet from './action-sheet';
5 | import ActionSheetItem from './action-sheet-item';
6 |
7 | export type { ActionSheetProps } from './action-sheet';
8 | export type { ActionSheetItemProps, ActionSheetItemType } from './action-sheet-item';
9 |
10 | export interface ActionSheetOptions {
11 | title?: string;
12 | actions?: ActionSheetItemType[];
13 | duration?: number;
14 | cancelText?: string;
15 | showClose?: boolean;
16 | closeOnClickAction?: boolean;
17 | safeAreaInsetBottom?: boolean;
18 | className?: string;
19 | onCancel?: () => void;
20 | onClose?: () => void;
21 | onSelect?: (item: ActionSheetItemType, index?: number) => void;
22 | }
23 |
24 | export default {
25 | showActionSheet: CreateActionSheet,
26 | ActionSheet,
27 | ActionSheetItem,
28 | };
29 |
--------------------------------------------------------------------------------
/src/components/badge/demo/index.less:
--------------------------------------------------------------------------------
1 | @import '../../../style/common';
2 |
3 | .demo-badge {
4 | .@{prefix}badge {
5 | margin-right: 16px;
6 | }
7 |
8 | .@{prefix}cell__title,
9 | .@{prefix}cell__content {
10 | display: flex;
11 | align-items: center;
12 | overflow: visible;
13 | }
14 |
15 | .@{prefix}cell__content {
16 | justify-content: flex-end;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/badge/demo/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable semi */
2 | import React, { FC } from 'react';
3 | import { useHistory } from 'react-router-dom';
4 | import { Badge, Cell } from '../../../../src/index';
5 | import { MobileCard } from '../../../../docs/mobile/components/mobile-card/index';
6 | import DemoHeader from '../../../../docs/mobile/components/mobile-header/index';
7 |
8 | import './index.less';
9 |
10 | const DemoBadge: FC<{}> = () => {
11 | const history = useHistory();
12 | const onClickLeft = (): void => {
13 | history.goBack();
14 | };
15 | return (
16 |
17 |
18 |
19 |
20 | {
22 | return 标题;
23 | }}
24 | value={(): any => {
25 | return ;
26 | }}
27 | isLink
28 | border
29 | size="large"
30 | > |
31 | {
33 | return 标题;
34 | }}
35 | value={(): any => {
36 | return ;
37 | }}
38 | isLink
39 | border
40 | size="large"
41 | > |
42 |
43 |
44 | );
45 | };
46 |
47 | export default DemoBadge;
48 |
--------------------------------------------------------------------------------
/src/components/badge/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common';
2 |
3 | .@{prefix}badge {
4 | position: relative;
5 | display: inline-flex;
6 |
7 | &__info {
8 | position: absolute;
9 | top: 0;
10 | right: (@badge-height / 2);
11 | display: flex;
12 | flex-flow: row nowrap;
13 | align-content: center;
14 | align-items: center;
15 | justify-content: center;
16 | box-sizing: border-box;
17 | min-width: @badge-height;
18 | height: @badge-height;
19 | padding: 0 @badge-padding-horizontal;
20 | color: @badge-text-color;
21 | font-size: @badge-font-size;
22 | line-height: 1;
23 | white-space: nowrap;
24 | text-align: center;
25 | background-color: @badge-background-color;
26 | border-radius: (@badge-height / 2);
27 | transform: translate3d(100%, -50%, 0);
28 |
29 | &::after {
30 | position: absolute;
31 | top: -1px;
32 | left: -1px;
33 | z-index: -1;
34 | display: block;
35 | width: 100%;
36 | height: 100%;
37 | border: 1px solid @badge-border-color;
38 | border-radius: (@badge-height / 2) + 1px;
39 | content: ' ';
40 | }
41 | }
42 |
43 | &--is-dot &__info {
44 | right: (@badge-dot-size / 2);
45 | width: @badge-dot-size;
46 | min-width: @badge-dot-size;
47 | height: @badge-dot-size;
48 | padding: 0;
49 | border-radius: 50%;
50 |
51 | &::after {
52 | border-radius: 50%;
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/badge/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable indent */
2 | /* eslint-disable semi */
3 | import React, { AllHTMLAttributes, FC, PropsWithChildren } from 'react';
4 |
5 | import createBEM, { addClass } from '../../utils/create/createBEM';
6 |
7 | const bem = createBEM('badge');
8 |
9 | export interface BadgeProps extends AllHTMLAttributes {
10 | /**
11 | * The number or text content.
12 | */
13 | value?: number | string;
14 |
15 | /**
16 | * The max limit when value type is number.
17 | */
18 | max?: number;
19 |
20 | /**
21 | * Display as a dot.
22 | * @default false
23 | */
24 | isDot?: boolean;
25 | }
26 |
27 | const Badge: FC = (props: PropsWithChildren) => {
28 | const { className, style, value, max, isDot, children, ...attrs } = props;
29 |
30 | const info = isDot ? '' : typeof value === 'number' && max && max < value ? `${max}+` : value;
31 |
32 | const classes = bem([{ 'is-dot': isDot }]);
33 |
34 | return (
35 |
36 | {children}
37 | {info}
38 |
39 | );
40 | };
41 |
42 | Badge.displayName = 'Badge';
43 |
44 | export default Badge;
45 |
--------------------------------------------------------------------------------
/src/components/button/demo/index.less:
--------------------------------------------------------------------------------
1 | @import '../../../style/common';
2 |
3 | .demo-button {
4 | .@{prefix}-button {
5 | margin-right: 10px;
6 | margin-bottom: 10px;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/cell/demo/index.less:
--------------------------------------------------------------------------------
1 | // .demo-comp-cell {
2 | // padding-right: 0;
3 | // padding-left: 0;
4 |
5 | // .demo-cell + .demo-cell {
6 | // margin-top: 16px;
7 | // }
8 |
9 | // h4 {
10 | // margin: 0;
11 | // padding: 16px 16px 8px;
12 | // font-size: 18px;
13 | // }
14 |
15 | // p {
16 | // margin: 0;
17 | // padding: 8px 16px 4px;
18 | // font-size: 16px;
19 | // }
20 |
21 | // h4 + p {
22 | // padding-top: 0;
23 | // }
24 | // }
25 |
--------------------------------------------------------------------------------
/src/components/cell/demo/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable semi */
2 | import React from 'react';
3 | import { Cell } from '../../../../src/index';
4 | import { MobileCard } from '../../../../docs/mobile/components/mobile-card/index';
5 | import DemoHeader from '../../../../docs/mobile/components/mobile-header/index';
6 |
7 | import './index.less';
8 |
9 | export default class DemoCell extends React.Component {
10 | render(): JSX.Element {
11 | const onClickLeft = (): void => {
12 | this.props.history.goBack();
13 | };
14 | const title = '标题';
15 | const content = '内容';
16 |
17 | // const longTitle = sadfaasdfaasdfasdfaasdfasdfasdfasdfasd
;
22 |
23 | return (
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 |
--------------------------------------------------------------------------------
/src/components/checkbox/demo/index.less:
--------------------------------------------------------------------------------
1 | @import '../../../style/common';
2 |
3 | .demo.checkbox {
4 | .@{prefix}-checkbox {
5 | margin: 10px 0;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/checkbox/group.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Component Checkbox Group
3 | */
4 | import React, { useState, FC, Context, CSSProperties, MouseEventHandler } from 'react';
5 |
6 | import createBEM, { addClass } from '../../utils/create/createBEM';
7 |
8 | import { CheckboxType, CheckboxSize, CheckboxValue } from './types';
9 |
10 | export interface CheckboxGroupChild {
11 | value: CheckboxValue;
12 | checked: boolean;
13 | }
14 |
15 | export interface CheckboxGroupState {
16 | type?: CheckboxType;
17 | size?: CheckboxSize;
18 | value?: CheckboxValue[];
19 | onChange?: (value: CheckboxValue[]) => void;
20 | children?: CheckboxGroupChild[];
21 | }
22 |
23 | export const CheckboxGroupContext: Context = React.createContext({});
24 |
25 | const bem = createBEM('checkbox-group');
26 |
27 | export interface CheckboxGroupProps {
28 | className?: string;
29 | style?: CSSProperties;
30 | children?: React.ReactNode;
31 |
32 | /**
33 | * The type of checkbox.
34 | */
35 | type?: CheckboxType;
36 |
37 | /**
38 | * Size of checkbox.
39 | */
40 | size?: CheckboxSize;
41 |
42 | /**
43 | * The value of checkbox.
44 | */
45 | value: CheckboxValue[];
46 |
47 | /**
48 | * When checkbox value changed.
49 | */
50 | onChange?: (value: CheckboxValue[]) => void;
51 |
52 | /**
53 | * Optional callback when Radio Group is clicked.
54 | */
55 | onClick?: MouseEventHandler;
56 | }
57 |
58 | const CheckboxGroup: FC = (props: CheckboxGroupProps) => {
59 | const { className, style, type, size, value, onChange, children } = props;
60 |
61 | const [checkboxGroupChildren] = useState([]);
62 |
63 | return (
64 |
73 |
74 | {children}
75 |
76 |
77 | );
78 | };
79 |
80 | CheckboxGroup.displayName = 'CheckboxGroup';
81 |
82 | export default React.memo(CheckboxGroup);
83 |
--------------------------------------------------------------------------------
/src/components/checkbox/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Component Checkbox Entery
3 | */
4 | import { NamedExoticComponent, PropsWithChildren } from 'react';
5 |
6 | import InternalCheckbox from './checkbox';
7 | import CheckboxGroup from './group';
8 | import type { CheckboxProps } from './checkbox';
9 |
10 | export type { CheckboxProps };
11 | export type { CheckboxGroupProps } from './group';
12 |
13 | export * from './types';
14 |
15 | export interface Checkbox extends NamedExoticComponent> {
16 | Group: typeof CheckboxGroup;
17 | }
18 |
19 | const Checkbox = InternalCheckbox as Checkbox;
20 | Checkbox.Group = CheckboxGroup;
21 |
22 | export { CheckboxGroup };
23 |
24 | export default Checkbox;
25 |
--------------------------------------------------------------------------------
/src/components/checkbox/types.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * interface
3 | */
4 | export type CheckboxType = 'default' | 'number' | 'square';
5 |
6 | export type CheckboxSize = 'small' | 'normal' | 'large';
7 |
8 | export type CheckboxValue = number | string;
9 |
--------------------------------------------------------------------------------
/src/components/col/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common';
2 |
3 | .@{prefix}col {
4 | float: left;
5 | box-sizing: border-box;
6 | }
7 |
8 | .generate-col(@n, @i: 1) when (@i =< @n) {
9 | .@{prefix}col--@{i} {
10 | width: @i * (100% / 24);
11 | }
12 |
13 | .@{prefix}col--offset-@{i} {
14 | margin-left: @i * (100% / 24);
15 | }
16 |
17 | .generate-col(@n, (@i + 1));
18 | }
19 |
20 | .generate-col(24);
21 |
--------------------------------------------------------------------------------
/src/components/col/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Component Col
3 | */
4 | import React, { FC, PropsWithChildren } from 'react';
5 |
6 | import RowContext from '../row/RowContext';
7 |
8 | import createBEM from '../../utils/create/createBEM';
9 | import { value2DomUnit } from '../../utils/dom/unit';
10 |
11 | const bem = createBEM('col');
12 |
13 | export interface ColProps {
14 | span: number | string;
15 | offset?: number | string;
16 | }
17 |
18 | export const Col: FC = (props: PropsWithChildren) => {
19 | const { span, offset } = props;
20 |
21 | return (
22 |
23 | {({ gutter }): JSX.Element => {
24 | const colStyle: React.CSSProperties = {};
25 |
26 | if (gutter) {
27 | const gap = value2DomUnit(gutter, 0.5);
28 | colStyle.paddingLeft = gap;
29 | colStyle.paddingRight = gap;
30 | }
31 |
32 | return (
33 |
40 | {props.children}
41 |
42 | );
43 | }}
44 |
45 | );
46 | };
47 |
48 | Col.displayName = 'Col';
49 |
50 | export default Col;
51 |
--------------------------------------------------------------------------------
/src/components/collapse/demo/index.less:
--------------------------------------------------------------------------------
1 | .demo.demo-collapse {
2 | background: white;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/date-time-picker/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common.less';
2 |
3 | .@{prefix}date-time-picker {
4 | font-size: @date-time-picker-font-size;
5 |
6 | &__range {
7 | position: relative;
8 | display: flex;
9 | align-items: center;
10 | justify-content: center;
11 | height: @date-time-picker-range-height;
12 | .@{prefix}button--large {
13 | width: auto;
14 | padding-right: @date-time-picker-padding;
15 | padding-left: @date-time-picker-padding;
16 | }
17 |
18 | &__start {
19 | position: absolute;
20 | left: 0;
21 | }
22 |
23 | &__end {
24 | position: absolute;
25 | right: 0;
26 | }
27 |
28 | &__start,
29 | &__end {
30 | padding-right: @date-time-picker-padding;
31 | padding-left: @date-time-picker-padding;
32 |
33 | &--active {
34 | color: @primary-color;
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/dropdown-item/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common';
2 |
3 | .@{prefix}dropdown-item {
4 | position: absolute;
5 | width: 100%;
6 | overflow: hidden;
7 |
8 | &--down {
9 | top: @dropdown-height;
10 | }
11 |
12 | &--up {
13 | top: 0;
14 | transform: translate3d(0, -100%, 0);
15 | }
16 |
17 | &__cell {
18 | padding: 15px 16px;
19 | .@{prefix}cell__content {
20 | width: auto;
21 | line-height: 0;
22 | }
23 | .@{prefix}cell__whitespace {
24 | flex: none;
25 | width: 0;
26 | }
27 | .@{prefix}cell__title {
28 | flex: 1;
29 | min-width: 0;
30 | overflow: hidden;
31 | white-space: nowrap;
32 | text-overflow: ellipsis;
33 | }
34 | }
35 |
36 | &__icon {
37 | width: @dropdown-icon-size;
38 | height: @dropdown-icon-size;
39 | fill: @dropdown-icon-color;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/dropdown/demo/index.less:
--------------------------------------------------------------------------------
1 | .demo-dropdown {
2 | padding: 68px 0 20px 0;
3 |
4 | .demo-cell-header-title {
5 | padding: 0 20px;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/dropdown/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common';
2 |
3 | .@{prefix}dropdown {
4 | position: relative;
5 | display: flex;
6 | height: @dropdown-height;
7 | background-color: @dropdown-background-color;
8 | user-select: none;
9 |
10 | &__menu {
11 | position: relative;
12 | display: flex;
13 | flex: 1;
14 | align-items: center;
15 | justify-content: center;
16 | min-width: 0;
17 |
18 | &:not(&--first) {
19 | &::after {
20 | position: absolute;
21 | top: (@dropdown-height - @dropdown-line-height) / 2;
22 | bottom: (@dropdown-height - @dropdown-line-height) / 2;
23 | left: 0;
24 | border-left: 1px solid @cell-border-color;
25 | content: ' ';
26 | pointer-events: none;
27 | }
28 | }
29 |
30 | &:active {
31 | opacity: 0.7;
32 | }
33 |
34 | &--disabled {
35 | &:active {
36 | opacity: 1;
37 | }
38 |
39 | .@{prefix}dropdown__title {
40 | color: @dropdown-title-disabled-color !important;
41 | }
42 | }
43 | }
44 |
45 | &__title {
46 | position: relative;
47 | box-sizing: border-box;
48 | max-width: 100%;
49 | padding: 0 18px 0 12px;
50 | color: @dropdown-title-default-color;
51 | font-size: @dropdown-title-font-size;
52 |
53 | &--active {
54 | color: @dropdown-title-active-color;
55 | }
56 |
57 | &::after {
58 | position: absolute;
59 | top: 7px;
60 | right: 2px;
61 | border: 3px solid; /* no */
62 | border-color: transparent transparent currentColor currentColor;
63 | transform: rotate(-45deg);
64 | opacity: 0.8;
65 | content: '';
66 | }
67 |
68 | &--down {
69 | &::after {
70 | top: 10px;
71 | transform: rotate(135deg);
72 | }
73 | }
74 | }
75 |
76 | &__ellipsis {
77 | overflow: hidden;
78 | white-space: nowrap;
79 | text-overflow: ellipsis;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/components/header-tab/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common';
2 |
3 | .@{prefix}header-tab {
4 | position: relative;
5 | display: inline-flex;
6 | flex: 1;
7 | height: @header-height;
8 | font-size: @header-tab-font-size;
9 | text-align: center;
10 |
11 | &__box {
12 | position: relative;
13 | display: flex;
14 | flex-flow: row nowrap;
15 | align-content: center;
16 | align-items: center;
17 | justify-content: center;
18 | margin: 0 auto;
19 | color: @header-tab-color;
20 | opacity: 0.8;
21 | }
22 |
23 | &--active {
24 | font-size: @header-tab-active-font-size;
25 | }
26 |
27 | &--active &__box {
28 | color: @header-tab-active-color;
29 | font-weight: 500;
30 | opacity: 1;
31 | }
32 |
33 | &--primary &__box {
34 | color: @white;
35 | }
36 |
37 | &--active&--primary &__box {
38 | font-weight: 500;
39 | opacity: 1;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/header-tab/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable semi */
2 | import React, { FC, PropsWithChildren, CSSProperties, useContext } from 'react';
3 |
4 | import createBEM from '../../utils/create/createBEM';
5 | import { RenderFunction } from '../../utils/types';
6 |
7 | import { HeaderContext } from '../header';
8 | import { HeaderTabsContext } from '../header-tabs';
9 |
10 | const bem = createBEM('header-tab');
11 |
12 | export interface HeaderTabProps {
13 | className?: string;
14 | style?: CSSProperties;
15 |
16 | /**
17 | * The title of the header tab.
18 | */
19 | title?: string | JSX.Element | RenderFunction;
20 |
21 | /**
22 | * The id name of the header tab.
23 | */
24 | name?: string | number | boolean;
25 | }
26 |
27 | const HeaderTab: FC = (props: PropsWithChildren) => {
28 | const { className, style, title, name = '' } = props;
29 |
30 | const { type } = useContext(HeaderContext);
31 | const { activeName, setActiveName } = useContext(HeaderTabsContext);
32 |
33 | const active = name === activeName;
34 |
35 | let classes = bem([type, { active }]);
36 | if (className) {
37 | classes = `${classes} ${className}`;
38 | }
39 |
40 | return (
41 | setActiveName(name)}
46 | >
47 |
{typeof title === 'function' ? title({}) : title}
48 |
49 | );
50 | };
51 |
52 | HeaderTab.displayName = 'HeaderTab';
53 |
54 | export default HeaderTab;
55 |
--------------------------------------------------------------------------------
/src/components/header-tabs/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common';
2 |
3 | .@{prefix}header-tabs {
4 | position: relative;
5 | width: 100%;
6 | height: @header-height;
7 | background-color: transparent;
8 |
9 | &__box {
10 | position: relative;
11 | display: flex;
12 | }
13 |
14 | &__line {
15 | position: absolute;
16 | bottom: @header-line-bottom;
17 | left: 0;
18 | height: @header-line-height;
19 | background-color: @header-line-color;
20 | transition: @animation-duration-fast ease-in-out;
21 | transition-property: width, transform;
22 | will-change: width, transform;
23 | .okui-border-radius(1);
24 | }
25 |
26 | &--primary &__line {
27 | background-color: @white;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/components/header/demo/index.less:
--------------------------------------------------------------------------------
1 | .demo-header {
2 | .demo-fixed-placeholder {
3 | height: 44px;
4 | border: 1px dashed #ccc;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/header/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common';
2 |
3 | .@{prefix}header {
4 | position: relative;
5 | display: flex;
6 | flex-flow: row nowrap;
7 | align-content: center;
8 | align-items: center;
9 | justify-content: center;
10 | width: 100%;
11 | height: @header-height;
12 | background-color: @header-background-color;
13 |
14 | &--fixed {
15 | position: fixed;
16 | top: 0;
17 | left: 0;
18 | z-index: 1999;
19 | width: 100%;
20 | }
21 |
22 | &__icon {
23 | position: relative;
24 | width: @header-icon-size;
25 | height: @header-icon-size;
26 | vertical-align: middle;
27 | fill: @header-icon-fill;
28 | }
29 |
30 | &__left,
31 | &__right,
32 | &__title {
33 | position: relative;
34 | display: flex;
35 | flex: 2;
36 | flex-flow: row nowrap;
37 | align-content: center;
38 | align-items: center;
39 | height: 100%;
40 | }
41 |
42 | &__left {
43 | justify-content: flex-start;
44 | padding-left: @header-padding-horizontal;
45 | }
46 |
47 | &__right {
48 | justify-content: flex-end;
49 | padding-right: @header-padding-horizontal;
50 | }
51 |
52 | &__title {
53 | position: relative;
54 | display: flex;
55 | flex: 8;
56 | justify-content: center;
57 | overflow: hidden;
58 | color: @header-title-text-color;
59 | font-weight: @header-title-font-weight;
60 | font-size: @header-title-font-size;
61 | letter-spacing: 0;
62 | white-space: nowrap;
63 | text-overflow: ellipsis;
64 | }
65 |
66 | &--primary {
67 | background-color: @primary-color;
68 | }
69 |
70 | &--primary &__icon {
71 | fill: @white;
72 | }
73 |
74 | &--primary &__title {
75 | color: @white;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/components/icon/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common';
2 |
3 | .@{prefix}icon {
4 | display: inline-block;
5 | font-size: 0;
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/icon/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Component Icon
3 | */
4 | import React, { FC, CSSProperties, HTMLAttributes } from 'react';
5 |
6 | import createBEM, { addClass } from '../../utils/create/createBEM';
7 |
8 | import { value2DomUnit } from '../../utils/dom/unit';
9 |
10 | import { allIcons } from './all';
11 |
12 | import type { IconMap } from './all';
13 |
14 | export type { IconMap };
15 |
16 | export type IconName = keyof IconMap;
17 |
18 | const bem = createBEM('icon');
19 |
20 | export interface IconProps extends HTMLAttributes {
21 | className?: string;
22 | style?: CSSProperties;
23 |
24 | /**
25 | * Necessary
26 | */
27 | name: IconName;
28 |
29 | /**
30 | * size.
31 | */
32 | size?: string | number;
33 |
34 | /**
35 | * fill color
36 | */
37 | fill?: string;
38 |
39 | /**
40 | * stroke color
41 | */
42 | stroke?: string;
43 | }
44 |
45 | const Icon: FC = (props: IconProps) => {
46 | const { className, style = {}, name, size, fill, stroke, ...attributes } = props;
47 |
48 | if (size) {
49 | style.width = value2DomUnit(size);
50 | style.height = value2DomUnit(size);
51 | }
52 |
53 | if (fill) {
54 | style.fill = fill;
55 | }
56 | if (stroke) {
57 | style.stroke = stroke;
58 | }
59 |
60 | return (
61 |
69 | );
70 | };
71 |
72 | Icon.displayName = 'Icon';
73 |
74 | export default Icon;
75 |
--------------------------------------------------------------------------------
/src/components/icon/svg/arrow-down.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/icon/svg/arrow-left.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/icon/svg/arrow-right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/icon/svg/arrow-up.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/icon/svg/attention.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/icon/svg/check-one.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/icon/svg/check.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/icon/svg/circle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/icon/svg/close-one.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/icon/svg/close.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/icon/svg/default.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/icon/svg/error.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/icon/svg/filter.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/components/icon/svg/info.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/icon/svg/loading.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/icon/svg/partial-check.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/icon/svg/question-mark.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/components/icon/svg/radio-uncheck.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/icon/svg/radio.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/icon/svg/square-check-one.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/components/icon/svg/square-uncheck.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/src/components/icon/svg/success.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/icon/svg/triangular.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/icon/svg/warning.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/infinite-scroll/demo/index.less:
--------------------------------------------------------------------------------
1 | .demo-infinite-scroll.demo {
2 | padding-right: 0;
3 | padding-bottom: 0;
4 | padding-left: 0;
5 |
6 | .demo-cell {
7 | .custom-infinite-scroll {
8 | li {
9 | position: relative;
10 | display: flex;
11 | align-items: center;
12 | height: 44px;
13 | padding: 0 16px;
14 | list-style: none;
15 | }
16 |
17 | li:first-of-type::before,
18 | > li::after {
19 | position: absolute;
20 | left: 0;
21 | width: 100%;
22 | height: 1px;
23 | background-color: #c8c7cc;
24 | transform: scaleY(0.5);
25 | content: '';
26 | }
27 |
28 | li:first-of-type::before {
29 | top: 0;
30 | transform-origin: 50% 0%;
31 | }
32 |
33 | > li::after {
34 | bottom: 0;
35 | transform-origin: 50% 100%;
36 | }
37 | }
38 |
39 | h4 {
40 | padding-right: 10px;
41 | padding-left: 10px;
42 | }
43 |
44 | &--region,
45 | &--combine {
46 | height: calc(100vh - 68px);
47 |
48 | .custom-infinite-scroll {
49 | height: calc(100vh - 68px);
50 | }
51 | }
52 | }
53 | }
54 |
55 | .app.demo-infinite-scroll {
56 | width: 100%;
57 | height: auto;
58 | overflow: visible;
59 | }
60 |
--------------------------------------------------------------------------------
/src/components/infinite-scroll/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common';
2 |
3 | .@{prefix}infinite-scroll {
4 | overflow-y: auto;
5 |
6 | &__preload {
7 | display: flex;
8 | align-items: center;
9 | justify-content: center;
10 | height: @infinite-scroll-preload-height;
11 | user-select: none;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/loading/demo/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Loading } from '../../../../src/index';
3 | import DemoHeader from '../../../../docs/mobile/components/mobile-header/index';
4 | import { MobileCard } from '../../../../docs/mobile/components/mobile-card/index';
5 |
6 | class DemoLoading extends React.Component {
7 | render(): JSX.Element {
8 | const onClickLeft = (): void => {
9 | this.props.history.goBack();
10 | };
11 | return (
12 |
13 |
14 |
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 | export default DemoLoading;
41 |
--------------------------------------------------------------------------------
/src/components/loading/icon/circle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/loading/icon/default.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/loading/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common';
2 |
3 | .@{prefix}loading {
4 | display: inline-flex;
5 | align-items: center;
6 | justify-content: center;
7 | font-size: 0;
8 |
9 | &__icon {
10 | display: inline-block;
11 | width: @loading-icon-height;
12 | height: @loading-icon-height;
13 | fill: @primary-color;
14 | }
15 |
16 | &__text {
17 | display: inline-block;
18 | margin-left: 8px;
19 | color: @text-color-4;
20 | font-size: @loading-font-size;
21 | }
22 |
23 | &--vertical {
24 | flex-direction: column;
25 |
26 | .okee-loading__text {
27 | margin: 8px 0 0;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/loading/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createNamespace } from '../../utils/create';
3 | import { value2DomUnit } from '../../utils/dom/unit';
4 |
5 | import { addClass, prefix } from '../../utils/create/createBEM';
6 |
7 | import Icon from '../icon';
8 | import type { IconName } from '../icon';
9 | import { upperCamelize } from '../../utils/format/string';
10 |
11 | const [bem] = createNamespace('loading');
12 |
13 | export type LoadingType = 'default' | 'circle';
14 |
15 | export interface LoadingProps {
16 | type?: LoadingType;
17 | text?: string;
18 | size?: string | number;
19 | vertical?: boolean;
20 | className?: string;
21 | onClick?: React.MouseEventHandler;
22 | children?: any;
23 | style?: React.CSSProperties;
24 | }
25 |
26 | function Loading(props: LoadingProps): JSX.Element {
27 | const { type, text, vertical, size, children, style, className } = props;
28 |
29 | const loadingStyle = style || {};
30 |
31 | if (size) {
32 | loadingStyle.width = value2DomUnit(size);
33 | loadingStyle.height = value2DomUnit(size);
34 | }
35 |
36 | const message = children ? children : text;
37 |
38 | return (
39 |
40 |
45 | {message &&
{message}
}
46 |
47 | );
48 | }
49 |
50 | const defaultProps: LoadingProps = {
51 | type: 'default',
52 | };
53 |
54 | Loading.defaultProps = defaultProps;
55 |
56 | export default Loading;
57 |
--------------------------------------------------------------------------------
/src/components/locale-context/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import local from '../../locale/zh-CN';
3 |
4 | export type Local = 'dialog' | 'picker' | 'datetimePicker' | 'field';
5 |
6 | export type LocalType = Partial>;
7 |
8 | const localContext: LocalType = local;
9 |
10 | const LocaleContext = React.createContext(localContext);
11 |
12 | export default LocaleContext;
13 |
--------------------------------------------------------------------------------
/src/components/modal/confirm.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 |
4 | import { ModalProps } from './index';
5 | import ModalDialog, { ModalDialogProps } from './modal-dialog';
6 |
7 | export default function withConfirm(showCancelButton: boolean): (options: ModalProps) => void {
8 | return function (options: ModalProps): void {
9 | const { title, message } = options;
10 |
11 | if (!title && !message) {
12 | console.log('Must specify either an alert title, or message, or both');
13 | return;
14 | }
15 |
16 | if (typeof window === 'undefined') {
17 | return;
18 | }
19 | const div: HTMLElement = document.createElement('div');
20 | document.body.appendChild(div);
21 |
22 | function destroy(): void {
23 | const unmountResult = ReactDOM.unmountComponentAtNode(div);
24 | if (unmountResult && div.parentNode) {
25 | div.parentNode.removeChild(div);
26 | }
27 | }
28 |
29 | function render(options: ModalDialogProps): void {
30 | ReactDOM.render(, div);
31 | }
32 |
33 | function close(): void {
34 | /* eslint-disable @typescript-eslint/no-use-before-define */
35 | currentOptions = Object.assign({}, currentOptions, {
36 | value: false,
37 | afterClose: destroy,
38 | });
39 |
40 | render(currentOptions);
41 | }
42 |
43 | let currentOptions = {
44 | ...options,
45 | close,
46 | value: true,
47 | showConfirmButton: true,
48 | showCancelButton,
49 | };
50 |
51 | render(currentOptions);
52 | };
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/modal/demo/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Modal, Cell } from '../../../../src/index';
3 | import DemoHeader from '../../../../docs/mobile/components/mobile-header/index';
4 | import { MobileCard } from '../../../../docs/mobile/components/mobile-card/index';
5 |
6 | const { alert, confirm } = Modal;
7 |
8 | class DemoModal extends React.Component {
9 | onClick(): void {
10 | alert({
11 | message: 'OKUI-轻量、可靠的React 组件库',
12 | });
13 | }
14 |
15 | onClick1(): void {
16 | confirm({
17 | message: 'OKUI-轻量、可靠的React 组件库',
18 | onCancel: (): void => {},
19 | });
20 | }
21 |
22 | onClick2(): void {
23 | alert({
24 | message: 'OKUI-轻量、可靠的React 组件库',
25 | title: 'OKUI',
26 | });
27 | }
28 |
29 | onClick3(): void {
30 | confirm({
31 | message: 'OKUI-轻量、可靠的React 组件库',
32 | title: 'OKUI',
33 | });
34 | }
35 |
36 | render(): JSX.Element {
37 | const onClickLeft = (): void => {
38 | this.props.history.goBack();
39 | };
40 | return (
41 |
42 |
43 |
44 |
45 | this.onClick()}> |
46 | this.onClick1()}> |
47 |
48 |
49 |
50 | this.onClick2()}> |
51 | this.onClick3()}> |
52 |
53 |
54 | );
55 | }
56 | }
57 |
58 | export default DemoModal;
59 |
--------------------------------------------------------------------------------
/src/components/modal/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Diloag from '../dialog';
3 | import type { DialogProps } from '../dialog';
4 |
5 | import withConfirm from './confirm';
6 |
7 | export type ModalProps = DialogProps;
8 |
9 | function Modal(props: ModalProps): JSX.Element {
10 | return ;
11 | }
12 |
13 | Modal.alert = withConfirm(false);
14 | Modal.confirm = withConfirm(true);
15 |
16 | export default Modal;
17 |
--------------------------------------------------------------------------------
/src/components/modal/modal-dialog.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Diloag from '../dialog';
3 |
4 | import { ModalProps } from './index';
5 |
6 | export interface ModalDialogProps extends ModalProps {
7 | value: boolean;
8 | close: (...args: any[]) => void;
9 | afterClose?: () => void;
10 | showConfirmButton?: boolean;
11 | showCancelButton?: boolean;
12 | }
13 |
14 | function ModalDialog(props: ModalDialogProps): JSX.Element {
15 | const {
16 | value,
17 | title,
18 | message,
19 | showConfirmButton,
20 | showCancelButton,
21 | close,
22 | onConfirm,
23 | onCancel,
24 | afterClose,
25 | ...extra
26 | } = props;
27 |
28 | return (
29 | {
36 | close();
37 | onConfirm && onConfirm();
38 | }}
39 | onCancel={(): void => {
40 | close();
41 | onCancel && onCancel();
42 | }}
43 | afterClose={afterClose}
44 | {...extra}
45 | />
46 | );
47 | }
48 |
49 | export default ModalDialog;
50 |
--------------------------------------------------------------------------------
/src/components/notice-bar/demo/index.less:
--------------------------------------------------------------------------------
1 | .notice-bar-demo {
2 | margin-bottom: 10px;
3 |
4 | .right-slot {
5 | display: inline-flex;
6 | align-items: center;
7 |
8 | img {
9 | margin-right: 8px;
10 | }
11 | }
12 |
13 | .omui-notice-bar__content {
14 | flex: 1;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/notice-bar/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common';
2 |
3 | .@{prefix}notice-bar {
4 | position: relative;
5 | display: flex;
6 | padding: @notice-bar-padding;
7 | color: @notice-bar-color;
8 | font-size: @notice-bar-font-size;
9 | background: @warning-color-5;
10 |
11 | &__content {
12 | display: -webkit-box;
13 | overflow: hidden;
14 | line-height: @notice-bar-height;
15 | text-overflow: ellipsis;
16 | word-wrap: break-word;
17 | word-break: break-all;
18 | -webkit-box-orient: vertical;
19 |
20 | &--right {
21 | margin-right: 24px;
22 | }
23 | }
24 |
25 | &__left {
26 | flex-shrink: 0;
27 | width: 16px;
28 | height: 16px;
29 | margin-right: 8px;
30 | fill: @warning-color;
31 | }
32 |
33 | &--none {
34 | display: none;
35 | }
36 |
37 | &--primary {
38 | background: @primary-color-5;
39 |
40 | .@{prefix}notice-bar__left {
41 | fill: @primary-color;
42 | }
43 | }
44 |
45 | &--success {
46 | background: @success-color-5;
47 |
48 | .@{prefix}notice-bar__left {
49 | fill: @success-color;
50 | }
51 | }
52 |
53 | &--danger {
54 | background: @danger-color-5;
55 |
56 | .@{prefix}notice-bar__left {
57 | fill: @danger-color;
58 | }
59 | }
60 |
61 | &__right {
62 | position: absolute;
63 | top: 11px;
64 | right: 16px;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/components/overlay/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common';
2 |
3 | .@{prefix}overlay {
4 | position: fixed;
5 | top: 0;
6 | left: 0;
7 | width: 100%;
8 | height: 100%;
9 | background-color: @overlay-background-color;
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/overlay/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { CSSTransition } from 'react-transition-group';
3 |
4 | import createBEM, { addClass, prefix } from '../../utils/create/createBEM';
5 |
6 | export interface OverlayProps {
7 | show?: boolean;
8 | zIndex?: number | string;
9 | className?: any;
10 | customStyle?: any;
11 | onClick?: React.MouseEventHandler;
12 | }
13 |
14 | const bem = createBEM('overlay');
15 |
16 | function Overlay(props: OverlayProps): any {
17 | const { show, zIndex, className, customStyle, onClick } = props;
18 |
19 | const style: { [key: string]: any } = {
20 | zIndex: zIndex,
21 | ...customStyle,
22 | };
23 |
24 | return (
25 |
26 |
27 |
28 | );
29 | }
30 |
31 | export default Overlay;
32 |
--------------------------------------------------------------------------------
/src/components/picker-column/demo/index.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oceanengine/okeedesign-mobile-react/4b873fa5f335aedc192daa744dd78b452f6d26ed/src/components/picker-column/demo/index.tsx
--------------------------------------------------------------------------------
/src/components/picker-column/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common.less';
2 |
3 | .@{prefix}picker-column {
4 | flex: 1;
5 | min-width: 0;
6 | overscroll-behavior: none;
7 |
8 | &__wrapper {
9 | height: 100%;
10 | }
11 |
12 | &__item {
13 | display: flex;
14 | align-items: center;
15 | justify-content: center;
16 | height: @picker-column-option-height;
17 | padding: @picker-column-item-padding;
18 | color: @picker-column-color;
19 | font-weight: @picker-column-font-weight;
20 | font-size: @picker-column-font-size;
21 | text-align: center;
22 |
23 | &--active {
24 | color: @picker-column-active-color;
25 | font-weight: @picker-column-active-font-weight;
26 | font-size: @picker-column-active-font-size;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/components/picker/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common.less';
2 |
3 | .@{prefix}picker {
4 | background-color: white;
5 |
6 | &__content {
7 | position: relative;
8 | display: flex;
9 | overflow: hidden;
10 | }
11 |
12 | &__mask {
13 | position: absolute;
14 | z-index: 2;
15 | width: 100%;
16 | height: 100%;
17 | background-image: linear-gradient(to bottom, @white 0%, rgba(255, 255, 255, 0.2) 100%),
18 | linear-gradient(to top, @white 0%, rgba(255, 255, 255, 0.2) 100%);
19 | background-repeat: no-repeat;
20 | background-position: top, bottom;
21 | background-size: 100% calc((100% - @picker-indicator-height) / 2);
22 | transform: translate3d(0, 0, 0);
23 | pointer-events: none;
24 | }
25 |
26 | &__indicator {
27 | position: absolute;
28 | top: 50%;
29 | left: -50%;
30 | z-index: 3;
31 | width: 200%;
32 | height: (@picker-indicator-height * 2);
33 | border-top: 1px solid @picker-indicator-color;
34 | border-bottom: 1px solid @picker-indicator-color;
35 | transform: translate3d(0, -50%, 0) scale(0.5);
36 | pointer-events: none;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/popup-drill-down-item/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { HTMLAttributes, useContext } from 'react';
2 | import createBEM, { addClass } from '../../utils/create/createBEM';
3 | import { RenderFunction } from '../../utils/types';
4 | import { PopupDrillDownContext, PopupDrillDownState } from '../popup-drill-down/context';
5 |
6 | export interface PopupDrillDownItemProps extends Omit, 'title'> {
7 | title?: string | JSX.Element | RenderFunction;
8 |
9 | left?: string | JSX.Element | RenderFunction;
10 | right?: string | JSX.Element | RenderFunction<(() => void) | void>;
11 |
12 | active?: boolean;
13 | target?: boolean;
14 | children?: string | JSX.Element | RenderFunction;
15 | }
16 |
17 | const bem = createBEM('popup-drill-down-item');
18 |
19 | function PopupDrillDownItem(props: PopupDrillDownItemProps): JSX.Element {
20 | const { children, className, active = false, target = false } = props;
21 |
22 | const { ...methods } = useContext(PopupDrillDownContext);
23 |
24 | const childProps = {
25 | ...methods,
26 | } as PopupDrillDownState;
27 |
28 | return (
29 |
30 | {active &&
}
31 | {typeof children === 'function' ? children(childProps) : children}
32 |
33 | );
34 | }
35 |
36 | export default PopupDrillDownItem;
37 |
--------------------------------------------------------------------------------
/src/components/popup-drill-down/context.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export type PopupDrillDownState = {
4 | forward: () => void;
5 | back: () => void;
6 | closePopup: () => void;
7 | };
8 |
9 | export const PopupDrillDownContext = React.createContext({
10 | forward() {},
11 | back() {},
12 | closePopup() {},
13 | });
14 |
--------------------------------------------------------------------------------
/src/components/popup-drill-down/demo/index.less:
--------------------------------------------------------------------------------
1 | .shopping-drill-down {
2 | height: 80%;
3 |
4 | .shopping-drill-down-item {
5 | display: flex;
6 | flex-direction: column;
7 | align-items: center;
8 | height: 100%;
9 | overflow: auto;
10 |
11 | &-placeholder {
12 | flex: none;
13 | width: 100%;
14 | height: 100px;
15 | background-color: pink;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/popup/demo/index.less:
--------------------------------------------------------------------------------
1 | .demo-popup-basic {
2 | padding: 38px;
3 | font-size: 18px;
4 | border-radius: 4px;
5 |
6 | .popup-img {
7 | width: 124px;
8 | }
9 | }
10 |
11 | .demo-popup-vh {
12 | height: 100vh;
13 | line-height: 100vh;
14 | }
15 |
16 | .demo.popup {
17 | position: relative;
18 | margin: 0;
19 | padding: 0 16px;
20 |
21 | .popup-box {
22 | .okee-button {
23 | margin-right: 10px;
24 | }
25 | }
26 | }
27 |
28 | .demo-popup-top,
29 | .demo-popup-bottom {
30 | height: 40vh;
31 | font-size: 0;
32 | line-height: 40vh;
33 | text-align: center;
34 |
35 | img {
36 | vertical-align: middle;
37 | }
38 | }
39 |
40 | .demo-popup-left,
41 | .demo-popup-right {
42 | display: flex;
43 | align-items: center;
44 | justify-content: center;
45 | width: 60vw;
46 | height: inherit;
47 | }
48 |
--------------------------------------------------------------------------------
/src/components/progress/demo/index.less:
--------------------------------------------------------------------------------
1 | .demo.demo-progress {
2 | .demo-cell {
3 | > div + div {
4 | margin-top: 4px;
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/pull-refresh/demo/index.less:
--------------------------------------------------------------------------------
1 | .demo-pull-refresh {
2 | .content {
3 | display: flex;
4 | flex-direction: column;
5 | align-items: center;
6 | height: 30vh;
7 | padding: 32px 0 16px 0;
8 | }
9 |
10 | .omui-pull-refresh {
11 | background: rgb(255, 255, 255);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/radio/demo/index.less:
--------------------------------------------------------------------------------
1 | .demo.radio {
2 | .omui-radio {
3 | justify-content: flex-end;
4 | margin: 8px 0;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/radio/group.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Component Radio Group
3 | */
4 | import React, { FC, Context, CSSProperties, MouseEventHandler } from 'react';
5 |
6 | import createBEM, { addClass } from '../../utils/create/createBEM';
7 |
8 | import { RadioType, RadioSize, RadioLabelPosition, RadioValue } from './types';
9 |
10 | export interface RadioGroupState {
11 | type?: RadioType;
12 | size?: RadioSize;
13 | value?: RadioValue;
14 | labelPosition?: RadioLabelPosition;
15 | onChange?: (value: RadioValue) => void;
16 | }
17 |
18 | export const RadioGroupContext: Context = React.createContext({});
19 |
20 | const bem = createBEM('radio-group');
21 |
22 | export interface RadioGroupProps {
23 | className?: string;
24 | style?: CSSProperties;
25 | children?: React.ReactNode;
26 |
27 | /**
28 | * The type of shape.
29 | */
30 | type?: RadioType;
31 |
32 | /**
33 | * Size of radio
34 | */
35 | size?: RadioSize;
36 |
37 | /**
38 | * The pisition of label
39 | */
40 | labelPosition?: RadioLabelPosition;
41 |
42 | /**
43 | * The value of radio.
44 | */
45 | value: RadioValue;
46 |
47 | /**
48 | * When radio value changed.
49 | */
50 | onChange?: (value: RadioValue) => void;
51 |
52 | /**
53 | * Optional callback when Radio Group is clicked.
54 | */
55 | onClick?: MouseEventHandler;
56 | }
57 |
58 | const RadioGroup: FC = (props: RadioGroupProps) => {
59 | const { type, size, labelPosition, value, onChange } = props;
60 |
61 | return (
62 |
71 |
72 | {props.children}
73 |
74 |
75 | );
76 | };
77 |
78 | RadioGroup.displayName = 'RadioGroup';
79 |
80 | export default React.memo(RadioGroup);
81 |
--------------------------------------------------------------------------------
/src/components/radio/icon.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Component Radio Icon
3 | */
4 | import React, { FC } from 'react';
5 |
6 | import Icon from '../icon';
7 |
8 | import { RadioType } from './types';
9 |
10 | export interface RadioIconProps {
11 | className?: string;
12 |
13 | /**
14 | * type of shape.
15 | */
16 | type: RadioType;
17 |
18 | /**
19 | * value of radio.
20 | */
21 | checked: boolean;
22 | }
23 |
24 | const RadioIcon: FC = (props: RadioIconProps) => {
25 | const { className, type, checked } = props;
26 |
27 | if (type === 'dot') {
28 | return checked ? (
29 |
30 | ) : (
31 |
32 | );
33 | }
34 |
35 | if (type === 'hook') {
36 | return checked ? : ;
37 | }
38 |
39 | if (type === 'circle') {
40 | return checked ? (
41 |
42 | ) : (
43 |
44 | );
45 | }
46 |
47 | return checked ? : ;
48 | };
49 |
50 | RadioIcon.displayName = 'RadioIcon';
51 |
52 | export default RadioIcon;
53 |
--------------------------------------------------------------------------------
/src/components/radio/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common';
2 |
3 | .@{prefix}radio {
4 | display: flex;
5 | align-items: center;
6 | overflow: hidden;
7 | user-select: none;
8 |
9 | &__input {
10 | display: none;
11 | }
12 |
13 | &__label {
14 | flex: 1;
15 | margin-left: @radio-normal-label-margin;
16 | color: @radio-checked-label-color;
17 | font-size: @radio-normal-font-size;
18 | line-height: @radio-normal-size;
19 | }
20 |
21 | &__icon {
22 | display: block;
23 | flex-shrink: 0;
24 | width: @radio-normal-size;
25 | height: @radio-normal-size;
26 | fill: @radio-checked-icon-color;
27 | }
28 |
29 | &--checked &__icon {
30 | fill: @radio-checked-icon-active-color;
31 | }
32 |
33 | &--checked &__label {
34 | color: @radio-checked-label-active-color;
35 | }
36 |
37 | &--disabled &__icon {
38 | fill: @radio-disabled-icon-color;
39 | }
40 |
41 | &--disabled &__label {
42 | color: @radio-disabled-label-color;
43 | }
44 |
45 | &--disabled&--checked &__icon {
46 | fill: @radio-disabled-icon-active-color;
47 | }
48 |
49 | &--small &__icon {
50 | width: @radio-small-size;
51 | height: @radio-small-size;
52 | }
53 |
54 | &--small &__label {
55 | margin-left: @radio-small-label-margin;
56 | font-size: @radio-small-font-size;
57 | line-height: @radio-small-size;
58 | }
59 |
60 | &--large &__icon {
61 | width: @radio-large-size;
62 | height: @radio-large-size;
63 | }
64 |
65 | &--large &__label {
66 | margin-left: @radio-large-label-margin;
67 | font-size: @radio-large-font-size;
68 | line-height: @radio-large-size;
69 | }
70 |
71 | &--left &__label {
72 | margin-right: @radio-normal-label-margin;
73 | margin-left: 0;
74 | text-align: right;
75 | }
76 |
77 | &--small&--left &__label {
78 | margin-right: @radio-small-label-margin;
79 | margin-left: 0;
80 | }
81 |
82 | &--large&--left &__label {
83 | margin-right: @radio-large-label-margin;
84 | margin-left: 0;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/components/radio/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Component Radio Entery
3 | */
4 | import { NamedExoticComponent, PropsWithChildren } from 'react';
5 |
6 | export * from './types';
7 |
8 | import InternalRadio from './radio';
9 | import type { RadioProps } from './radio';
10 | import RadioGroup from './group';
11 |
12 | export type { RadioGroupProps } from './group';
13 |
14 | export type { RadioProps };
15 |
16 | export interface Radio extends NamedExoticComponent> {
17 | Group: typeof RadioGroup;
18 | }
19 |
20 | const Radio = InternalRadio as Radio;
21 | Radio.Group = RadioGroup;
22 |
23 | export { RadioGroup };
24 |
25 | export default Radio;
26 |
--------------------------------------------------------------------------------
/src/components/radio/types.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * interface
3 | */
4 | export type RadioType = 'default' | 'primary' | 'dot' | 'hook' | 'circle';
5 |
6 | export type RadioSize = 'small' | 'normal' | 'large';
7 |
8 | export type RadioLabelPosition = 'left' | 'right';
9 |
10 | export type RadioValue = number | string;
11 |
--------------------------------------------------------------------------------
/src/components/row/RowContext.tsx:
--------------------------------------------------------------------------------
1 | import React, { Context } from 'react';
2 |
3 | export interface RowContextState {
4 | gutter?: number | string;
5 | }
6 |
7 | const RowContext: Context = React.createContext({});
8 |
9 | export default RowContext;
10 |
--------------------------------------------------------------------------------
/src/components/row/demo/index.less:
--------------------------------------------------------------------------------
1 | .demo.row {
2 | .omui-row {
3 | .omui-col {
4 | height: 32px;
5 | margin-bottom: 8px;
6 | color: #fff;
7 | font-size: 16px;
8 | line-height: 2;
9 | text-align: center;
10 | background-clip: content-box;
11 |
12 | &:nth-child(odd) {
13 | background-color: #58a0ff;
14 | }
15 |
16 | &:nth-child(even) {
17 | background-color: #a1caff;
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/row/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common';
2 |
3 | .@{prefix}row {
4 | &::after {
5 | display: table;
6 | clear: both;
7 | content: "";
8 | }
9 |
10 | &--flex {
11 | display: flex;
12 |
13 | &::after {
14 | display: none;
15 | }
16 | }
17 |
18 | &--justify-center {
19 | justify-content: center;
20 | }
21 |
22 | &--justify-end {
23 | justify-content: flex-end;
24 | }
25 |
26 | &--justify-space-between {
27 | justify-content: space-between;
28 | }
29 |
30 | &--justify-space-around {
31 | justify-content: space-around;
32 | }
33 |
34 | &--align-center {
35 | align-items: center;
36 | }
37 |
38 | &--align-bottom {
39 | align-items: flex-end;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/row/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Component Row
3 | */
4 | import React, { FC, PropsWithChildren } from 'react';
5 |
6 | import RowContext from './RowContext';
7 |
8 | import createBEM from '../../utils/create/createBEM';
9 | import { value2DomUnit } from '../../utils/dom/unit';
10 |
11 | const bem = createBEM('row');
12 |
13 | export interface RowProps {
14 | type?: 'flex';
15 | align?: string;
16 | justify?: string;
17 | gutter?: number | string;
18 | }
19 |
20 | export const Row: FC = (props: PropsWithChildren) => {
21 | const { type, align, justify, gutter } = props;
22 |
23 | const flex = type === 'flex';
24 |
25 | const rowStyle: React.CSSProperties = {};
26 |
27 | if (gutter) {
28 | const gap = value2DomUnit(gutter, 0.5);
29 |
30 | rowStyle.marginLeft = `-${gap}`;
31 | rowStyle.marginRight = `-${gap}`;
32 | }
33 |
34 | return (
35 |
36 |
44 | {props.children}
45 |
46 |
47 | );
48 | };
49 |
50 | Row.displayName = 'Row';
51 |
52 | export default Row;
53 |
--------------------------------------------------------------------------------
/src/components/search/demo/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useHistory } from 'react-router-dom';
3 | import { Search } from '../../../../src/index';
4 | import DemoHeader from '../../../../docs/mobile/components/mobile-header/index';
5 | import { MobileCard } from '../../../../docs/mobile/components/mobile-card/index';
6 |
7 | function DemoSearch(): JSX.Element {
8 | const history = useHistory();
9 | const onClickLeft = (): void => {
10 | history.goBack();
11 | };
12 | const [baseValue, setBaseValue] = useState('');
13 | const [cancelValue, setCancelValue] = useState('');
14 | const [styleValue, setStyleValue] = useState('');
15 | // const onSearch = (value: string) => {
16 | // alert('search ' + value);
17 | // }
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | );
41 | }
42 |
43 | export default DemoSearch;
44 |
--------------------------------------------------------------------------------
/src/components/search/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common';
2 |
3 | .@{prefix}search {
4 | display: flex;
5 | align-items: center;
6 | box-sizing: border-box;
7 | width: 100%;
8 | height: @search-height;
9 | padding: 0 @search-padding;
10 | background-color: @white;
11 |
12 | &__content {
13 | position: relative;
14 | display: flex;
15 | flex: 1;
16 | align-items: center;
17 | height: @search-content-height;
18 | padding: 0 @search-content-padding;
19 | color: @search-color;
20 | font-size: @search-font-size;
21 | background-color: @search-content-background-color;
22 |
23 | &--round {
24 | border-radius: @search-content-border-radius;
25 | }
26 |
27 | &--square {
28 | border-radius: @search-content-square-border-radius;
29 | }
30 | }
31 |
32 | &__btn {
33 | flex: none;
34 | margin-left: @search-content-padding;
35 | }
36 |
37 | &__icon {
38 | width: @search-icon-height;
39 | height: @search-icon-height;
40 | margin-right: @search-icon-margin;
41 | }
42 |
43 | // override search style
44 | // https://stackoverflow.com/questions/9421551/how-do-i-remove-all-default-webkit-search-field-styling
45 | &__input {
46 | &::-webkit-search-decoration,
47 | &::-webkit-search-cancel-button,
48 | &::-webkit-search-results-button,
49 | &::-webkit-search-results-decoration {
50 | appearance: none;
51 | }
52 |
53 | box-sizing: border-box;
54 | width: 100%;
55 | padding-right: @search-icon-height;
56 | line-height: @search-line-height;
57 | background-color: transparent;
58 | border: none;
59 | outline: none;
60 | caret-color: @search-caret-color;
61 |
62 | ::placeholder {
63 | color: @search-placeholder-color;
64 | }
65 | }
66 |
67 | &__clear {
68 | position: absolute;
69 | right: @search-content-padding;
70 | width: @search-font-size;
71 | height: @search-font-size;
72 | fill: @search-icon-fill;
73 | }
74 |
75 | &--large {
76 | .@{prefix}search {
77 | &__content {
78 | height: @search-content-large-height;
79 | }
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/components/sticky/demo/index.less:
--------------------------------------------------------------------------------
1 |
2 | .demo-button {
3 | .omui-button {
4 | margin-right: 10px;
5 | margin-bottom: 10px;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/sticky/demo/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useMemo } from 'react';
2 | import { Sticky, Button, NoticeBar } from '../../../../src/index';
3 | import { useHistory } from 'react-router-dom';
4 | import DemoHeader from '../../../../docs/mobile/components/mobile-header/index';
5 | import { MobileCard } from '../../../../docs/mobile/components/mobile-card/index';
6 | import './index.less';
7 |
8 | function DemoSticky(): JSX.Element {
9 | const history = useHistory();
10 | const onClickLeft = (): void => {
11 | history.goBack();
12 | };
13 |
14 | const StickyContent = useMemo(() => {
15 | // return ;
16 | return Sticky Header;
17 | }, []);
18 |
19 | const target = useCallback(() => {
20 | return document.querySelector('.app');
21 | }, []);
22 |
23 | return (
24 |
25 | {/*
*/}
26 |
27 |
28 |
29 |
30 |
31 |
32 | {StickyContent}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | );
41 | }
42 |
43 | export default DemoSticky;
44 |
--------------------------------------------------------------------------------
/src/components/sticky/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common';
2 | @import '../../style/compat.less';
3 |
--------------------------------------------------------------------------------
/src/components/swipe-cell/demo/index.less:
--------------------------------------------------------------------------------
1 | .demo-swipe-cell {
2 | // h4 {
3 | // margin: 0;
4 | // padding: 16px 0;
5 | // }
6 |
7 | // .demo-cell {
8 | // margin-bottom: 16px;
9 | // }
10 |
11 | // .commands {
12 | // display: flex;
13 | // flex-flow: row nowrap;
14 | // justify-content: space-between;
15 | // padding: 8px 0;
16 | // }
17 | .icon {
18 | width: 24px;
19 | height: 24px;
20 | fill: rgb(102, 102, 102);
21 | }
22 |
23 | .left-two {
24 | display: flex;
25 | align-items: center;
26 | width: 80px;
27 | height: 100%;
28 | }
29 |
30 | .icon-box {
31 | display: flex;
32 | flex: 1;
33 | align-items: center;
34 | justify-content: center;
35 | width: 40px;
36 | height: 100%;
37 | text-align: center;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/swipe-cell/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common';
2 |
3 | .@{prefix}swipe-cell {
4 | position: relative;
5 | overflow: hidden;
6 |
7 | &__wrapper {
8 | position: relative;
9 | transform: translate3d(0, 0, 0);
10 | will-change: transform;
11 | }
12 |
13 | &__left,
14 | &__right {
15 | position: absolute;
16 | top: 0;
17 | bottom: 0;
18 | height: 100%;
19 | }
20 |
21 | &__left {
22 | left: 0;
23 | transform: translate3d(-100%, 0, 0);
24 | }
25 |
26 | &__right {
27 | right: 0;
28 | transform: translate3d(100%, 0, 0);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/swipe/demo/index.less:
--------------------------------------------------------------------------------
1 |
2 | .demo-swipe {
3 | .swipe-item {
4 | height: 80px;
5 | line-height: 80px;
6 | text-align: center;
7 | }
8 |
9 | .omui-swipe__track {
10 | .swipe-item {
11 | color: #fff;
12 | }
13 |
14 | &:nth-child(odd) {
15 | .swipe-item {
16 | background: #58a0ff;
17 | }
18 | }
19 |
20 | &:nth-child(even) {
21 | .swipe-item {
22 | background: #a1caff;
23 | }
24 | }
25 | }
26 |
27 | .custom-indicator {
28 | position: absolute;
29 | right: 0;
30 | bottom: 0;
31 | padding: 4px 6px;
32 | background: rgba(255, 255, 255, 0.6);
33 | }
34 |
35 | .button-wrap {
36 | display: flex;
37 | flex-flow: row nowrap;
38 | justify-content: center;
39 | margin-top: 16px;
40 |
41 | > *:not(:last-child) {
42 | margin-right: 4px;
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/components/swipe/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common';
2 |
3 | .@{prefix}swipe {
4 | position: relative;
5 | overflow: hidden;
6 |
7 | &__track {
8 | position: relative;
9 | display: flex;
10 | flex-flow: row nowrap;
11 | align-content: stretch;
12 | align-items: stretch;
13 | justify-content: flex-start;
14 | width: 100%;
15 | height: 100%;
16 | transform: translate3d(0, 0, 0);
17 | will-change: transform;
18 | }
19 |
20 | &__indicators {
21 | position: absolute;
22 | right: 0;
23 | bottom: @swipe-indicators-bottom;
24 | left: 0;
25 | display: flex;
26 | flex-flow: row nowrap;
27 | align-content: center;
28 | align-items: center;
29 | justify-content: center;
30 | }
31 |
32 | &__indicator {
33 | position: relative;
34 | flex: none;
35 | width: @swipe-indicator-width;
36 | height: @swipe-indicator-height;
37 | background-color: @swipe-indicator-color;
38 | border-radius: @swipe-indicator-border-radius;
39 | opacity: @swipe-indicator-opacity;
40 |
41 | &:not(:last-child) {
42 | margin-right: @swipe-indicator-gap;
43 | }
44 |
45 | &--active {
46 | background-color: @swipe-indicator-color-active;
47 | opacity: @swipe-indicator-opacity-active;
48 | }
49 | }
50 | }
51 |
52 | .@{prefix}swipe-item {
53 | position: relative;
54 | flex: none;
55 | width: 100%;
56 | height: 100%;
57 | overflow: hidden;
58 | }
59 |
--------------------------------------------------------------------------------
/src/components/switch/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, useState } from 'react';
2 | import { createNamespace } from '../../utils/create';
3 | import Loading from '../loading';
4 |
5 | import { addClass } from '../../utils/create/createBEM';
6 |
7 | const [bem] = createNamespace('switch');
8 |
9 | export type SwitchSize = 'normal' | 'large';
10 |
11 | export type SwitchProps = {
12 | size?: SwitchSize;
13 | value: boolean;
14 | disabled?: boolean;
15 | loading?: boolean;
16 | className?: string;
17 | name?: string;
18 | onClick?: (value: boolean) => void;
19 | onChange?: (value: boolean) => void;
20 | };
21 |
22 | export const Switch: FC = (props: SwitchProps) => {
23 | const { size, value, disabled, loading, className, name } = props;
24 |
25 | const [switchValue, setSwitchValue] = useState(value);
26 |
27 | function onClick(): void {
28 | if (!disabled && !loading) {
29 | setSwitchValue(!switchValue);
30 | if (props.onClick) props.onClick(!switchValue);
31 | }
32 | }
33 |
34 | function onChange(): void {
35 | if (!disabled && !loading) {
36 | if (props.onChange) props.onChange(!switchValue);
37 | }
38 | }
39 |
40 | const switchClassName =
41 | bem([
42 | {
43 | disabled,
44 | checked: value,
45 | },
46 | ]) +
47 | ' ' +
48 | size;
49 |
50 | return (
51 |
52 |
61 |
62 | {loading && (
63 |
64 | )}
65 |
66 |
67 | );
68 | };
69 |
70 | const defaultProps: SwitchProps = {
71 | size: 'normal',
72 | value: false,
73 | name: '',
74 | };
75 |
76 | Switch.defaultProps = defaultProps;
77 |
78 | export default Switch;
79 |
--------------------------------------------------------------------------------
/src/components/tab/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common';
2 |
3 | .@{prefix}tab {
4 | &__pane {
5 | flex-shrink: 0;
6 | width: 100%;
7 |
8 | &--inactive {
9 | height: 0;
10 | padding: 0;
11 | pointer-events: none;
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/tab/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, useMemo, useRef } from 'react';
2 | import createBEM from '../../utils/create/createBEM';
3 |
4 | const bem = createBEM('tab');
5 |
6 | export interface TabProps {
7 | title: string | React.ReactElement;
8 | name?: number | string;
9 | disabled?: boolean;
10 | active?: boolean;
11 | children?: any;
12 | lazyRender?: boolean;
13 | animated?: boolean;
14 | swipeable?: boolean;
15 | }
16 |
17 | export const Tab: FC = (props: TabProps) => {
18 | const {
19 | name,
20 | children,
21 | active = true,
22 | lazyRender = true,
23 | animated = false,
24 | swipeable = false,
25 | } = props;
26 |
27 | const inited = useRef(false);
28 |
29 | if (!inited.current && active) {
30 | inited.current = true;
31 | }
32 |
33 | // inited effected by active
34 | const isRenderChildren = useMemo(() => {
35 | return animated || swipeable || inited.current || !lazyRender;
36 | // eslint-disable-next-line react-hooks/exhaustive-deps
37 | }, [active, lazyRender, animated, swipeable]);
38 |
39 | return (
40 |
41 | {isRenderChildren && children}
42 |
43 | );
44 | };
45 |
46 | Tab.displayName = 'Tab';
47 |
48 | export default Tab;
49 |
--------------------------------------------------------------------------------
/src/components/table/demo/index.less:
--------------------------------------------------------------------------------
1 | body.demo-table {
2 | overflow: hidden;
3 | }
4 |
5 | .custom-table-cell,
6 | .custom-table-th-cell {
7 | display: inline-flex;
8 | align-items: center;
9 | vertical-align: bottom;
10 |
11 | &__icon {
12 | width: 20px;
13 | height: 20px;
14 | margin-right: 4px;
15 | fill: #0278ff;
16 | }
17 | }
18 |
19 | .custom-table-th-cell__icon {
20 | width: 16px;
21 | height: 16px;
22 | fill: #0278ff;
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/table/indicator.tsx:
--------------------------------------------------------------------------------
1 | import { HTMLAttributes, useMemo } from 'react';
2 | import * as React from 'react';
3 | import createBEM from '../../utils/create/createBEM';
4 |
5 | export type TableIndicatorShape = 'round';
6 |
7 | export interface TableIndicatorProps extends HTMLAttributes {
8 | value: number[];
9 | shape?: TableIndicatorShape;
10 | length: number;
11 | }
12 |
13 | const bem = createBEM('table-indicator');
14 |
15 | function TableIndicator(props: TableIndicatorProps): JSX.Element {
16 | const { length, value = [] } = props;
17 |
18 | const Items = useMemo(() => {
19 | return Array(length)
20 | .fill(0)
21 | .map((theValue, index) => {
22 | return ;
23 | });
24 | }, [value, length]);
25 |
26 | return {Items}
;
27 | }
28 |
29 | export default TableIndicator;
30 |
--------------------------------------------------------------------------------
/src/components/table/sort-icon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import createBEM, { addClass } from '../../utils/create/createBEM';
3 | import { TableSortType } from './types';
4 |
5 | export interface SortIconProps extends React.HTMLAttributes {
6 | sortType: TableSortType;
7 | }
8 |
9 | const bem = createBEM('sort-icon');
10 |
11 | function SortIcon(props: SortIconProps): JSX.Element {
12 | const { className = '', sortType } = props;
13 |
14 | return (
15 |
34 | );
35 | }
36 |
37 | export default SortIcon;
38 |
--------------------------------------------------------------------------------
/src/components/table/types.ts:
--------------------------------------------------------------------------------
1 | import type { DefaultTextAlignType, RenderContent } from '../../utils/types';
2 |
3 | export type TableFixedType = 'left' | 'right';
4 | export type TableCellClassType = string | Array;
5 | export type TableColumnWidthType = number | string;
6 | export type TableSortType = 'asc' | 'desc' | undefined;
7 |
8 | export type TableDataPropType = {
9 | id?: string;
10 | children?: string;
11 | };
12 |
13 | export type TableRenderTdCellType = (rowData: unknown, column: TableColumnProps) => RenderContent;
14 |
15 | export type TableRenderThCellType = (column: TableColumnProps) => RenderContent;
16 |
17 | export type BaseTableColumnProps = {
18 | dataProp: string;
19 | minWidth?: TableColumnWidthType;
20 | maxWidth?: TableColumnWidthType;
21 | width?: TableColumnWidthType;
22 | cellClass?: TableCellClassType;
23 | thCellClass?: TableCellClassType;
24 | tdCellClass?: TableCellClassType;
25 | fixed?: TableFixedType;
26 | textAlign?: DefaultTextAlignType;
27 | resizable?: boolean;
28 | };
29 |
30 | export interface TableColumnProps extends BaseTableColumnProps {
31 | renderCell?: TableRenderTdCellType;
32 | renderThCell?: TableRenderThCellType;
33 | sortable?: boolean;
34 | filterable?: boolean;
35 | colspan?: number;
36 | title?: string;
37 | children?: Array;
38 | }
39 |
40 | export type TableDataProps = Record;
41 |
42 | export interface TableExpandOptionsType extends BaseTableColumnProps {
43 | contentClass?: TableCellClassType;
44 | type?: 'expand';
45 | trigger?: 'icon' | 'row';
46 | showIconColumn?: boolean;
47 | renderIcon?: (rowData: Record) => RenderContent;
48 | // 展开的区域内容的自定义渲染函数
49 | renderContent: (rowData: Record) => RenderContent;
50 | }
51 |
52 | export type TableCeilingOptionsType = {
53 | scrollBoundary: HTMLElement | Document;
54 | top?: number | string;
55 | };
56 |
--------------------------------------------------------------------------------
/src/components/tabs/demo/index.less:
--------------------------------------------------------------------------------
1 | .demo-tabs {
2 | .demo-cell {
3 | margin: 0 0 10px 0;
4 |
5 | h4 {
6 | margin: 20px;
7 | }
8 |
9 | .content {
10 | padding: 16px;
11 | font-size: 16px;
12 | text-align: center;
13 | background: #fff;
14 | }
15 |
16 | .omui-tabs--card {
17 | margin: 0 10px;
18 |
19 | .content {
20 | background: transparent;
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/tabs/utils.ts:
--------------------------------------------------------------------------------
1 | import { raf } from '../../utils/dom/raf';
2 |
3 | export function scrollLeftTo(el: HTMLElement, to: number, duration: number): void {
4 | let count = 0;
5 | const from = el.scrollLeft;
6 | const frames = duration === 0 ? 1 : Math.round((duration * 1000) / 16);
7 |
8 | function animate(): void {
9 | el.scrollLeft += (to - from) / frames;
10 |
11 | el.scrollLeft = to - ((frames - count - 1) * (to - from)) / frames;
12 |
13 | if (++count < frames) {
14 | raf(animate);
15 | }
16 | }
17 |
18 | animate();
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/tag/demo/index.less:
--------------------------------------------------------------------------------
1 | @import '../../../style/common';
2 |
3 | .demo-tag {
4 | .omui-cell__title,
5 | .omui-cell__content {
6 | overflow: visible;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/toast/icon/error.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/toast/icon/loading.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/toast/icon/success.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/toast/icon/warning.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/toast/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common';
2 |
3 | .@{prefix}toast {
4 | position: fixed;
5 | top: 50%;
6 | display: flex;
7 | flex-direction: column;
8 | align-items: center;
9 | justify-content: center;
10 | box-sizing: content-box;
11 | padding: @toast-padding;
12 | color: @white;
13 | font-size: @toast-font-size;
14 | line-height: @toast-line-height;
15 | white-space: pre-wrap;
16 | text-align: center;
17 | word-break: break-all;
18 | background-color: @toast-background-color;
19 | transform: translate3d(calc(50vw - 50%), -50%, 0);
20 |
21 | .okui-border-radius(3);
22 |
23 | // should not add pointer-events: none directly to body tag
24 | // that will cause unexpected tap-highlight-color in mobile safari
25 | &--unclickable {
26 | * {
27 | pointer-events: none;
28 | }
29 | }
30 |
31 | &--text {
32 | width: fit-content;
33 | min-width: @toast-text-min-width;
34 | min-height: unset;
35 | padding: @toast-text-padding;
36 |
37 | .@{prefix}toast__text {
38 | margin-top: 0;
39 | }
40 | }
41 |
42 | &__text {
43 | min-width: @toast-text-min-width;
44 | max-width: @toast-text-max-width;
45 | }
46 |
47 | &__icon {
48 | margin-bottom: 12px;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/components/toolbar/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/common.less';
2 |
3 | .@{prefix}toolbar {
4 | &__toolbar {
5 | position: relative;
6 | display: flex;
7 | align-items: center;
8 | justify-content: center;
9 | height: @toolbar-height;
10 | .@{prefix}button--large {
11 | width: auto;
12 | padding-right: @toolbar-padding-horizontal;
13 | padding-left: @toolbar-padding-horizontal;
14 | }
15 | }
16 |
17 | &__title {
18 | color: @toolbar-title-color;
19 | font-weight: @toolbar-title-font-weight;
20 | font-size: @toolbar-title-font-size;
21 | }
22 |
23 | &__button--cancel {
24 | position: absolute;
25 | left: 0;
26 | height: @toolbar-height;
27 | color: @toolbar-cancel-btn-color;
28 | font-weight: @toolbar-cancel-btn-font-weight;
29 | font-size: @toolbar-cancel-btn-font-size;
30 | line-height: @toolbar-height;
31 |
32 | &:active {
33 | color: @toolbar-cancel-btn-active-color;
34 | }
35 | }
36 |
37 | &__button--confirm {
38 | position: absolute;
39 | right: 0;
40 | height: @toolbar-height;
41 | color: @toolbar-confirm-btn-color;
42 | font-weight: @toolbar-confirm-btn-font-weight;
43 | font-size: @toolbar-confirm-btn-font-size;
44 | line-height: @toolbar-height;
45 |
46 | &:active {
47 | color: @toolbar-confirm-btn-active-color;
48 | }
49 | }
50 |
51 | &__header--showBottomLine {
52 | .@{prefix}toolbar__toolbar {
53 | &::after {
54 | .hairline-bottom(@toolbar-border-color);
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/hooks/callback/const-callback.ts:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 |
3 | /**
4 | * Hook to ensure a callback function always has the same identify.
5 | * Unlike `React.useCallback`, this is guaranteed to always return the same value.
6 | *
7 | * @param callback The callback function. Only the first value passed is respected.
8 | * @returns The callback function. The identify of the callback will always be the same.
9 | */
10 | // eslint-disable-next-line @typescript-eslint/no-explicit-any,space-before-function-paren
11 | export function useConstCallback any>(callback: T): T {
12 | const ref = useRef();
13 | if (!ref.current) {
14 | ref.current = callback;
15 | }
16 | return ref.current;
17 | }
18 |
--------------------------------------------------------------------------------
/src/hooks/callback/index.ts:
--------------------------------------------------------------------------------
1 | export * from './const-callback';
2 | export * from './ref-callback';
3 |
--------------------------------------------------------------------------------
/src/hooks/callback/ref-callback.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import { useRef } from 'react';
3 | import { useConstCallback } from './const-callback';
4 |
5 | /**
6 | * Hook to wrap callback function, ensure the wrapper always has the same identify,
7 | * meanwhile, always call the latest callback passed when call the wrapper.
8 | *
9 | * @param callback The callback function. Always use the latest value passed.
10 | * @returns The wrapper function. The identify of the wrapper always be the same.
11 | */
12 | export function useRefCallback any>(callback: T): T;
13 |
14 | export function useRefCallback any>(
15 | callback: T | undefined,
16 | ): (...args: Parameters) => ReturnType | undefined;
17 |
18 | /**
19 | * Hook to wrap callback function, ensure the wrapper always has the same identify,
20 | * meanwhile, always call the latest callback passed when call the wrapper.
21 | *
22 | * @param callback The callback function. Always use the latest value passed.
23 | * @returns The wrapper function. The identify of the wrapper always be the same.
24 | */
25 | // eslint-disable-next-line space-before-function-paren
26 | export function useRefCallback any>(
27 | callback: T | undefined,
28 | ): T | ((...args: Parameters) => ReturnType | undefined) {
29 | const callbackRef = useRef();
30 | callbackRef.current = callback;
31 | const wrapper = useConstCallback((...args) => callbackRef.current?.(...args));
32 | return wrapper;
33 | }
34 |
--------------------------------------------------------------------------------
/src/hooks/click/index.ts:
--------------------------------------------------------------------------------
1 | import { RefObject, useEffect } from 'react';
2 | import { off, on } from '../../utils/dom/event';
3 | import { useRefCallback } from '../callback';
4 |
5 | export type ClickOutsideConfig = {
6 | container: RefObject;
7 | method: 'click' | 'touchstart';
8 | callback: () => void;
9 | closeOnClickOutside: boolean;
10 | };
11 |
12 | export function useClickOutside(config: ClickOutsideConfig): void {
13 | const listener = useRefCallback(function (event: MouseEvent | TouchEvent) {
14 | if (config.closeOnClickOutside && !config.container.current?.contains(event.target as Node)) {
15 | config.callback();
16 | }
17 | });
18 | useEffect(() => {
19 | on(document, config.method, listener);
20 | return (): void => {
21 | off(document, config.method, listener);
22 | };
23 | }, []);
24 | }
25 |
--------------------------------------------------------------------------------
/src/hooks/const.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable semi */
2 | import { useRef } from 'react';
3 |
4 | /**
5 | * Returns a constant value or uses to ensure the same reference.
6 | * If provides reference type value (object, array...), it will always return same reference from the first initial, even if re-rendered.
7 | * @param initialValue
8 | */
9 | export function useConst(initialValue: T | (() => T)): T {
10 | const ref = useRef<{ value: T }>();
11 | if (ref.current === undefined) {
12 | ref.current = {
13 | value: typeof initialValue === 'function' ? (initialValue as () => T)() : initialValue,
14 | };
15 | }
16 | return ref.current.value;
17 | }
18 |
--------------------------------------------------------------------------------
/src/hooks/dom/index.ts:
--------------------------------------------------------------------------------
1 | export * from './resize';
2 |
--------------------------------------------------------------------------------
/src/hooks/dom/resize.ts:
--------------------------------------------------------------------------------
1 | import { MutableRefObject, useEffect } from 'react';
2 | import { useRefCallback } from '../callback';
3 |
4 | type ResizeHandler = () => void;
5 |
6 | export type useResizeOptions = {
7 | onResize?: ResizeHandler;
8 | };
9 | export function useResize(options: useResizeOptions): void {
10 | const { onResize } = options;
11 |
12 | const onResizeValue = useRefCallback(onResize);
13 |
14 | useEffect(() => {
15 | if (window) {
16 | window.addEventListener('resize', onResizeValue);
17 | }
18 | return (): void => {
19 | if (window) {
20 | window.removeEventListener('resize', onResizeValue);
21 | }
22 | };
23 | // eslint-disable-next-line react-hooks/exhaustive-deps
24 | }, []);
25 | }
26 |
27 | export type useContainerResizeOptions = {
28 | containerRef: MutableRefObject;
29 | callback: (...args: any[]) => void;
30 | removeCallback?: (...args: any[]) => void;
31 | };
32 | export function useContainerResize(options: useContainerResizeOptions): void {
33 | const {
34 | containerRef,
35 | callback: propsCallback,
36 | removeCallback: propsRemoveCallback = (): void => {},
37 | } = options;
38 |
39 | const callback = useRefCallback(propsCallback);
40 | const removecallback = useRefCallback(propsRemoveCallback);
41 |
42 | useEffect(() => {
43 | callback();
44 |
45 | return (): void => {
46 | removecallback();
47 | };
48 | }, [callback, removecallback]);
49 |
50 | useEffect(() => {
51 | let resizeObserver: ResizeObserver;
52 | const container = containerRef.current;
53 |
54 | if (ResizeObserver && container) {
55 | resizeObserver = new ResizeObserver(() => {
56 | callback();
57 | });
58 | resizeObserver.observe(container);
59 | }
60 |
61 | return (): void => {
62 | if (container && resizeObserver) {
63 | resizeObserver.unobserve(container);
64 | removecallback();
65 | }
66 | };
67 | // eslint-disable-next-line react-hooks/exhaustive-deps
68 | }, [callback]);
69 | }
70 |
--------------------------------------------------------------------------------
/src/hooks/event.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable semi */
2 | import { RefObject, useRef, useEffect } from 'react';
3 |
4 | export function useEventListener(
5 | element: RefObject | Element | Window | Document,
6 | eventName: string,
7 | listener: (event: Event) => void,
8 | passive?: boolean,
9 | ): void {
10 | const listenerRef = useRef(listener);
11 | listenerRef.current = listener;
12 |
13 | useEffect(() => {
14 | const actualElement = element && 'current' in element ? element.current : element;
15 | if (!actualElement) {
16 | return;
17 | }
18 | const finalListener = (event: Event): void => listenerRef.current(event);
19 | actualElement.addEventListener(eventName, finalListener, { passive });
20 | return (): void => actualElement.removeEventListener(eventName, finalListener);
21 | }, [element, eventName, passive]);
22 | }
23 |
--------------------------------------------------------------------------------
/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable semi */
2 | export * from './state';
3 | export * from './callback';
4 | export * from './input';
5 | export * from './touch';
6 | export * from './click';
7 | export * from './drag';
8 |
--------------------------------------------------------------------------------
/src/hooks/input/index.ts:
--------------------------------------------------------------------------------
1 | export * from './composition-input';
2 |
--------------------------------------------------------------------------------
/src/hooks/state/controlled.ts:
--------------------------------------------------------------------------------
1 | import { Dispatch, SetStateAction, useEffect, useState } from 'react';
2 |
3 | export function useControlled(
4 | value: T | undefined,
5 | defaultValue: T,
6 | ): [T, Dispatch>] {
7 | const [activeValue, setActiveValue] = useState(() => {
8 | return typeof value !== 'undefined' ? value : defaultValue;
9 | });
10 |
11 | useEffect(() => {
12 | if (typeof value === 'undefined') return;
13 | setActiveValue(value!);
14 | }, [value]);
15 |
16 | return [activeValue, setActiveValue];
17 | }
18 |
--------------------------------------------------------------------------------
/src/hooks/state/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable semi */
2 | export * from './record';
3 | export * from './controlled';
4 |
--------------------------------------------------------------------------------
/src/hooks/state/record.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */
2 | /* eslint-disable semi */
3 | import { Dispatch, SetStateAction, useRef, useState } from 'react';
4 |
5 | /**
6 | * Returns a stateful value in record like object type, a function to update it.
7 | * The new state object can be partial, the hook will merge new and old object automatically.
8 | */
9 | export function useRecord>(
10 | initState: S | (() => S),
11 | ): [S, Dispatch>>] {
12 | const ref = useRef<{ value: S }>();
13 | if (!ref.current) {
14 | ref.current = {
15 | value: typeof initState === 'function' ? initState() : initState,
16 | };
17 | }
18 | const [record, setState] = useState(initState);
19 | const callbackRef = useRef>>>(partial => {
20 | const newRecord = {
21 | ...ref.current!.value,
22 | ...partial,
23 | };
24 | ref.current!.value = newRecord;
25 | setState(newRecord);
26 | });
27 | return [record, callbackRef.current];
28 | }
29 |
--------------------------------------------------------------------------------
/src/hooks/timer.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable semi */
2 | import { useConst } from './const';
3 | import { useEffect } from 'react';
4 |
5 | export interface UseSetTimeoutReturnType {
6 | setTimeout(callback: () => void, delay: number): number;
7 | clearTimeout(id: number): void;
8 | }
9 |
10 | /**
11 | * Returns a wrapper function for `setTimeout` which automatically cleanup.
12 | */
13 | export function useSetTimeout(): UseSetTimeoutReturnType {
14 | const idMap = useConst>({});
15 |
16 | useEffect(
17 | () => (): void => {
18 | for (const id of Object.keys(idMap)) {
19 | clearTimeout(id as any);
20 | }
21 | },
22 | [idMap],
23 | );
24 |
25 | return useConst({
26 | setTimeout(callback, delay) {
27 | const id = setTimeout(callback, delay) as unknown as number;
28 | idMap[id] = 1;
29 | return id;
30 | },
31 | clearTimeout(id) {
32 | delete idMap[id];
33 | clearTimeout(id);
34 | },
35 | });
36 | }
37 |
38 | export interface UseSetIntervalReturnType {
39 | setInterval(callback: () => void, interval: number): number;
40 | clearInterval(id: number): void;
41 | }
42 |
43 | /**
44 | * Returns a wrapper function for `setInterval` which automatically cleanup.
45 | */
46 | export function useSetInterval(): UseSetIntervalReturnType {
47 | const idMap = useConst>({});
48 |
49 | useEffect(
50 | () => (): void => {
51 | for (const id of Object.keys(idMap)) {
52 | clearInterval(id as any);
53 | }
54 | },
55 | [idMap],
56 | );
57 |
58 | return useConst({
59 | setInterval(callback, interval) {
60 | const id = setInterval(callback, interval) as unknown as number;
61 | idMap[id] = 1;
62 | return id;
63 | },
64 | clearInterval(id) {
65 | delete idMap[id];
66 | clearInterval(id);
67 | },
68 | });
69 | }
70 |
--------------------------------------------------------------------------------
/src/hooks/touch/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable semi */
2 | export * from './touch';
3 |
--------------------------------------------------------------------------------
/src/icon/arrow-left.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icon/arrow-right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icon/attention.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icon/check-one.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icon/check.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icon/close-one.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/icon/close.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icon/info.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/index.less:
--------------------------------------------------------------------------------
1 | // This file is auto gererated by build/build-entry.js
2 | @import './style/index';
3 | @import './style/root';
4 | @import './components/action-sheet/index';
5 | @import './components/badge/index';
6 | @import './components/button/index';
7 | @import './components/calendar/index';
8 | @import './components/cascader/index';
9 | @import './components/cascader-sliding/index';
10 | @import './components/cell/index';
11 | @import './components/checkbox/index';
12 | @import './components/col/index';
13 | @import './components/collapse/index';
14 | @import './components/date-time-picker/index';
15 | @import './components/dialog/index';
16 | @import './components/dropdown/index';
17 | @import './components/dropdown-item/index';
18 | @import './components/field/index';
19 | @import './components/header/index';
20 | @import './components/header-tab/index';
21 | @import './components/header-tabs/index';
22 | @import './components/icon/index';
23 | @import './components/image-preview/index';
24 | @import './components/infinite-scroll/index';
25 | @import './components/loading/index';
26 | @import './components/notice-bar/index';
27 | @import './components/overlay/index';
28 | @import './components/picker/index';
29 | @import './components/picker-column/index';
30 | @import './components/popup/index';
31 | @import './components/popup-drill-down/index';
32 | @import './components/progress/index';
33 | @import './components/pull-refresh/index';
34 | @import './components/radio/index';
35 | @import './components/row/index';
36 | @import './components/search/index';
37 | @import './components/slider/index';
38 | @import './components/sticky/index';
39 | @import './components/swipe/index';
40 | @import './components/swipe-cell/index';
41 | @import './components/switch/index';
42 | @import './components/tab/index';
43 | @import './components/table/index';
44 | @import './components/tabs/index';
45 | @import './components/tag/index';
46 | @import './components/toast/index';
47 | @import './components/toolbar/index';
48 | @import './components/tree/index';
49 |
--------------------------------------------------------------------------------
/src/locale/en-US.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | dialog: {
3 | cancel: 'Cancel',
4 | confirm: 'Confirm',
5 | },
6 | picker: {
7 | cancel: 'Cancel',
8 | confirm: 'Confirm',
9 | },
10 | datetimePicker: {
11 | title: 'Please Select',
12 | year: '',
13 | month: '',
14 | day: '',
15 | hour: '',
16 | minute: '',
17 | second: '',
18 | },
19 | calendar: {
20 | 0: '日',
21 | 1: '一',
22 | 2: '二',
23 | 3: '三',
24 | 4: '四',
25 | 5: '五',
26 | 6: '六',
27 | },
28 | field: {
29 | placeholder: 'Please enter content',
30 | },
31 | loading: {
32 | loading: 'Loading',
33 | },
34 | toast: {
35 | loading: 'loading...',
36 | },
37 | toolbar: {
38 | cancel: 'Cancel',
39 | confirm: 'Confirm',
40 | title: 'Please Select',
41 | },
42 | };
43 |
--------------------------------------------------------------------------------
/src/locale/ja.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | dialog: {
3 | cancel: 'キャンセル',
4 | confirm: '確認',
5 | },
6 | picker: {
7 | cancel: 'キャンセル',
8 | confirm: '確認',
9 | },
10 | datetimePicker: {
11 | title: '選択してください',
12 | year: '年',
13 | month: '月',
14 | day: '日',
15 | hour: '時',
16 | minute: '分',
17 | second: '秒',
18 | },
19 | calendar: {
20 | 0: '日',
21 | 1: '一',
22 | 2: '二',
23 | 3: '三',
24 | 4: '四',
25 | 5: '五',
26 | 6: '六',
27 | },
28 | field: {
29 | placeholder: '请输入内容',
30 | },
31 | loading: {
32 | loading: '加载显示',
33 | },
34 | toast: {
35 | loading: '加载中...',
36 | },
37 | toolbar: {
38 | cancel: 'キャンセル',
39 | confirm: '確認',
40 | title: '選んでください',
41 | },
42 | };
43 |
--------------------------------------------------------------------------------
/src/locale/zh-CN.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | dialog: {
3 | cancel: '取消',
4 | confirm: '确定',
5 | },
6 | picker: {
7 | cancel: '取消',
8 | confirm: '确定',
9 | },
10 | datetimePicker: {
11 | title: '选择时间',
12 | year: '年',
13 | month: '月',
14 | day: '日',
15 | hour: '时',
16 | minute: '分',
17 | second: '秒',
18 | },
19 | calendar: {
20 | 0: '日',
21 | 1: '一',
22 | 2: '二',
23 | 3: '三',
24 | 4: '四',
25 | 5: '五',
26 | 6: '六',
27 | year: '年',
28 | month: '月',
29 | },
30 | field: {
31 | placeholder: '请输入内容',
32 | },
33 | loading: {
34 | loading: '加载显示',
35 | },
36 | toast: {
37 | loading: '加载中...',
38 | },
39 | toolbar: {
40 | cancel: '取消',
41 | confirm: '确定',
42 | title: '请选择',
43 | },
44 | };
45 |
--------------------------------------------------------------------------------
/src/public-api/index.ts:
--------------------------------------------------------------------------------
1 | import { context } from '../common/popup/context';
2 |
3 | export function getZIndex(): number {
4 | return context.zIndex;
5 | }
6 |
7 | export function setZIndex(zIndex: number): void {
8 | context.zIndex = zIndex;
9 | }
10 |
11 | export { useZIndex } from '../common/popup/context';
12 |
13 | export { getMonthWeekByDate, getDateByMonthWeek } from '../utils/format/date';
14 |
--------------------------------------------------------------------------------
/src/style/animation.less:
--------------------------------------------------------------------------------
1 | @import './var';
2 | @import './common.less';
3 |
4 | @keyframes omui-fade-in {
5 | from {
6 | opacity: 0;
7 | }
8 |
9 | to {
10 | opacity: 1;
11 | }
12 | }
13 |
14 | @keyframes omui-fade-out {
15 | from {
16 | opacity: 1;
17 | }
18 |
19 | to {
20 | opacity: 0;
21 | }
22 | }
23 |
24 | .@{prefix}fade {
25 | &-enter-active {
26 | animation: @animation-duration-base omui-fade-in;
27 | animation-fill-mode: forwards;
28 | }
29 |
30 | &-enter-done {
31 | display: block;
32 | }
33 |
34 | &-exit-active {
35 | animation: @animation-duration-base omui-fade-out;
36 | animation-fill-mode: forwards;
37 | }
38 |
39 | &-exit-done {
40 | display: none;
41 | }
42 | }
43 |
44 | @keyframes omui-loading-circle {
45 | from {
46 | transform: rotate(0);
47 | }
48 |
49 | to {
50 | transform: rotate(360deg);
51 | }
52 | }
53 |
54 | .@{prefix}loading-circle {
55 | display: inline-block;
56 | transform-origin: center;
57 | animation: omui-loading-circle 1s linear infinite;
58 | }
59 |
--------------------------------------------------------------------------------
/src/style/color-palette/palette.less:
--------------------------------------------------------------------------------
1 | @import './tinyColor.less';
2 |
3 | .colorPaletteMixin() {
4 | @functions: ~`(function(){
5 | this.colorPalette = function(color, index) {
6 |
7 | var darkestL = 16;
8 | var lightestL = 96;
9 | var hsl = tinycolor(color).toHsl();
10 | var curLight = hsl.l * 100;
11 |
12 | var light = void 0;
13 | var darkStep = (curLight - darkestL) / 5;
14 | var lightStep = (lightestL - curLight) / 5;
15 | if (index === 6) {
16 | light = hsl.l;
17 | } else if (index < 6) {
18 | light = (curLight + (6 - index) * lightStep) / 100;
19 | } else if (index > 6) {
20 | light = (curLight - (index - 6) * darkStep) / 100;
21 | }
22 |
23 | return tinycolor({
24 | h: Math.round(hsl.h),
25 | s: hsl.s,
26 | l: light
27 | }).toHexString();
28 | }
29 | })()`;
30 | }
31 | .colorPaletteMixin();
32 |
--------------------------------------------------------------------------------
/src/style/common.less:
--------------------------------------------------------------------------------
1 | @prefix: omui-;
2 |
3 | @import './var';
4 | @import './mixins';
5 | @import './hairline.less';
6 |
--------------------------------------------------------------------------------
/src/style/compat.less:
--------------------------------------------------------------------------------
1 | .remCompatMixin() {
2 | @functions: ~`(function(){
3 | this.remcompat = function(pxvalue) {
4 | return pxvalue.replace(/px/g, 'PX');
5 | }
6 | })()`;
7 | }
8 | .remCompatMixin();
--------------------------------------------------------------------------------
/src/style/hairline.less:
--------------------------------------------------------------------------------
1 | @import './mixins/hairline';
2 | @import './common.less';
3 |
4 | [class*='@{prefix}hairline'] {
5 | position: relative;
6 |
7 | &::after {
8 | .hairline();
9 | }
10 | }
11 |
12 | .@{prefix}hairline {
13 | &--top::after {
14 | border-top-width: 1PX; /* no */
15 | }
16 |
17 | &--left::after {
18 | border-left-width: 1PX; /* no */
19 | }
20 |
21 | &--right::after {
22 | border-right-width: 1PX; /* no */
23 | }
24 |
25 | &--bottom::after {
26 | border-bottom-width: 1PX; /* no */
27 | }
28 |
29 | &--top-bottom::after {
30 | border-width: 1PX 0; /* no */
31 | }
32 |
33 | &--surround::after {
34 | border-width: 1PX; /* no */
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/style/index.less:
--------------------------------------------------------------------------------
1 | @import './compat';
2 | @import './rem';
3 | @import './reset';
4 | @import './common.less';
5 | @import './animation';
6 |
--------------------------------------------------------------------------------
/src/style/mixins.less:
--------------------------------------------------------------------------------
1 | @import './mixins/hairline';
2 | @import './mixins/radius';
3 | @import './mixins/shadow';
4 |
--------------------------------------------------------------------------------
/src/style/mixins/hairline.less:
--------------------------------------------------------------------------------
1 | /**
2 | * 一像素线
3 | */
4 | .hairline-common() {
5 | position: absolute;
6 | z-index: 1;
7 | box-sizing: border-box;
8 | content: ' ';
9 | pointer-events: none;
10 | }
11 |
12 | .hairline(@gray-3: #ebedf0) {
13 | .hairline-common();
14 |
15 | top: -50%;
16 | left: -50%;
17 | width: 200%;
18 | height: 200%;
19 | border: 0 solid @gray-3;
20 | transform: scale(0.5);
21 | will-change: transform;
22 | }
23 |
24 | .hairline-bottom(@gray-3: #ebedf0, @horizontal: 0) {
25 | .hairline-common();
26 |
27 | right: 0;
28 | right: @horizontal;
29 | bottom: 0;
30 | left: @horizontal;
31 | border-bottom: 1px solid @gray-3;
32 | transform: scaleY(0.5);
33 | will-change: transform;
34 | }
35 |
--------------------------------------------------------------------------------
/src/style/mixins/radius.less:
--------------------------------------------------------------------------------
1 | /**
2 | * 六个层级的圆角设置
3 | */
4 | .okui-border-radius(@level:1) when (@level = 1) {
5 | border-radius: 2PX; /* no */
6 | }
7 | .okui-border-radius(@level) when (@level = 2) {
8 | border-radius: 4PX; /* no */
9 | }
10 | .okui-border-radius(@level) when (@level = 3) {
11 | border-radius: 8PX; /* no */
12 | }
13 | .okui-border-radius(@level) when (@level = 4) {
14 | border-radius: 12PX; /* no */
15 | }
16 | .okui-border-radius(@level) when (@level = 5) {
17 | border-radius: 16PX; /* no */
18 | }
19 | .okui-border-radius(@level) when (@level = 6) {
20 | border-radius: 18PX; /* no */
21 | }
22 |
--------------------------------------------------------------------------------
/src/style/mixins/shadow.less:
--------------------------------------------------------------------------------
1 | /**
2 | * 五个层级的阴影设置
3 | */
4 | .okui-box-shadow(@level:1) when (@level = 1) {
5 | box-shadow: 0 2px 6px 0 rgba(#a6c6ec, 0.1);
6 | }
7 | .okui-box-shadow(@level) when (@level = 2) {
8 | box-shadow: 0 4px 12px 0 rgba(#a6c6ec, 0.1);
9 | }
10 | .okui-box-shadow(@level) when (@level = 3) {
11 | box-shadow: 0 6px 18px 0 rgba(#a6c6ec, 0.1);
12 | }
13 | .okui-box-shadow(@level) when (@level = 4) {
14 | box-shadow: 0 8px 24px 0 rgba(#a6c6ec, 0.1);
15 | }
16 | .okui-box-shadow(@level) when (@level = 5) {
17 | box-shadow: 0 10px 46px 0 rgba(#a6c6ec, 0.18);
18 | }
19 | .okui-box-shadow(@level) when (@level = 6) {
20 | box-shadow: 0 10px 46px 0 rgba(#000, 0.1);
21 | }
22 |
--------------------------------------------------------------------------------
/src/style/rem.less:
--------------------------------------------------------------------------------
1 | @import './compat.less';
2 |
3 | //
4 | // Rem
5 | // --------------------------------------------------
6 | // Vertical screen
7 | // 375屏幕为 20px,以此为基础计算出每一种宽度的字体大小
8 |
9 | @useDefaultFontSize: true;
10 |
11 | @baseWidth: 375PX; /* no */
12 | @baseFont: 20PX; /* no */
13 | @bps: 320PX, 360PX, 375PX, 400PX, 414PX, 440PX, 480PX, 520PX, 560PX, 600PX, 640PX, 680PX, 720PX,
14 | 760PX, 800PX, 960PX; /* no */
15 |
16 | .setFontSize () when (@useDefaultFontSize = true) {
17 | html {
18 | font-size: ~`remcompat('@{baseFont}') `; /* no */
19 | }
20 | .loop(@i: 1) when (@i <= length(@bps)) {
21 | @bp: extract(@bps, @i);
22 | @font: (@bp / @baseWidth) * @baseFont;
23 |
24 | @media only screen and (min-width: @bp) {
25 | html {
26 | font-size: ~`remcompat('@{font}') ` !important; /* no */
27 | }
28 | }
29 | .loop((@i + 1));
30 | }
31 | .loop;
32 | }
33 |
34 | .setFontSize();
35 |
--------------------------------------------------------------------------------
/src/style/root.less:
--------------------------------------------------------------------------------
1 |
2 | /* stylelint-disable */
3 | :root{--animation-duration-base:0.3s;--animation-duration-fast:0.2s;--blue:#0278ff;--cyan:#00d2d5;--orange:#fec038;--red:#ff6767;--white:#fff;--black:#000;--primary-color:#0278ff;--success-color:#00d2d5;--warning-color:#fec038;--danger-color:#ff6767;--gray-1:#fafafa;--gray-2:#f7f7f7;--gray-3:#eeeeee;--gray-4:#dcdcdc;--gray-5:#c1c1c1;--text-color-1:#000000;--text-color-2:#333333;--text-color-3:#666666;--text-color-4:#999999;--text-color-5:#c1c1c1;--text-color-link:#0278ff;--background-color-base:#fff;--background-color-active:rgba(153; 153; 153; 0.1);--primary-color-1:#0068de;--primary-color-2:#0278ff;--primary-color-3:#3191ff;--primary-color-4:#8ec2ff;--primary-color-5:#ebf4ff;--success-color-1:#00b8bb;--success-color-2:#00d2d5;--success-color-3:#0dfbff;--success-color-4:#7cfdff;--success-color-5:#ebffff;--warning-color-1:#feb10b;--warning-color-2:#fec038;--warning-color-3:#fecb5c;--warning-color-4:#ffe2a3;--warning-color-5:#fff9eb;--danger-color-1:#ff3030;--danger-color-2:#ff6767;--danger-color-3:#ff8181;--danger-color-4:#ffb6b6;--danger-color-5:#ffebeb;--picker-confirm-btn-color:#0278ff;}
4 |
--------------------------------------------------------------------------------
/src/utils/const.ts:
--------------------------------------------------------------------------------
1 | import { prefix } from './create/createBEM';
2 |
3 | /**
4 | * 样式相关常量收敛
5 | */
6 |
7 | // border
8 | export const BORDER = `${prefix}hairline`;
9 | export const BORDER_TOP = `${BORDER}--top`;
10 | export const BORDER_LEFT = `${BORDER}--left`;
11 | export const BORDER_RIGHT = `${BORDER}--right`;
12 | export const BORDER_BOTTOM = `${BORDER}--bottom`;
13 | export const BORDER_SURROUND = `${BORDER}--surround`;
14 | export const BORDER_TOP_BOTTOM = `${BORDER}--top-bottom`;
15 |
--------------------------------------------------------------------------------
/src/utils/create/bem.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * bem helper
3 | * bem() // 'button'
4 | * bem('text') // 'button__text'
5 | * bem('text', 'color') // 'button__text button__text--color'
6 | * bem({ disabled }) // 'button button--disabled'
7 | * bem('text', { disabled }) // 'button__text button__text--disabled'
8 | * bem(['primary', 'large']) // 'button button--primary button--large'
9 | */
10 |
11 | type Mod = string | { [key: string]: any };
12 | type Mods = Mod | Mod[];
13 |
14 | const ELEMENT = '__';
15 | const MODS = '--';
16 |
17 | function join(name: string, el?: string | undefined, symbol?: string): string {
18 | return el ? name + symbol + el : name;
19 | }
20 |
21 | function prefix(name: string, mods: Mods): string {
22 | if (typeof mods === 'string') {
23 | return join(name, mods, MODS);
24 | }
25 |
26 | if (Array.isArray(mods)) {
27 | return mods.map(item => prefix(name, item)).join(' ');
28 | }
29 |
30 | const ret: string[] = [];
31 | if (mods) {
32 | Object.keys(mods).forEach(key => {
33 | if (mods[key]) {
34 | ret.push(name + MODS + key);
35 | }
36 | });
37 | }
38 |
39 | return ret.join(' ');
40 | }
41 |
42 | export function createBEM(name: string) {
43 | return function (el?: Mods, mods?: Mods): string {
44 | if (el && typeof el !== 'string') {
45 | mods = el;
46 | el = '';
47 | }
48 |
49 | el = join(name, el as string | undefined, ELEMENT);
50 |
51 | if (mods) {
52 | return `${el} ${prefix(el, mods)}`.trim();
53 | } else {
54 | return el;
55 | }
56 | };
57 | }
58 |
--------------------------------------------------------------------------------
/src/utils/create/createBEM.ts:
--------------------------------------------------------------------------------
1 | import { createBEM } from './bem';
2 |
3 | /**
4 | * 全局bem创建函数
5 | */
6 | export const prefix = 'omui-';
7 |
8 | export default function create(name: string): ReturnType {
9 | return createBEM(`${prefix}${name}`);
10 | }
11 |
12 | export function addClass(
13 | base: string,
14 | ...classNames: (string | undefined | null | boolean | Array)[]
15 | ): string {
16 | let result = base;
17 | for (let i = 0; i < classNames.length; i++) {
18 | const className = classNames[i];
19 | if (className) {
20 | if (Array.isArray(className)) {
21 | result = addClass(result, className);
22 | } else {
23 | result = `${result} ${className}`;
24 | }
25 | }
26 | }
27 | return result;
28 | }
29 |
--------------------------------------------------------------------------------
/src/utils/create/i18n.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import LocaleContext from '../../components/locale-context';
3 |
4 | import { get } from '..';
5 | import { camelize, translate } from '../format/string';
6 |
7 | export function createI18N(name: string): (path: string) => string {
8 | const prefix = name ? camelize(name) + '.' : '';
9 |
10 | return (path: string, ...args: any[]): string =>
11 | translate(get(useContext(LocaleContext), prefix + path), args);
12 | }
13 |
--------------------------------------------------------------------------------
/src/utils/create/index.ts:
--------------------------------------------------------------------------------
1 | import { createBEM } from './bem';
2 | import { createI18N } from './i18n';
3 |
4 | /**
5 | * 全局函数
6 | */
7 | const prefix = 'omui-';
8 |
9 | type CreateNamespaceReturn = [ReturnType, ReturnType];
10 |
11 | export function createNamespace(name: string): CreateNamespaceReturn {
12 | return [createBEM(`${prefix}${name}`), createI18N(name)];
13 | }
14 |
--------------------------------------------------------------------------------
/src/utils/dom/raf.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * requestAnimationFrame polyfill
3 | */
4 |
5 | import { isServer } from '..';
6 |
7 | let prev = Date.now();
8 |
9 | function fallback(fn: FrameRequestCallback): number {
10 | const curr = Date.now();
11 | const ms = Math.max(0, 16 - (curr - prev));
12 | const id = setTimeout(fn, ms);
13 | prev = curr + ms;
14 | return id;
15 | }
16 |
17 | const root = (isServer ? global : window) as Window;
18 |
19 | const iRaf = root.requestAnimationFrame || fallback;
20 |
21 | const iCancel = root.cancelAnimationFrame || root.clearTimeout;
22 |
23 | export function raf(fn: FrameRequestCallback): number {
24 | return iRaf.call(root, fn);
25 | }
26 |
27 | export function doubleRaf(fn: FrameRequestCallback): void {
28 | raf(() => {
29 | raf(fn);
30 | });
31 | }
32 |
33 | export function cancelRaf(id: number): void {
34 | iCancel.call(root, id);
35 | }
36 |
--------------------------------------------------------------------------------
/src/utils/dom/scroll.ts:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | type ScrollElement = HTMLElement | Window;
5 |
6 | const overflowScrollReg = /scroll|auto/i;
7 |
8 | export function getScrollEventTarget(
9 | element: HTMLElement,
10 | rootParent: ScrollElement = window,
11 | ): ScrollElement {
12 | let node = element;
13 |
14 | while (node && node.tagName !== 'HTML' && node.nodeType === 1 && node !== rootParent) {
15 | const { overflowY } = window.getComputedStyle(node);
16 |
17 | if (overflowScrollReg.test(overflowY as string)) {
18 | if (node.tagName !== 'BODY') {
19 | return node;
20 | }
21 |
22 | const { overflowY: htmlOverflowY } = window.getComputedStyle(node.parentNode as Element);
23 |
24 | if (overflowScrollReg.test(htmlOverflowY as string)) {
25 | return node;
26 | }
27 | }
28 | node = node.parentNode as HTMLElement;
29 | }
30 |
31 | return rootParent;
32 | }
33 |
34 | export function getScrollBottom(element: ScrollElement): number {
35 | if (typeof window === 'undefined') {
36 | return 0;
37 | }
38 | let scrollElement;
39 | if (element === window) {
40 | scrollElement = window.document.documentElement;
41 | } else {
42 | scrollElement = element as HTMLElement;
43 | }
44 | return scrollElement.scrollHeight - scrollElement.scrollTop - scrollElement.clientHeight;
45 | }
46 |
47 | export function getScrollTop(element: ScrollElement): number {
48 | return 'scrollTop' in element ? element.scrollTop : element.pageYOffset;
49 | }
50 |
51 | export function setScrollTop(element: ScrollElement, value: number): void {
52 | 'scrollTop' in element ? (element.scrollTop = value) : element.scrollTo(element.scrollX, value);
53 | }
54 |
55 | export function getRootScrollTop(): number {
56 | if (typeof window === 'undefined') {
57 | return 0;
58 | }
59 | return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
60 | }
61 |
62 | export function setRootScrollTop(value: number): void {
63 | if (typeof window === 'undefined') {
64 | return;
65 | }
66 | setScrollTop(window, value);
67 | setScrollTop(document.body, value);
68 | }
69 |
--------------------------------------------------------------------------------
/src/utils/dom/unit.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.__esModule = true;
4 | exports.value2DomUnit = value2DomUnit;
5 |
6 | /**
7 | * 串转px或者rem
8 | */
9 | var unit = 'px';
10 |
11 | function value2DomUnit(value, multiple) {
12 | if (value === void 0) {
13 | value = '';
14 | }
15 |
16 | if (multiple === void 0) {
17 | multiple = 1;
18 | }
19 |
20 | if (value === '') {
21 | return '';
22 | }
23 |
24 | if (typeof value === 'number') {
25 | return value * multiple + 'px';
26 | } else {
27 | unit = value.indexOf('rem') > -1 ? 'rem' : 'px';
28 | value = value.replace(/[^\d.]/g, '').trim();
29 | return '' + Number(value) * multiple + unit;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/utils/format/string.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 驼峰处理
3 | * b(okee-button) // 'okeeButton'
4 | */
5 | export function camelize(str: string): string {
6 | return str.replace(/-(\w)/g, (_match, p1) => p1.toUpperCase());
7 | }
8 |
9 | export function upperCamelize(str: string): string {
10 | return camelize(str).replace(/^(\w)/, (_match, p1) => p1.toUpperCase());
11 | }
12 |
13 | /**
14 | * 多语言文案翻译
15 | * @param msg 源文案
16 | * @param options 格式化选项
17 | * @example
18 | * translate('normal text') // 'normal text'
19 | * translate('已选 {num} 项', { num: 10 }) // '已选 10 项'
20 | * translate('escape {{ brace }} ') // 'escape brace'
21 | */
22 | const RE_NARGS = /(%|)\{([0-9a-zA-Z_]+)\}/g;
23 |
24 | /**
25 | * 获取字符长度
26 | * 数字英文算一个字符长度 汉字算两个字符长度
27 | */
28 | export function getStringLen(str: string): number {
29 | // eslint-disable-next-line no-control-regex
30 | return !str ? 0 : str.replace(/[^\x00-\xff]/g, 'oe').length;
31 | }
32 |
33 | export function translate(msg: string, options: any): string {
34 | if (options.length === 1 && typeof options[0] === 'object') {
35 | options = options[0];
36 | }
37 |
38 | if (!options || !options.hasOwnProperty) {
39 | options = {};
40 | }
41 |
42 | return msg.replace(RE_NARGS, (match, prefix, i, index) => {
43 | let result;
44 |
45 | if (msg[index - 1] === '{' && msg[index + match.length] === '}') {
46 | return i;
47 | } else {
48 | result = Object.prototype.hasOwnProperty.call(options, i) ? options[i] : null;
49 | if (result === null || result === undefined) {
50 | return '';
51 | }
52 |
53 | return result;
54 | }
55 | });
56 | }
57 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 全局运行环境判断
3 | */
4 | export const isServer = typeof window === 'undefined';
5 |
6 | /**
7 | * 定义判断函数
8 | */
9 | export function isDef(value: any): boolean {
10 | return value !== undefined && value !== null;
11 | }
12 |
13 | /**
14 | * 根据path取object中的value
15 | */
16 | let result: any = {};
17 | let keys: string[] = [];
18 |
19 | export function get(object: any, path: string): any {
20 | result = object;
21 | keys = path.split('.');
22 |
23 | keys.forEach(key => {
24 | result = isDef(result[key]) ? result[key] : '';
25 | });
26 | return result;
27 | }
28 |
--------------------------------------------------------------------------------
/src/utils/math.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable semi */
2 |
3 | /**
4 | * Limit the value in specified range.
5 | * @param min specify minimum value
6 | * @param max specify maximum value
7 | */
8 | export function clamp(value: number, min: number, max: number): number {
9 | if (value < min) {
10 | return min;
11 | }
12 | if (value > max) {
13 | return max;
14 | }
15 | return value;
16 | }
17 |
18 | /**
19 | * Create a function to limit the value in specified range.
20 | * @param min specify minimum value
21 | * @param max specify maximum value
22 | */
23 | export function createClamp(min: number, max: number): (value: number) => number {
24 | return function (value: number): number {
25 | return clamp(value, min, max);
26 | };
27 | }
28 |
29 | /**
30 | * Transform a integer number to fit array index in loop.
31 | * @param index the raw index
32 | * @param length the length of array
33 | */
34 | export function clampIndexLoop(index: number, length: number): number {
35 | if (!index) {
36 | return index;
37 | }
38 |
39 | const indexInt = Math.floor(index);
40 | const lengthInt = Math.floor(length);
41 |
42 | if (indexInt > 0) {
43 | return indexInt % lengthInt;
44 | }
45 |
46 | if (length === 1) {
47 | return 0;
48 | }
49 |
50 | return (indexInt % lengthInt) + lengthInt;
51 | }
52 |
53 | /**
54 | * Create a function to transform a integer number to fit array index in loop.
55 | * @param length the length of array
56 | */
57 | export function createClampIndexLoop(length: number): (value: number) => number {
58 | return function (value: number): number {
59 | return clampIndexLoop(value, length);
60 | };
61 | }
62 |
63 | /**
64 | * Get correct index in a array.
65 | * If the array is empty (length 0), return -1.
66 | * @param length The length of the array
67 | * @param rawIndex The raw index to process
68 | */
69 | export function getCorrectIndexInArray(length: number, rawIndex: number): number {
70 | if (length <= 0) {
71 | return -1;
72 | }
73 | const index = rawIndex % length;
74 | if (index < 0) {
75 | return length + index;
76 | }
77 | return index;
78 | }
79 |
--------------------------------------------------------------------------------
/src/utils/struct/array.ts:
--------------------------------------------------------------------------------
1 | export function findClosestNumber(value: number, set: number[]): number {
2 | if (!set.length) return value;
3 |
4 | let closestValue = set[0];
5 | for (let i = 0; i < set.length; i++) {
6 | if (Math.abs(value - set[i]) < Math.abs(value - closestValue)) {
7 | closestValue = set[i];
8 | }
9 | }
10 | return closestValue;
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/struct/tree.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * as tree is a component of library, use prefix "Struct" to distinguish
3 | */
4 |
5 | export type StructTreeValue = string | number | object;
6 |
7 | export type StructTreeOption = {
8 | value: StructTreeValue;
9 | label: string;
10 | };
11 | export interface StructTreeNode extends StructTreeOption {
12 | children?: StructTreeNode[];
13 | needLoad?: boolean;
14 | showAll?: boolean;
15 | }
16 | export type StructTree = StructTreeNode[];
17 |
18 | export type StructFlatTree = StructTreeOption[][];
19 |
20 | export type TreeNodeOperator = (node: StructTreeNode) => T;
21 |
22 | export function findTreeNode(tree: StructTree, value: StructTreeValue): StructTreeNode | null {
23 | let matchedNode = null;
24 | tree.some(treeNode => {
25 | if (treeNode.value === value) {
26 | matchedNode = treeNode;
27 | return true;
28 | }
29 | });
30 | return matchedNode;
31 | }
32 |
33 | /**
34 | * trace the tree by value
35 | */
36 | export function traceTreeByValue(
37 | tree: StructTree,
38 | value: StructTreeValue[],
39 | operator: TreeNodeOperator,
40 | ): void {
41 | value.forEach(singleValue => {
42 | const node = findTreeNode(tree, singleValue);
43 | if (node) {
44 | operator(node);
45 | tree = node.children || [];
46 | }
47 | });
48 | }
49 |
50 | /**
51 | * walk the tree along first branch
52 | */
53 | export function walkTree(tree: StructTree, operator: TreeNodeOperator): void {
54 | while (tree.length) {
55 | const node = tree[0];
56 | if (node) {
57 | operator(node);
58 | tree = node.children || [];
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/utils/types.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable semi */
2 | /*
3 | * utils 公有 type
4 | */
5 |
6 | export type RenderFunction = (props: T) => JSX.Element | null;
7 |
8 | export type RenderContent =
9 | | string
10 | | number
11 | | JSX.Element
12 | | RenderFunction;
13 |
14 | export declare type UnionOmit = T & Omit;
15 |
16 | export function render(
17 | renderFunction: string | JSX.Element | RenderFunction,
18 | props?: T,
19 | ): string | JSX.Element | null {
20 | if (typeof renderFunction === 'function') {
21 | return renderFunction(props);
22 | }
23 | return renderFunction;
24 | }
25 |
26 | export type DefaultTextAlignType = 'start' | 'end' | 'left' | 'right' | 'center';
27 |
--------------------------------------------------------------------------------
/ssr/.npmrc:
--------------------------------------------------------------------------------
1 | registry= https://registry.npmjs.org
--------------------------------------------------------------------------------
/ssr/.yarnrc:
--------------------------------------------------------------------------------
1 | "registry" "https://registry.yarnpkg.com"
--------------------------------------------------------------------------------
/ssr/build/webpack.server.js:
--------------------------------------------------------------------------------
1 | /**
2 | * webpack server config
3 | */
4 | const path = require('path');
5 | const nodeExternals = require('webpack-node-externals');
6 |
7 | function resolve (dir) {
8 | return path.join(__dirname, '..', dir);
9 | }
10 |
11 | module.exports = {
12 | target: 'node',
13 | mode: 'development',
14 | entry: resolve('src/server/index'),
15 | output: {
16 | filename: 'index.js',
17 | path: resolve('lib/server'),
18 | },
19 | externals: [nodeExternals()],
20 | resolve: {
21 | extensions: [ '.tsx', '.ts', '.js' ],
22 | },
23 | module: {
24 | rules: [
25 | {
26 | test: /\.tsx?$/,
27 | loader: 'babel-loader',
28 | options: {
29 | presets: [
30 | [
31 | '@babel/preset-env',
32 | {
33 | loose: true,
34 | modules: 'commonjs',
35 | targets: {
36 | esmodules: false,
37 | node: 'current',
38 | },
39 | useBuiltIns: 'usage',
40 | }
41 | ],
42 | '@babel/preset-react',
43 | '@babel/preset-typescript',
44 | ],
45 | 'plugins': [
46 | [
47 | '@babel/plugin-transform-runtime'
48 | ],
49 | [
50 | 'transform-class-properties'
51 | ]
52 | ]
53 | }
54 | },
55 | {
56 | test: /\.less$/,
57 | use: [
58 | 'null-loader'
59 | ]
60 | },
61 | {
62 | test: /\.svg$/,
63 | loader: 'url-loader'
64 | },
65 | ]
66 | }
67 | }
--------------------------------------------------------------------------------
/ssr/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ssr",
3 | "version": "1.0.0",
4 | "description": "demo for ssr",
5 | "main": "index.js",
6 | "publishConfig": {
7 | "registry": "https://registry.npmjs.org"
8 | },
9 | "scripts": {
10 | "start": "concurrently \"npm run dev:client\" \"npm run dev:server\" \"npm run dev:start\"",
11 | "dev:client": "webpack --config build/webpack.client.js --watch",
12 | "dev:server": "webpack --config build/webpack.server.js --watch",
13 | "dev:start": "nodemon --watch build --exec node lib/server/index.js"
14 | },
15 | "keywords": [
16 | "ssr"
17 | ],
18 | "author": "Totoo",
19 | "license": "MIT",
20 | "devDependencies": {
21 | "@babel/core": "^7.12.10",
22 | "@babel/plugin-transform-runtime": "^7.12.10",
23 | "@babel/preset-env": "^7.12.11",
24 | "@babel/preset-react": "^7.12.10",
25 | "@babel/preset-typescript": "^7.13.0",
26 | "@babel/runtime": "^7.12.5",
27 | "@types/koa": "^2.11.6",
28 | "@types/koa-static": "^4.0.1",
29 | "@types/node": "^14.14.16",
30 | "babel": "^6.23.0",
31 | "babel-loader": "^8.2.2",
32 | "concurrently": "^6.0.1",
33 | "install": "^0.13.0",
34 | "isomorphic-style-loader": "^5.1.0",
35 | "koa": "^2.13.0",
36 | "koa-static": "^5.0.0",
37 | "less": "^4.1.1",
38 | "less-loader": "^8.1.0",
39 | "nodemon": "^2.0.7",
40 | "npm": "^6.14.10",
41 | "null-loader": "^4.0.1",
42 | "typescript": "^4.1.3",
43 | "webpack": "^5.11.1",
44 | "webpack-cli": "^4.3.0",
45 | "webpack-node-externals": "^2.5.2"
46 | },
47 | "dependencies": {
48 | "core-js": "3",
49 | "dayjs": "^1.10.4"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/ssr/src/client/App.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * App
3 | */
4 | import React, {FC} from 'react';
5 |
6 | import ButtonDemo from '../../../src/components/tabs/demo';
7 |
8 | const App: FC = () => {
9 | return (
10 |
11 |
12 |
13 | );
14 | }
15 |
16 | export default App;
17 |
--------------------------------------------------------------------------------
/ssr/src/client/index.less:
--------------------------------------------------------------------------------
1 | * {
2 | background-color: red;
3 | }
--------------------------------------------------------------------------------
/ssr/src/client/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * client index
3 | */
4 | import React from 'react';
5 | import ReactDom from 'react-dom'
6 | import App from './App'
7 |
8 | import '../../../src/index.less';
9 |
10 | ReactDom.hydrate(
11 | ,
12 | document.getElementById('root')
13 | )
--------------------------------------------------------------------------------
/ssr/src/server/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * server index
3 | */
4 | import path from 'path';
5 | import Koa from 'koa';
6 | import KoaStatic from 'koa-static';
7 | import React from 'react';
8 | import ReactDOMServer from 'react-dom/server';
9 | import App from '../client/App';
10 |
11 | const app = new Koa();
12 |
13 | app.use(KoaStatic(path.join(__dirname, '..', 'client')))
14 |
15 | app.use(async ctx => {
16 | if (ctx.request.url === '/') {
17 | ctx.body = `
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | ${ReactDOMServer.renderToString(
)}
26 |
27 |
28 |
29 | `;
30 | }
31 | });
32 |
33 | app.listen(8080);
34 |
35 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "jsx": "react",
5 | "target": "esnext",
6 | "module": "commonjs",
7 | "moduleResolution": "node",
8 | "strict": true,
9 | "allowJs": true,
10 | "noEmit": true,
11 | "skipLibCheck": true,
12 | "noImplicitThis": true,
13 | "esModuleInterop": true
14 | },
15 | "include": ["./typings/index.d.ts", "docs/**/*", "src/**/*"],
16 | "exclude": ["node_modules"]
17 | }
18 |
--------------------------------------------------------------------------------
/typings/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.css'
2 | declare module '*.less'
3 | declare module '*.svg'
4 |
5 |
--------------------------------------------------------------------------------
/workflows/build.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ main ]
9 | pull_request:
10 | branches: [ main ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [14.x]
20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21 |
22 | steps:
23 | - uses: actions/checkout@v2
24 | - name: Use Node.js ${{ matrix.node-version }}
25 | uses: actions/setup-node@v2
26 | with:
27 | node-version: ${{ matrix.node-version }}
28 | # - run: npm ci
29 | - run: npm i -g yarn && yarn
30 | - run: yarn build
31 |
--------------------------------------------------------------------------------