├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── deploy.yml │ └── main.yml ├── .gitignore ├── .nvmrc ├── .storybook ├── main.js ├── manager.js ├── postcss.config.js ├── preview.js ├── tailwind.config.js ├── tsconfig.json ├── typing │ └── svgr.d.ts └── webpack.config.js ├── .svgrrc.js ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets └── screenshots │ └── overview.png ├── babel.config.js ├── docs ├── CHANGELOG.zh-CN.md ├── CODE_OF_CONDUCT.zh-CN.md ├── CONTRIBUTING.zh-CN.md ├── DEPLOYMENT.md ├── DEPLOYMENT.zh-CN.md ├── DEVELOPMENT.md ├── DEVTIPS.zh-CN.md ├── GETTING_STARTED.md ├── GETTING_STARTED.zh-CN.md ├── INTRODUCTION.zh-CN.md ├── PRINCIPLE.md ├── PRINCIPLE.zh-CN.md ├── README.zh-CN.md ├── TESTING.md ├── TESTING.zh-CN.md └── USING_CUSTOMIZE.zh-CN.md ├── jest.config.js ├── package.json ├── rollup.config.js ├── src ├── UUIProvider.tsx ├── components │ ├── Accordion │ │ ├── Accordion.tsx │ │ ├── AccordionContext.tsx │ │ ├── AccordionPane.tsx │ │ └── index.ts │ ├── Breadcrumb │ │ ├── Breadcrumb.tsx │ │ └── index.ts │ ├── Button │ │ ├── Button.tsx │ │ └── index.ts │ ├── Cascader │ │ ├── Cascader.tsx │ │ └── index.ts │ ├── Checkbox │ │ ├── Checkbox.tsx │ │ ├── CheckboxGroup.tsx │ │ └── index.ts │ ├── Collapse │ │ ├── Collapse.tsx │ │ └── index.ts │ ├── DateTime │ │ ├── DatePicker.tsx │ │ ├── DateRangePicker.tsx │ │ ├── DateSelect.tsx │ │ ├── DateTimePicker.tsx │ │ ├── DateTimeRangePicker.tsx │ │ ├── DateTimeShortcut.tsx │ │ ├── PickerButtons.tsx │ │ ├── TimePicker.tsx │ │ ├── TimeRangePicker.tsx │ │ ├── TimeSelect.tsx │ │ ├── YearMonthSelect.tsx │ │ ├── index.tsx │ │ └── utils │ │ │ ├── DateTimeDefaults.ts │ │ │ ├── DateTimeUtils.ts │ │ │ ├── DateUtils.ts │ │ │ └── TimeUtils.ts │ ├── Dialog │ │ ├── AppDialog.tsx │ │ ├── Dialog.tsx │ │ └── index.ts │ ├── Drawer │ │ ├── Drawer.tsx │ │ └── index.ts │ ├── Icon │ │ ├── Icon.tsx │ │ ├── IconGallery.tsx │ │ └── index.ts │ ├── Input │ │ ├── NumberField.tsx │ │ ├── TextArea.tsx │ │ ├── TextField.tsx │ │ └── index.ts │ ├── Label │ │ ├── CountdownLabel.tsx │ │ ├── DateLabel.tsx │ │ ├── MoneyLabel.tsx │ │ ├── NumberAbbrLabel.tsx │ │ ├── TimeLabel.tsx │ │ └── index.ts │ ├── Layout │ │ ├── Layout.tsx │ │ ├── LayoutAside.tsx │ │ ├── LayoutFooter.tsx │ │ ├── LayoutHeader.tsx │ │ ├── LayoutMain.tsx │ │ ├── LayoutNav.tsx │ │ └── index.ts │ ├── ListBox │ │ ├── ListBox.tsx │ │ └── index.ts │ ├── Loading │ │ ├── LoadingCover.tsx │ │ ├── LoadingSpinner.tsx │ │ └── index.ts │ ├── Menu │ │ ├── Menu.tsx │ │ ├── MenuButton.tsx │ │ ├── MenuItem.tsx │ │ ├── MenuSeparator.tsx │ │ └── index.ts │ ├── Page │ │ ├── Page.tsx │ │ ├── PageAnnotatedSection.tsx │ │ ├── PageSection.tsx │ │ └── index.ts │ ├── Pagination │ │ ├── PageInfo.tsx │ │ ├── PageJumper.tsx │ │ ├── PageList.tsx │ │ ├── PageNextButton.tsx │ │ ├── PagePrevButton.tsx │ │ ├── PageSelector.tsx │ │ ├── PageSize.tsx │ │ ├── Pagination.tsx │ │ ├── PaginationContext.tsx │ │ └── index.ts │ ├── Popover │ │ ├── Popover.tsx │ │ └── index.ts │ ├── ProgressBar │ │ ├── ProgressBar.tsx │ │ └── index.ts │ ├── Radio │ │ ├── Radio.tsx │ │ ├── RadioGroup.tsx │ │ ├── RadioGroupContext.tsx │ │ └── index.ts │ ├── RightClickZone │ │ ├── RightClickZone.tsx │ │ └── index.ts │ ├── SegmentControl │ │ ├── SegmentControl.tsx │ │ └── index.ts │ ├── Select │ │ ├── HTMLSelect.tsx │ │ ├── Select.tsx │ │ └── index.ts │ ├── Skeleton │ │ ├── Paragraph.tsx │ │ ├── Picture.tsx │ │ ├── Skeleton.tsx │ │ ├── Title.tsx │ │ └── index.ts │ ├── Slider │ │ ├── Slider.tsx │ │ └── index.ts │ ├── Stepper │ │ ├── Stepper.tsx │ │ └── index.ts │ ├── Switch │ │ ├── Switch.tsx │ │ └── index.tsx │ ├── Table │ │ ├── Table.tsx │ │ └── index.ts │ ├── Tabs │ │ ├── Tab.tsx │ │ ├── Tabs.tsx │ │ ├── TabsContext.tsx │ │ └── index.ts │ ├── Tag │ │ ├── Tag.tsx │ │ └── index.ts │ ├── Toast │ │ ├── Toast.tsx │ │ ├── Toaster.tsx │ │ └── index.ts │ ├── Tooltip │ │ ├── Tooltip.tsx │ │ └── index.ts │ └── UUIComponentNames.ts ├── core │ ├── UUIComponent.tsx │ ├── UUIComponentProxy.tsx │ ├── index.ts │ ├── modules │ │ ├── UUIComponentPropTypes.ts │ │ ├── UUIComponentProps.ts │ │ ├── UUICustomizeAriaAttributes.ts │ │ └── UUICustomizeNode.tsx │ └── utils │ │ ├── compileProps.ts │ │ ├── mergeCustomize.tsx │ │ ├── mergeProviderCustomize.ts │ │ ├── mergeRefs.ts │ │ └── typeHelper.ts ├── hooks │ ├── useCacheRender.ts │ ├── useGlobalClickAway.ts │ ├── usePagination.ts │ └── usePendingValue.tsx ├── icons │ ├── Icons.tsx │ └── assets │ │ ├── arrow-right.svg │ │ ├── calendar.svg │ │ ├── chevron-down.svg │ │ ├── chevron-left.svg │ │ ├── chevron-right.svg │ │ ├── chevron-up.svg │ │ ├── chevrons-down.svg │ │ ├── chevrons-left.svg │ │ ├── chevrons-right.svg │ │ ├── chevrons-up.svg │ │ ├── clock.svg │ │ ├── eye-off.svg │ │ ├── eye.svg │ │ ├── home.svg │ │ ├── loader.svg │ │ ├── search.svg │ │ └── spinner.svg ├── index.ts ├── styles │ ├── base.scss │ ├── components.scss │ ├── components │ │ ├── Accordion.scss │ │ ├── Breadcrumb.scss │ │ ├── Button.scss │ │ ├── Cascader.scss │ │ ├── Checkbox.scss │ │ ├── CheckboxGroup.scss │ │ ├── Collapse.scss │ │ ├── DateTime.scss │ │ ├── Dialog.scss │ │ ├── Drawer.scss │ │ ├── HTMLSelect.scss │ │ ├── Icon.scss │ │ ├── Label.scss │ │ ├── Layout.scss │ │ ├── ListBox.scss │ │ ├── Loading.scss │ │ ├── Menu.scss │ │ ├── NumberField.scss │ │ ├── Page.scss │ │ ├── Pagination.scss │ │ ├── Popover.scss │ │ ├── ProgressBar.scss │ │ ├── Radio.scss │ │ ├── RadioGroup.scss │ │ ├── RightClickZone.scss │ │ ├── SegmentControl.scss │ │ ├── Select.scss │ │ ├── Skeleton.scss │ │ ├── Slider.scss │ │ ├── Stepper.scss │ │ ├── Switch.scss │ │ ├── Table.scss │ │ ├── Tabs.scss │ │ ├── Tag.scss │ │ ├── TextArea.scss │ │ ├── TextField.scss │ │ ├── Toaster.scss │ │ └── Tooltip.scss │ ├── global.scss │ ├── index.scss │ ├── mixins.scss │ ├── normalize.scss │ └── variables.scss └── utils │ ├── ReactHelper.tsx │ ├── componentHelper.ts │ ├── createGroupedComponent.ts │ ├── createPropTypes.ts │ ├── dateFormatter.ts │ ├── keyboardHelper.tsx │ ├── moneyHelper.ts │ ├── numberHelper.ts │ ├── timeFormatter.ts │ └── useFocus.tsx ├── stories ├── assets │ ├── cookies.png │ ├── cookies.svg │ ├── donuts.png │ ├── donuts.svg │ ├── email.svg │ ├── hotdog.png │ ├── hotdog.svg │ ├── pudding.png │ ├── pudding.svg │ ├── strawberry.png │ ├── strawberry.svg │ ├── sushi.png │ └── sushi.svg ├── components │ ├── Accordion.stories.mdx │ ├── Breadcrumb.stories.mdx │ ├── Button.stories.mdx │ ├── Cascader.stories.mdx │ ├── Checkbox.stories.mdx │ ├── DateTime.stories.mdx │ ├── Dialog.stories.mdx │ ├── Drawer.stories.mdx │ ├── Form.stories.mdx │ ├── Form.stories.tsx │ ├── Icon.stories.mdx │ ├── Icon.stories.tsx │ ├── Input.stories.mdx │ ├── Label.stories.mdx │ ├── Layout.stories.mdx │ ├── Layout.stories.tsx │ ├── ListBox.stories.mdx │ ├── Loading.stories.mdx │ ├── Menu.stories.mdx │ ├── Overview.stories.mdx │ ├── Page.stories.mdx │ ├── Pagination.stories.mdx │ ├── Popover.stories.mdx │ ├── ProgressBar.stories.mdx │ ├── Radio.stories.mdx │ ├── RightClickZone.stories.mdx │ ├── SegmentControl.stories.mdx │ ├── Select.stories.mdx │ ├── Skeleton.stories.mdx │ ├── Slider.stories.mdx │ ├── Stepper.stories.mdx │ ├── Switch.stories.mdx │ ├── Table.stories.mdx │ ├── Tabs.stories.mdx │ ├── Tag.stories.mdx │ ├── Toast.stories.mdx │ ├── Toast.stories.tsx │ └── Tooltip.stories.mdx ├── demo │ ├── ComponentsCombination.stories.mdx │ └── ComponentsCombination.stories.tsx ├── dev │ ├── Contributing.stories.mdx │ ├── Deployment.stories.mdx │ ├── DevTips.stories.mdx │ └── Principle.stories.mdx ├── docs │ ├── Changelog.stories.mdx │ ├── GettingStarted.stories.mdx │ ├── Introduction.stories.mdx │ ├── UsingCustomize.stories.mdx │ └── Welcome.stories.mdx ├── style │ ├── CustomizeDemoStepper.scss │ ├── storybook.scss │ ├── tailwind.css │ └── uui.scss └── utils │ ├── ComponentComparator.tsx │ └── PreviewBox.tsx ├── tests ├── UUIProvider.test.tsx ├── __snapshots__ │ └── UUIProvider.test.tsx.snap ├── components │ └── .gitkeep └── core │ ├── UUIComponent.test.tsx │ ├── UUIComponentProxy.test.tsx │ └── __snapshots__ │ ├── UUIComponent.test.tsx.snap │ └── UUIComponentProxy.test.tsx.snap ├── tsconfig.json ├── typing └── svgr.d.ts └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | stories 2 | *.config.js 3 | .storybook 4 | tests 5 | jest.config.js -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:react/recommended", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended" 11 | ], 12 | "globals": { 13 | "Atomics": "readonly", 14 | "SharedArrayBuffer": "readonly" 15 | }, 16 | "parser": "@typescript-eslint/parser", 17 | "parserOptions": { 18 | "ecmaFeatures": { 19 | "jsx": true 20 | }, 21 | "ecmaVersion": 2018, 22 | "sourceType": "module" 23 | }, 24 | "plugins": [ 25 | "react", 26 | "@typescript-eslint", 27 | "react-hooks" 28 | ], 29 | "rules": { 30 | "@typescript-eslint/interface-name-prefix": "off", 31 | "@typescript-eslint/no-use-before-define": "off", 32 | "@typescript-eslint/ban-types": "off", 33 | "@typescript-eslint/explicit-module-boundary-types": "off", 34 | "@typescript-eslint/explicit-function-return-type": "off", 35 | "@typescript-eslint/no-explicit-any": "off", 36 | "@typescript-eslint/no-empty-interface": "off", 37 | "react-hooks/rules-of-hooks": "error", 38 | "react-hooks/exhaustive-deps": "warn", 39 | "react/display-name": "off" 40 | }, 41 | "settings": { 42 | "react": { 43 | "version": "detect" 44 | } 45 | } 46 | }; -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | day: "monday" 13 | allow: 14 | - dependency-type: "production" 15 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - 'v*' 8 | 9 | jobs: 10 | deploy_storybook: 11 | runs-on: ubuntu-latest 12 | steps: 13 | 14 | - uses: actions/setup-node@v1 15 | with: 16 | node-version: 12.x 17 | 18 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 19 | - uses: actions/checkout@v2 20 | 21 | # yarn install 22 | - name: Install 23 | run: yarn install 24 | 25 | # build storybook 26 | - name: Build 27 | run: yarn build:storybook 28 | 29 | # Deployment 30 | - name: Vercel Deployment 31 | uses: amondnet/vercel-action@v19 32 | with: 33 | vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required 34 | vercel-args: '--prod' #Optional 35 | vercel-org-id: ${{ secrets.ORG_ID}} #Required 36 | vercel-project-id: ${{ secrets.PROJECT_ID}} #Required 37 | working-directory: ./storybook-static 38 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: '14' 18 | cache: 'yarn' 19 | node-version-file: '.nvmrc' 20 | 21 | # yarn install 22 | - name: Install 23 | run: yarn install 24 | 25 | # build typescript 26 | - name: Build 27 | run: yarn build 28 | 29 | lint: 30 | if: ${{ github.event_name == 'pull_request' }} 31 | runs-on: ubuntu-latest 32 | steps: 33 | 34 | - uses: actions/checkout@v3 35 | - uses: actions/setup-node@v3 36 | with: 37 | node-version: '14' 38 | cache: 'yarn' 39 | node-version-file: '.nvmrc' 40 | 41 | # yarn install 42 | - name: Install 43 | run: yarn install 44 | 45 | # run eslint 46 | - name: Lint 47 | uses: tinovyatkin/action-eslint@master 48 | with: 49 | repo-token: ${{secrets.GITHUB_TOKEN}} 50 | check-name: eslint 51 | 52 | test: 53 | permissions: 54 | issues: write 55 | checks: write 56 | pull-requests: write 57 | if: ${{ github.event_name == 'pull_request' }} 58 | runs-on: ubuntu-latest 59 | steps: 60 | 61 | - uses: actions/checkout@v3 62 | - uses: actions/setup-node@v3 63 | with: 64 | node-version: '14' 65 | cache: 'yarn' 66 | node-version-file: '.nvmrc' 67 | 68 | - uses: ArtiomTr/jest-coverage-report-action@v2 69 | with: 70 | package-manager: yarn -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # project 4 | lib 5 | storybook-static 6 | .netlify 7 | 8 | #npm 9 | .npmrc 10 | hackplan-uui-*.tgz 11 | package 12 | 13 | #vs code 14 | .vscode 15 | 16 | #idea 17 | .idea/ 18 | 19 | # dependencies 20 | node_modules 21 | /.pnp 22 | .pnp.js 23 | 24 | # testing 25 | /coverage 26 | 27 | # production 28 | /build 29 | 30 | # misc 31 | .DS_Store 32 | .env.local 33 | .env.development.local 34 | .env.test.local 35 | .env.production.local 36 | 37 | npm-debug.log* 38 | yarn-debug.log* 39 | yarn-error.log* 40 | 41 | .now 42 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v12.13.0 -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: ['../stories/**/*.stories.@(tsx|mdx)'], 3 | addons: [ 4 | { 5 | name: '@storybook/addon-storysource', 6 | options: { 7 | loaderOptions: { 8 | parser: 'typescript', 9 | prettierConfig: { 10 | tabWidth: 2, 11 | singleQuote: false, 12 | }, 13 | }, 14 | }, 15 | }, 16 | '@storybook/addon-docs', 17 | '@storybook/addon-controls', 18 | '@storybook/addon-a11y', 19 | '@storybook/addon-viewport', 20 | '@storybook/addon-backgrounds', 21 | ], 22 | // typescript: { 23 | // check: false, 24 | // checkOptions: {}, 25 | // reactDocgen: 'react-docgen-typescript', 26 | // reactDocgenTypescriptOptions: { 27 | // propFilter: (prop) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true), 28 | // }, 29 | // }, 30 | webpackFinal: (config) => { 31 | 32 | // =================== 33 | /** 34 | * modify storybook default config 35 | * remove svg default file-loader 36 | * use both @svgr/webpack and file-loader 37 | */ 38 | const fileLoaderRule = config.module.rules.find(rule => { 39 | try { 40 | if (rule.test.test('.svg')) { 41 | return true 42 | } 43 | } catch (error) { 44 | } 45 | return false 46 | }); 47 | fileLoaderRule.test = /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/ 48 | config.module.rules.push({ 49 | test: /\.svg$/, 50 | use: [{ 51 | loader: '@svgr/webpack', 52 | options: { 53 | svgoConfig: { 54 | plugins: { 55 | removeViewBox: false 56 | } 57 | }, 58 | }, 59 | }, { 60 | loader: 'file-loader', 61 | options: { name: 'icons/assets/[name].[hash:8].[ext]', esModule: false }, 62 | }], 63 | }) 64 | // =================== 65 | 66 | return config 67 | } 68 | } -------------------------------------------------------------------------------- /.storybook/manager.js: -------------------------------------------------------------------------------- 1 | import { addons } from '@storybook/addons'; 2 | import { create } from '@storybook/theming/create'; 3 | 4 | const uuiPackage = require('../package.json') 5 | 6 | const theme = create({ 7 | base: 'light', 8 | 9 | brandTitle: `UUI (v${uuiPackage.version})`, 10 | }); 11 | 12 | addons.setConfig({ 13 | panelPosition: 'bottom', 14 | theme, 15 | showRoots: true, 16 | }); -------------------------------------------------------------------------------- /.storybook/postcss.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | plugins: [ 5 | require('postcss-import'), 6 | require('tailwindcss')(path.resolve(__dirname, 'tailwind.config.js')), 7 | ] 8 | } -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import { addParameters } from '@storybook/react'; 2 | 3 | import '../stories/style/uui.scss'; 4 | import '../stories/style/tailwind.css'; 5 | import '../stories/style/storybook.scss'; 6 | 7 | addParameters({ 8 | controls: { 9 | hideNoControlsWarning: true, 10 | }, 11 | }) 12 | 13 | addParameters({ 14 | options: { 15 | storySort: (a, b) => { 16 | const kindIndex = [ 17 | "文档", 18 | "组件", 19 | "演示", 20 | "开发", 21 | ] 22 | 23 | const docsIndex = [ 24 | "文档/主页 Welcome", 25 | "文档/介绍 Introduction", 26 | "文档/快速上手 Getting Started", 27 | "文档/使用自定义功能 Using Customize", 28 | "文档/更新日志 Changelog", 29 | ] 30 | 31 | const devIndex = [ 32 | "开发/实现原理 Principle", 33 | "开发/开发注意事项 Dev Tips", 34 | "开发/生产部署指南 Deployment", 35 | "开发/贡献指南 Contributing", 36 | ] 37 | 38 | const sortByAlphabet = (x, y) => { 39 | /** 40 | * 组件类 Story 按照英文字母表排序 41 | */ 42 | if (x.startsWith('总览')) return false 43 | if (y.startsWith('总览')) return true 44 | return x.replace(/[\u4e00-\u9fa5]/g, '') > y.replace(/[\u4e00-\u9fa5]/g, '') 45 | } 46 | 47 | const sortByIndexArray = (x, y, indexArr) => { 48 | return indexArr.findIndex((i) => i === x) > indexArr.findIndex((i) => i === y) 49 | } 50 | 51 | const apath = a[1]['kind'] 52 | const bpath = b[1]['kind'] 53 | const [akind, atitle] = apath.split('/') 54 | const [bkind, btitle] = bpath.split('/') 55 | 56 | if (akind === bkind) { 57 | if (akind == "文档") { 58 | return sortByIndexArray(apath, bpath, docsIndex) 59 | } else if (akind === "组件") { 60 | return sortByAlphabet(atitle, btitle) 61 | } else if (akind === "开发") { 62 | return sortByIndexArray(apath, bpath, devIndex) 63 | } 64 | } else { 65 | return sortByIndexArray(akind, bkind, kindIndex) 66 | } 67 | }, 68 | }, 69 | }); 70 | -------------------------------------------------------------------------------- /.storybook/tailwind.config.js: -------------------------------------------------------------------------------- 1 | // tailwind.config.js 2 | module.exports = { 3 | purge: false, 4 | important: true 5 | } -------------------------------------------------------------------------------- /.storybook/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "rootDirs": ["../src", "../stories"], 3 | "compilerOptions": { 4 | "baseUrl": "../src", 5 | "module": "es6", 6 | "target": "es5", 7 | "lib": ["es2015", "dom"], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "strict": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "moduleResolution": "node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": false, 17 | "jsx": "react", 18 | "declaration": false, 19 | "declarationMap": false, 20 | "sourceMap": true 21 | }, 22 | "include": [ 23 | "../src/**/*.ts", 24 | "../src/**/*.tsx", 25 | "../stories/**/*.tsx", 26 | "./typing/*.d.ts" 27 | ], 28 | "exclude": [ 29 | "../lib" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /.storybook/typing/svgr.d.ts: -------------------------------------------------------------------------------- 1 | interface SvgrComponent extends React.StatelessComponent> {} 2 | 3 | declare module '*.svg' { 4 | const ReactComponent: SvgrComponent; 5 | const url: string 6 | export default url; 7 | export { ReactComponent }; 8 | } -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = ({ config }) => { 4 | config.module.rules.push({ 5 | test: /\.(ts|tsx)$/, 6 | use: [ 7 | { 8 | loader: require.resolve('ts-loader'), 9 | options: { 10 | ignoreDiagnostics: [1005], 11 | configFile: path.resolve(__dirname, "./tsconfig.json"), 12 | } 13 | }, 14 | { 15 | loader: require.resolve('react-docgen-typescript-loader'), 16 | options: { 17 | // Provide the path to your tsconfig.json so that your stories can 18 | // display types from outside each individual story. 19 | tsconfigPath: path.resolve(__dirname, "./tsconfig.json"), 20 | }, 21 | }, 22 | ], 23 | }); 24 | config.module.rules.push({ 25 | test: /\.css$/, 26 | use: [ 27 | { 28 | loader: 'postcss-loader', 29 | options: { 30 | postcssOptions: { 31 | config: path.resolve(__dirname, 'postcss.config.js'), 32 | }, 33 | } 34 | } 35 | ], 36 | }) 37 | config.module.rules.push({ 38 | test: /\.s[ac]ss$/i, 39 | use: [ 40 | 'style-loader', 41 | 'css-loader', 42 | { 43 | loader: 'sass-loader', 44 | options: { 45 | // Prefer `dart-sass` 46 | implementation: require('sass'), 47 | sassOptions: { 48 | fiber: require('fibers'), 49 | }, 50 | } 51 | }, 52 | ], 53 | }) 54 | config.module.rules.push({ 55 | test: /\.less$/, 56 | use: [ 57 | { 58 | loader: 'style-loader', 59 | }, 60 | { 61 | loader: 'css-loader', 62 | }, 63 | { 64 | loader: 'less-loader', 65 | options: { 66 | javascriptEnabled: true, 67 | }, 68 | }, 69 | ], 70 | }) 71 | config.resolve.extensions.push('.ts', '.tsx'); 72 | return config; 73 | }; -------------------------------------------------------------------------------- /.svgrrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | svgoConfig: { 3 | plugins: { 4 | removeViewBox: false 5 | } 6 | } 7 | }; -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING 2 | 3 | English | [简体中文](https://github.com/HackPlan/UUI/blob/master/docs/CONTRIBUTING.zh-CN.md) 4 | 5 | Thanks for your contribution! 🎉 🎊 🥳 6 | 7 | ### Contributing topic 8 | 9 | Looking for places to contribute to the codebase? Check out the [issue list](https://github.com/HackPlan/UUI/issues). 10 | 11 | ## Development Document 12 | 13 | Most of the documents are placed in the [`docs`](https://github.com/HackPlan/UUI/tree/master/docs) directory. Before you start to modify or add more code, please take some time to read these documents. The documents not only explain how to use UUI, but also describe the design ideas and implementation principles of UUI. 14 | 15 | ## Bugs 16 | 17 | The UUI team mainly uses Github for software development and management, and uses Github Issues to track and manage bugs and features. 18 | 19 | If you find a bug of UUI, or want to have new features, please open a new Github Issue and describe the relevant details in detail. 20 | 21 | ## Pull request 22 | 23 | If you can submit a Pull Request, it is better than just submitting an Issue. We are very happy to accept some suitable Pull Requests from the community. Normally, if you are going to submit a Pull Request, before we accept these PRs, you need to do the following steps: 24 | 25 | 1. Open a Github issue and describe in detail the problem you want to solve or the new features you want to add. And fully discuss with UUI team or community contributors before proceeding with development. We can't just accept some PRs just to prevent your development time from wasting, all changes and new features should fit the UUI design ideas. So please be sure to discuss it in the Issue before taking the time to fix bugs or develop new features. 26 | 2. Fork UUI repository, clone your own repo locally. 27 | 3. Open a new branch in the local repo, the branch name is generally `feature / xxxxx` or `fix / xxxxxx` or `docs / xxxxxxx` (this is not mandatory) 28 | 4. Code! 29 | 5. After the development is completed, before submitting the PR, use the git rebase tool to organize the commit history. (We do not require that all commits be merged into a single commit, but we do not accept a large number of meaningless single small changes commits) 30 | 6. (If you modify the code in the `src/core` directory, you also need to ensure that the unit test passes) 31 | 7. Submit your PR and make sure that this PR is associated with the Issue created earlier. 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 HackPlan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/screenshots/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackPlan/UUI/31ddbb5da92748a2192f53c94b8f06282283a969/assets/screenshots/overview.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (api) => { 2 | const isTest = api.env('test'); 3 | if (isTest) { 4 | // special babel config for jest 5 | return { 6 | presets: [ 7 | [ 8 | "@babel/preset-env", 9 | { 10 | targets: { 11 | node: "current" 12 | } 13 | } 14 | ] 15 | ], 16 | plugins: ["transform-es2015-modules-commonjs"] 17 | }; 18 | } 19 | return {}; 20 | }; 21 | -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.zh-CN.md: -------------------------------------------------------------------------------- 1 | # 参与者公约 2 | 3 | ## 我们的承诺 4 | 5 | 为建设开放友好的环境,我们贡献者和维护者承诺:不论年龄、体型、身体健全与否、民族、性征、性别认同与表征、经验水平、教育程度、社会地位、国籍、相貌、种族、信仰、性取向,我们项目和社区的参与者皆免于骚扰。 6 | 7 | ## 我们的准则 8 | 9 | 有助于创造积极环境的行为包括但不限于: 10 | 11 | * 措辞友好且包容 12 | * 尊重不同的观点和经验 13 | * 耐心接受有益批评 14 | * 关注对社区最有利的事情 15 | * 与社区其他成员友善相处 16 | 17 | 参与者不应采取的行为包括但不限于: 18 | 19 | * 发布与性有关的言论或图像、不受欢迎地献殷勤 20 | * 捣乱/煽动/造谣行为、侮辱/贬损的评论、人身及政治攻击 21 | * 公开或私下骚扰 22 | * 未经明确授权便发布他人的资料,如住址、电子邮箱等 23 | * 其他有理由认定为违反职业操守的不当行为 24 | 25 | ## 我们的义务 26 | 27 | 项目维护者有义务诠释何谓“妥当行为”,并妥善公正地纠正已发生的不当行为。 28 | 29 | 项目维护者有权利和义务去删除、编辑、拒绝违背本行为标准的评论(comments)、提交(commits)、代码、wiki 编辑、问题(issues)等贡献;项目维护者可暂时或永久地封禁任何他们认为行为不当、威胁、冒犯、有害的参与者。 30 | 31 | ## 适用范围 32 | 33 | 本行为标准适用于本项目。当有人代表本项目或本社区时,本标准亦适用于此人所处的公共平台。 34 | 35 | 代表本项目或本社区的情形包括但不限于:使用项目的官方电子邮件、通过官方媒体账号发布消息、作为指定代表参与在线或线下活动等。 36 | 37 | 代表本项目的行为可由项目维护者进一步定义及解释。 38 | 39 | ## 贯彻落实 40 | 41 | 可以致信[在此输入EMAIL],向项目团队举报滥用、骚扰及不当行为。 42 | 43 | 维护团队将审议并调查全部投诉,妥善地予以必要的回应。项目团队有义务保密举报者信息。具体执行方针或将另行发布。 44 | 45 | 未切实遵守或执行本行为标准的项目维护人员,经项目负责人或其他成员决议,可能被暂时或永久地剥夺参与本项目的资格。 46 | 47 | ## 来源 48 | 49 | 本行为标准改编自[参与者公约][主页],版本 1.4 50 | 可在此查阅:https://www.contributor-covenant.org/zh-cn/version/1/4/code-of-conduct.html 51 | 52 | [主页]: https://www.contributor-covenant.org 53 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.zh-CN.md: -------------------------------------------------------------------------------- 1 | # 贡献指南 2 | 3 | [English](https://github.com/HackPlan/UUI/blob/master/docs/CONTRIBUTING.md) | 简体中文 4 | 5 | 感谢你的贡献! 🎉 🎊 🥳 6 | 7 | ## 开发文档 8 | 9 | 绝大部分文档都放在 [`docs`](https://github.com/HackPlan/UUI/tree/master/docs) 目录里。在开始修改、新增 UUI 代码之前,请花一些时间阅读这些文档,文档里不仅说明了如果使用 UUI,同时也解释描述了 UUI 的设计思路和实现原理。 10 | 11 | ## Bugs 12 | 13 | UUI 团队主要使用 Github 做软件开发管理,使用 Github Issue 用来追踪管理 Bugs 和功能需求。 14 | 15 | 如果你发现了 UUI 的 Bug,或者希望能有新的功能,请新开一个 Github Issue,并且详细描述一下相关细节。 16 | 17 | ## Pull Requests 18 | 19 | 如果你能提交 Pull Requests 那就比仅仅提出 Issue 更好了。我们非常高兴能接受一些合适的l来自社区的 Pull Request。通常情况下,如果你准备提交一个 Pull Request,在我们接受这些 PR 之前,你需要做以下这些步骤: 20 | 21 | 1. 开一个 Github Issue 并且详细描述你希望解决的问题或者你希望新增的功能需求。并且和 UUI 团队或者社区贡献者充分讨论,再进行开发。我们无法仅仅是为了不让你的开发时间白白浪费而随意的接受一些 PR,一切的改动和新增功能都应该契合 UUI 的设计思路。所以请在花时间修bug或者开发新功能之前务必在 Issue 里有所讨论。 22 | 2. Fork UUI 仓库,在本地 clone 自己的仓库。 23 | 3. 在本地仓库新开一个分支,分支名称一般为 `feature/xxxxx` 或者是 `fix/xxxxxx` 或者是 `docs/xxxxxxx`(这不是强制规定) 24 | 4. Code! 25 | 5. 开发完成之后,在开 PR 之前,通过 git rebase 工具整理 commit 历史。(我们不要求将所有commits合并为单个commit,但是我们也不接受大量无意义的单个小改动 commit) 26 | 6. (如果是修改了 `src/core` 目录里的代码,还需要保证单元测试通过) 27 | 7. 提交你的 PR,确保这个 PR 和之前创建的 Issue 关联在一起。 28 | -------------------------------------------------------------------------------- /docs/DEPLOYMENT.md: -------------------------------------------------------------------------------- 1 | # DEPLOYMENT 2 | 3 | English | [简体中文](https://github.com/HackPlan/UUI/blob/master/docs/DEPLOYMENT.zh-CN.md) 4 | 5 | (for maintainer) 6 | 7 | #### NPM Package 8 | 9 | 1. run `yarn pack:uui` command to generate a preview package file. 10 | 2. Check whether there are extra files that should not exist in the output package, if there are, they should be added to the `.npmignore` file. 11 | 3. run `yarn publish:uui` command, Make sure you enter the correct version number. 12 | 13 | #### Storybook 14 | 15 | (Now you can directly use the manual trigger button provided by [Github Actions](https://github.com/HackPlan/UUI/actions?query=workflow%3ADeploy) to trigger the deployment of Storybook) 16 | 17 | Push new tag to Github remote repo to trigger the deployment. 18 | 19 | Tag name should start with `sb`, recommended tag name format: 20 | 21 | ``` 22 | example: sb2020051301 23 | 24 | sb 2020 05 13 01 25 | prefix year mon day index 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/DEPLOYMENT.zh-CN.md: -------------------------------------------------------------------------------- 1 | # 生产部署指南 2 | 3 | [English](https://github.com/HackPlan/UUI/blob/master/docs/DEPLOYMENT.md) | 简体中文 4 | 5 | 写给项目维护者 6 | 7 | #### NPM 包 8 | 9 | 1. 运行 `yarn pack:uui` 命令来创建一个预览的 NPM 包文件; 10 | 2. 检查 NPM 包文件内是否有多余的文件被打包进去,如果有,应该在 `.npmignore` 文件中添加相应的文件名; 11 | 3. 运行 `yarn publish:uui` 命令,输入新的版本号,并仔细认真你输入了正确的版本号。 12 | 13 | #### Storybook 14 | 15 | (现在可以直接使用 [Github Actions](https://github.com/HackPlan/UUI/actions?query=workflow%3ADeploy)提供的手动触发按钮触发部署 Storybook) 16 | 17 | 为了触发 Storybook 生产部署,你需要在目标 commit 上创建一个新的标签,并推送到 Github 远程仓库。 18 | 19 | 标签名应该以 `sb` 作为开头,推荐的标签名格式: 20 | 21 | ``` 22 | example: sb2020051301 23 | 24 | sb 2020 05 13 01 25 | prefix year mon day index 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Development Practices 2 | 3 | ## Development workflow 4 | 5 | 1. Follow [README.md](https://github.com/HackPlan/UUI) instructions for setting up the dev environment. 6 | 2. 7 | 3. Create a local contributing branch off the latest `master` and switch to it. 8 | * Use the naming scheme `/` (example: sc/masked-textfield). This reduces potential conflicts and signifies a responsible party for the branch so that it can be deleted when stale. 9 | * If starting a collaborative or long-lived feature branch, We use a format like `feature/[short-name]` for new feature and `fix/[short-name]` for bug fix. 10 | 4. Run `yarn storybook` in the root project directory to start storybook dev app at `http://localhost:6006`. 11 | 5. Write some code, do some changes. 12 | 6. Update related documentation. 13 | 7. Ensure your code is **tested** and **linted**. 14 | * Linting is best handled by your editor for real-time feedback. Run yarn lint to be 100% safe. 15 | * Some TypeScript lint errors can often be automatically fixed by ESLint. Run lint fixes with `yarn lint --fix`. 16 | 8. Commit your code with a descriptive message. 17 | * If your change resolves an existing issue (usually, it should) include "Fixes #123" on a newline, where 123 is the issue number. 18 | 9. Push your contributing branch to your fork of the UUI repo. 19 | * If the `master` branch in local is out of dated, pull the latest changes and rebase your contributing branch to latest `master`. 20 | 10. Submit a Pull Request on Github. 21 | * fill out the description template. 22 | * If your change is visual (most UUI features are), include a screenshot or GIF demonstrating the change. 23 | 11. Get approval from the Blueprint team. 24 | * When addressing feedback, push additional commits instead of overwriting or squashing. 25 | * Be descriptive in your commit messages: prefer "fix style nits" to "address CR feedback" because the former provides context at a glance. 26 | * Our build bot and lint bot will run automatically on the PR. 27 | * Before merging, make the branch is based on latest `master`, you may need `git push --force-with-lease`. 28 | 12. Merged it! ✨ 29 | -------------------------------------------------------------------------------- /docs/GETTING_STARTED.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | ## Installing UUI 4 | 5 | 1. Install package ant its peer dependencies with an NPM client like `npm` or `yarn`, pulling in all relevant dependencies: 6 | 7 | ```bash 8 | yarn add @hackplan/uui react react-dom 9 | ``` 10 | 11 | 2. After installation, you will be able to import UUI components in your React web application: 12 | 13 | ```tsx 14 | import { Button } from '@hackplan/uui'; 15 | 16 | // using JSX 17 | const button1 = ; 18 | 19 | // using React.createElement 20 | const button2 = React.createElement(Button, { styling: { type: 'primary' } }, "Click me!"); 21 | ``` 22 | 23 | 3. **Don't forget to include css style file!** Besides, UUI package also provide Sass source style files, you can find it in `lib/styles`. 24 | 25 | ```tsx 26 | // using node-style package resolution in a CSS file 27 | @import '@hackplan/uui/lib/index.css'; 28 | ``` 29 | 30 | [![Edit uui-getting-started](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/uui-getting-started-3fg1o?fontsize=14&hidenavigation=1&theme=dark) 31 | 32 | 33 | ## TypeScript 34 | 35 | UUI is written in TypeScript and therefore its `*.d.ts` type definitions are bundled in the NPM package, should be used automatically by the compiler. However, you'll need to install typings for UUI's dependencies before you can consume it: 36 | 37 | ```bash 38 | yarn add @types/react @types/react-dom 39 | ``` -------------------------------------------------------------------------------- /docs/GETTING_STARTED.zh-CN.md: -------------------------------------------------------------------------------- 1 | # 快速上手 2 | 3 | ## 安装 UUI 4 | 5 | 1. 使用 NPM 客户端安装 UUI 包和它依赖的 peer dependencies,拉取所有相关的依赖: 6 | 7 | ```bash 8 | yarn add @hackplan/uui react react-dom 9 | ``` 10 | 11 | 2. 安装完成之后,就可以在你的网页应用中导入 UUI 组件了: 12 | 13 | ```tsx 14 | import { Button } from '@hackplan/uui'; 15 | 16 | // 使用 JSX 17 | const button1 = ; 18 | 19 | // 使用 React.createElement 20 | const button2 = React.createElement(Button, { styling: { type: 'primary' } }, "点击我!"); 21 | ``` 22 | 23 | 3. **不要忘记导入 CSS 样式文件!** 另外,UUI 的 NPM 包同时也提供了 Sass 的样式源码,你可以在 `lib/styles` 中找到它们。 24 | 25 | ```tsx 26 | // using node-style package resolution in a CSS file 27 | @import '@hackplan/uui/lib/index.css'; 28 | ``` 29 | 30 | [![Edit uui-getting-started](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/uui-getting-started-3fg1o?fontsize=14&hidenavigation=1&theme=dark) 31 | 32 | ## TypeScript 33 | 34 | UUI 是使用 TypeScript 开发实现的,因此它的 `*.d.ts` 类型定义文件都是打包进 UUI NPM 包的,会自动地被项目编译器使用。同时,在你使用 UUI 前,还需要安装 UUI 的依赖类型文件: 35 | 36 | ```bash 37 | yarn add @types/react @types/react-dom 38 | ``` -------------------------------------------------------------------------------- /docs/INTRODUCTION.zh-CN.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | ~~English~~ | 简体中文 4 | 5 | 在近几年的前端技术发展中,诞生出了很多优秀的 UI 库,例如 [Ant Design](https://ant.design/)、[Element UI](https://element.eleme.io/)、[Blueprint](https://blueprintjs.com/) 等等,这些优秀的开源项目提供了大量的 UI 组件和实用功能,给前端网页开发带来了巨量的效率提升。一些组件库还提出了一整套设计规范,包括不限于设计原则、样式风格、色彩选用等,这些规范限制能让组件更加美观一致,在网页项目里使用一整套 UI 库,也能让整个页面更加的协调一致。 6 | 7 | 这样的 UI 库非常适合用在后台管理页面上,对于管理使用者来说,更重要的是能稳定高效执行一些操作,对于开发者来说,更重要的是提供大量复杂的数据操作和展示的功能,而不是有更独特漂亮的页面样式和风格;但是对于产品的落地页、用户使用界面等这一类页面,每个产品都希望拥有自己特色风格的界面,这种情况下会面临的一个问题就是,这些有着高度设计风格主张的框架所提供的 UI 组件很难被修改它们样式。 8 | 9 | 对于这样的问题,我们([新小科技](https://xinxiao.tech/))决定尝试开发一套便于风格样式自定义的 UI 组件库 —— UUI。 10 | 11 | * 基于 React 的组件库,目前不考虑 Vue 版本; 12 | * 强大的组件样式自定义功能 —— 相比于市面上的其他 UI 库,UUI 更多的考虑如何让开发者更方便地定制组件样式; 13 | * UUI 功能和样式实现分离,UUI 内置了一套简约风格的样式,但是你也可以选择自己实现一套样式; 14 | * UUI 不主张特定的某一类设计风格(例如谷歌家的 Material Design、微软家的 Fluent Design 和阿里家的 Ant Design),如果愿意的话,你可以自己实现以上设计风格的样式; 15 | * 有一组开箱即用的组件; 16 | * 基于 TypeScript 的类型安全性; 17 | * 符合 WAI-ARIA 1.2 规范。 18 | 19 | 目前 **UUI 正处于频繁开发更新中**,接口和用法在将来可能会改变,目前暂时无法保证向后兼容。 20 | 21 | 我们也非常欢迎您加入到 UUI 的开发中,期待您的第一个 Pull Request。 -------------------------------------------------------------------------------- /docs/TESTING.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | English | [简体中文](https://github.com/HackPlan/UUI/blob/master/docs/TESTING.zh-CN.md) 4 | 5 | UUI unit testing is divided into two parts, core tools and components. 6 | 7 | UUI is currently in the early development stage, so we do not pursue 100% test coverage of components, but only do some tests on part of the work of some components. 8 | 9 | UUI components are built with HOC tool, so whenever these core HOC tool have some bugs or performance issues, almost all components will be affected. It is very necessary to write unit tests for these tool functions. 10 | 11 | We require 100% test coverage for this core part of the code. If you make changes to the files in the `core` folder, please run `yarn test --coverage` to ensure that the test passes and is 100% covered. -------------------------------------------------------------------------------- /docs/TESTING.zh-CN.md: -------------------------------------------------------------------------------- 1 | # 单元测试 2 | 3 | [English](https://github.com/HackPlan/UUI/blob/master/docs/TESTING.md) | 简体中文 4 | 5 | 6 | UUI 单元测试分为两个部分,核心工具和组件。 7 | 8 | 目前 UUI 处于早期开发阶段,所以我们不追求组件的 100% 测试覆盖,只对部分组件的部分工作做一些测试。 9 | 10 | UUI 组件都是通过这些工具函数实现功能的,所以每当这些核心的工具函数出了一些 bug 或者 performance issue,几乎会影响到所有的组件。给这些工具函数写单元测试是非常有必要的。 11 | 12 | 我们要求给这部分代码做 100% 测试覆盖。如果对 `core` 文件夹内部的文件做了改动,请跑一下 `yarn test --coverage` 保证测试通过并且百分百覆盖。 -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'jsdom', 4 | testMatch: [ 5 | '**/tests/**/*.test.ts', 6 | '**/tests/**/*.test.tsx', 7 | ], 8 | transform: { 9 | "^.+\\.js$": "babel-jest", 10 | "^.+\\.(ts|tsx)$": "ts-jest", 11 | "^.+\\.svg$": "jest-svg-transformer" 12 | }, 13 | transformIgnorePatterns: ["/node_modules/(?!(lodash-es|other-es-lib))"] 14 | }; -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | // rollup.config.js 2 | import typescript from 'rollup-plugin-typescript2'; 3 | import * as path from 'path'; 4 | import * as fs from 'fs'; 5 | import pkg from './package.json'; 6 | import sass from 'rollup-plugin-sass'; 7 | import copy from 'rollup-plugin-copy'; 8 | import commonjs from '@rollup/plugin-commonjs'; 9 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 10 | import svgr from '@svgr/rollup'; 11 | import url from '@rollup/plugin-url'; 12 | 13 | const config = [{ 14 | input: 'src/index.ts', 15 | output: [{ 16 | name: 'UUI', 17 | file: pkg.main, 18 | format: 'umd', 19 | plugins: [ 20 | ], 21 | globals: { 22 | 'react': 'React', 23 | 'react-dom': 'ReactDOM', 24 | 'react-popper': 'ReactPopper', 25 | 'prop-types': 'PropTypes', 26 | }, 27 | }, 28 | { 29 | name: 'UUI', 30 | file: pkg.module, 31 | format: 'es', 32 | plugins: [ 33 | ], 34 | }], 35 | external: [ 36 | "react", "react-dom", "prop-types", 37 | "@popperjs/core", "react-popper", 38 | ], 39 | plugins: [ 40 | url(), 41 | svgr(), 42 | nodeResolve({ 43 | browser: true, 44 | }), 45 | commonjs(), 46 | typescript({ 47 | tsconfig: path.join(__dirname, 'tsconfig.json'), 48 | typescript: require("typescript"), 49 | }), 50 | copy({ 51 | targets: [ 52 | { src: 'src/styles', dest: 'lib' }, 53 | ] 54 | }), 55 | ], 56 | }, { 57 | input: 'src/styles/index.scss', 58 | output: { 59 | file: 'lib/style.tmp.js', 60 | }, 61 | plugins: [ 62 | sass({ 63 | output: 'lib/index.css', 64 | runtime: require('sass'), 65 | options: { 66 | fiber: require('fibers'), 67 | } 68 | }), 69 | (() => { 70 | return { 71 | name: 'cleaner', 72 | writeBundle: (options, bundle) => { 73 | fs.unlinkSync(path.join(__dirname, './lib/style.tmp.js')) 74 | } 75 | } 76 | })(), 77 | ], 78 | }]; 79 | 80 | export default config; 81 | -------------------------------------------------------------------------------- /src/components/Accordion/AccordionContext.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export interface AccordionContextValue { 4 | expandedIds: Set; 5 | onPaneClicked: (index: string) => void; 6 | onNextPaneFocused?: () => void; 7 | onPrevPaneFocused?: () => void; 8 | onFirstPaneFocused?: () => void; 9 | onLastPaneFocused?: () => void; 10 | } 11 | 12 | export const AccordionContext = React.createContext(null) -------------------------------------------------------------------------------- /src/components/Accordion/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Accordion' 2 | export * from './AccordionPane' -------------------------------------------------------------------------------- /src/components/Breadcrumb/Breadcrumb.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import ReactHelper from '../../utils/ReactHelper'; 4 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 5 | 6 | export interface BreadcrumbItem { 7 | key: string; 8 | active?: boolean; 9 | label: React.ReactNode; 10 | path?: string; 11 | onAction?: () => void; 12 | } 13 | 14 | export interface BreadcrumbFeatureProps { 15 | separator?: React.ReactNode; 16 | items: BreadcrumbItem[]; 17 | } 18 | 19 | export const BreadcrumbPropTypes = createComponentPropTypes({ 20 | separator: PropTypes.node, 21 | items: PropTypes.arrayOf(PropTypes.shape({ 22 | key: PropTypes.string.isRequired, 23 | active: PropTypes.bool, 24 | label: PropTypes.node.isRequired, 25 | path: PropTypes.string, 26 | onAction: PropTypes.func, 27 | })).isRequired, 28 | }) 29 | 30 | export const Breadcrumb = UUIFunctionComponent({ 31 | name: 'Breadcrumb', 32 | nodes: { 33 | Root: 'nav', 34 | List: 'ol', 35 | Item: 'li', 36 | Separator: 'li', 37 | ItemLink: 'a', 38 | }, 39 | propTypes: BreadcrumbPropTypes, 40 | }, (props: BreadcrumbFeatureProps, { nodes, NodeDataProps }) => { 41 | const { Root, List, Item, ItemLink, Separator } = nodes 42 | 43 | const finalProps = { 44 | separator: props.separator || '/' 45 | } 46 | 47 | return ( 48 | 51 | 52 | {ReactHelper.join(props.items.map((i) => { 53 | const interactive = !!i.path || !!i.onAction 54 | return ( 55 | { 63 | if (i.onAction) i.onAction() 64 | }} 65 | > 66 | {!!i.path && !i.onAction ? ( 67 | {i.label} 68 | ) : i.label} 69 | 70 | ) 71 | }), {finalProps.separator})} 72 | 73 | 74 | ) 75 | }) 76 | 77 | export type BreadcrumbProps = UUIFunctionComponentProps 78 | -------------------------------------------------------------------------------- /src/components/Breadcrumb/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Breadcrumb'; -------------------------------------------------------------------------------- /src/components/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | import { omit } from 'lodash-es'; 2 | import React, { useRef } from 'react'; 3 | import { LoadingSpinner } from '../Loading'; 4 | import { KeyCode } from '../../utils/keyboardHelper'; 5 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 6 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 7 | 8 | export interface ButtonStylingProps { 9 | styling?: { 10 | type?: 'default' | 'primary' | 'text'; 11 | }; 12 | } 13 | 14 | export interface ButtonFeatureProps extends React.ButtonHTMLAttributes { 15 | /** 16 | * Whether the control is loading. 17 | * @default false 18 | */ 19 | loading?: boolean; 20 | } 21 | 22 | export const ButtonPropTypes = createComponentPropTypes & ButtonStylingProps>({ 23 | styling: PropTypes.shape({ 24 | type: PropTypes.oneOf(['default', 'primary', 'text']), 25 | }), 26 | loading: PropTypes.bool, 27 | }) 28 | 29 | export const Button = UUIFunctionComponent({ 30 | name: 'Button', 31 | nodes: { 32 | Root: 'button', 33 | LoadingSpinner: LoadingSpinner, 34 | Content: 'div', 35 | }, 36 | propTypes: ButtonPropTypes, 37 | }, (props: ButtonFeatureProps & ButtonStylingProps, { nodes, NodeDataProps }) => { 38 | const { Root, LoadingSpinner, Content } = nodes 39 | 40 | const ref = useRef(null) 41 | 42 | return ( 43 | { 52 | switch (event.keyCode) { 53 | case KeyCode.Enter: 54 | case KeyCode.SpaceBar: 55 | ref.current?.click() 56 | break 57 | default: 58 | // do nothing 59 | } 60 | }} 61 | > 62 | {props.loading ? : null} 63 | {props.children ? {props.children} : null} 64 | 65 | ) 66 | }) 67 | 68 | export type ButtonProps = UUIFunctionComponentProps 69 | -------------------------------------------------------------------------------- /src/components/Button/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Button' -------------------------------------------------------------------------------- /src/components/Cascader/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Cascader'; -------------------------------------------------------------------------------- /src/components/Checkbox/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Checkbox' 2 | export * from './CheckboxGroup' -------------------------------------------------------------------------------- /src/components/Collapse/Collapse.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 4 | 5 | export interface CollapseFeatureProps { 6 | opened: boolean; 7 | children: React.ReactNode; 8 | } 9 | 10 | export const CollapsePropTypes = createComponentPropTypes({ 11 | opened: PropTypes.bool.isRequired, 12 | children: PropTypes.node.isRequired, 13 | }) 14 | 15 | export const Collapse = UUIFunctionComponent({ 16 | name: 'Collapse', 17 | nodes: { 18 | Root: 'div', 19 | }, 20 | propTypes: CollapsePropTypes, 21 | }, (props: CollapseFeatureProps, { nodes, NodeDataProps }) => { 22 | const { Root } = nodes 23 | 24 | return ( 25 | 30 | {props.children} 31 | 32 | ) 33 | }) 34 | 35 | export type CollapseProps = UUIFunctionComponentProps 36 | -------------------------------------------------------------------------------- /src/components/Collapse/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Collapse' -------------------------------------------------------------------------------- /src/components/DateTime/PickerButtons.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 4 | import { Button as UUIButton } from '../Button/Button'; 5 | 6 | export interface PickerButtonsFeatureProps { 7 | confirmLabel?: React.ReactNode; 8 | cancelLabel?: React.ReactNode; 9 | onConfirm?: () => void; 10 | onCancel?: () => void; 11 | } 12 | 13 | export const PickerButtonsPropTypes = createComponentPropTypes({ 14 | confirmLabel: PropTypes.node, 15 | cancelLabel: PropTypes.node, 16 | onConfirm: PropTypes.func, 17 | onCancel: PropTypes.func, 18 | }) 19 | 20 | export const PickerButtons = UUIFunctionComponent({ 21 | name: 'PickerButtons', 22 | nodes: { 23 | Root: 'div', 24 | ConfirmButton: UUIButton, 25 | CancelButton: UUIButton, 26 | }, 27 | propTypes: PickerButtonsPropTypes, 28 | }, (props: PickerButtonsFeatureProps, { nodes }) => { 29 | const { Root, ConfirmButton, CancelButton } = nodes 30 | 31 | return ( 32 | 33 | {props.cancelLabel || "Cancel"} 34 | {props.confirmLabel || "Confirm"} 35 | 36 | ) 37 | }) 38 | 39 | export type PickerButtonsProps = UUIFunctionComponentProps 40 | -------------------------------------------------------------------------------- /src/components/DateTime/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './DatePicker'; 2 | export * from './DateRangePicker'; 3 | export * from './TimePicker'; 4 | export * from './TimeRangePicker'; 5 | export * from './DateTimePicker'; 6 | export * from './DateTimeRangePicker'; -------------------------------------------------------------------------------- /src/components/DateTime/utils/DateTimeDefaults.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_DATE_FORMAT_TOKEN = "yyyy-MM-dd" 2 | export const DEFAULT_TIME_FORMAT_TOKEN = "HH:mm:ss" 3 | export const DEFAULT_DATETIME_FORMAT_TOKEN = `${DEFAULT_DATE_FORMAT_TOKEN} ${DEFAULT_TIME_FORMAT_TOKEN}` 4 | -------------------------------------------------------------------------------- /src/components/DateTime/utils/DateTimeUtils.ts: -------------------------------------------------------------------------------- 1 | import { format, parse, set } from "date-fns"; 2 | import { DEFAULT_DATETIME_FORMAT_TOKEN } from "./DateTimeDefaults"; 3 | 4 | export function formatDateTime(date: Date | null) { 5 | return date === null ? '' : format(date, 'yyyy-MM-dd HH:mm:ss') 6 | } 7 | export function tryParseDateTimeFromString(dateString: string) { 8 | const result = parse(dateString, DEFAULT_DATETIME_FORMAT_TOKEN, getZeroDate()) 9 | if (isNaN(result.getTime())) throw new Error('date_string_parse_failed'); 10 | return result 11 | } 12 | export function getZeroDate() { 13 | return set(new Date(0), { hours: 0 }) 14 | } -------------------------------------------------------------------------------- /src/components/DateTime/utils/DateUtils.ts: -------------------------------------------------------------------------------- 1 | import { format, parse } from "date-fns" 2 | import { getZeroDate } from "./DateTimeUtils"; 3 | import { DEFAULT_DATE_FORMAT_TOKEN } from "./DateTimeDefaults"; 4 | 5 | 6 | export function formatDate(date: Date | null) { 7 | return date === null ? '' : format(date, 'yyyy-MM-dd') 8 | } 9 | export function tryParseDateFromString(dateString: string) { 10 | const result = parse(dateString, DEFAULT_DATE_FORMAT_TOKEN, getZeroDate()) 11 | if (isNaN(result.getTime())) throw new Error('date_string_parse_failed'); 12 | return result 13 | } -------------------------------------------------------------------------------- /src/components/DateTime/utils/TimeUtils.ts: -------------------------------------------------------------------------------- 1 | import { parse, format } from "date-fns" 2 | import { getZeroDate } from "./DateTimeUtils" 3 | import { DEFAULT_TIME_FORMAT_TOKEN } from "./DateTimeDefaults" 4 | 5 | 6 | export function formatTime(date: Date | null) { 7 | if (date === null) return '' 8 | return format(date, DEFAULT_TIME_FORMAT_TOKEN) 9 | } 10 | export function tryParseTimeFromString(dateString: string) { 11 | const result = parse(dateString, DEFAULT_TIME_FORMAT_TOKEN, getZeroDate()) 12 | if (isNaN(result.getTime())) throw new Error('time_string_parse_failed'); 13 | return result 14 | } -------------------------------------------------------------------------------- /src/components/Dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Dialog' 2 | export * from './AppDialog' -------------------------------------------------------------------------------- /src/components/Drawer/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Drawer'; -------------------------------------------------------------------------------- /src/components/Icon/IconGallery.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Icon, IconProps, IconImageFeatureProps, IconSvgFeatureProps, IconAnyFeatureProps, IconFeatureProps } from "./Icon" 3 | import { mapValues, isEqual } from "lodash-es" 4 | 5 | export type LoadedIcon = (props: Omit & Omit) => JSX.Element 6 | 7 | type LoadedIconByMode = 8 | T extends 'image' ? LoadedIcon : ( 9 | T extends 'svg' ? LoadedIcon : ( 10 | T extends 'any' ? LoadedIcon : undefined 11 | )) 12 | 13 | 14 | export type IconGalleryOptions = Partial> & { mode?: M } 15 | export function IconGallery< 16 | N extends string, 17 | P extends { 18 | [key in N]: Partial> & Pick 19 | }, 20 | M extends IconProps['mode'] | undefined, 21 | O extends IconGalleryOptions, 22 | V extends { 23 | [key in keyof P]: LoadedIconByMode extends undefined 24 | ? (O extends undefined 25 | ? never 26 | : (LoadedIconByMode extends undefined ? never : LoadedIconByMode) 27 | ) 28 | : LoadedIconByMode 29 | } 30 | >(initials: P, options?: O): V { 31 | return mapValues(initials, (value) => { 32 | const MemoIcon = React.memo((props: IconProps) => { 33 | return 34 | }, isEqual); 35 | (MemoIcon.type as React.SFC).displayName = ` IconGallery WrappedIcon`; 36 | return MemoIcon; 37 | }) as any 38 | } 39 | -------------------------------------------------------------------------------- /src/components/Icon/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Icon'; 2 | export * from './IconGallery'; -------------------------------------------------------------------------------- /src/components/Input/index.ts: -------------------------------------------------------------------------------- 1 | export * from './TextField'; 2 | export * from './NumberField'; 3 | export * from './TextArea'; -------------------------------------------------------------------------------- /src/components/Label/CountdownLabel.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback } from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { useInterval } from 'react-use'; 4 | import { format, differenceInMilliseconds } from 'date-fns'; 5 | import { addHours, addMilliseconds } from 'date-fns/esm'; 6 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 7 | 8 | export interface CountdownLabelFeatureProps { 9 | /** 10 | * Time in countdown. 11 | */ 12 | until: Date; 13 | /** 14 | * Luxon date format. 15 | * https://moment.github.io/luxon/docs/manual/formatting.html#table-of-tokens 16 | * @default hh:mm:ss 17 | */ 18 | format?: string; 19 | /** 20 | * Frequency of update text. (milliseconds) 21 | * @default 1000 22 | */ 23 | frequency?: number; 24 | /** 25 | * Whether the time could be a negative value. 26 | * @default false 27 | */ 28 | allowNegative?: boolean; 29 | } 30 | 31 | export const CountdownLabelPropTypes = createComponentPropTypes({ 32 | until: PropTypes.instanceOf(Date), 33 | format: PropTypes.string, 34 | frequency: PropTypes.number, 35 | allowNegative: PropTypes.bool, 36 | }) 37 | 38 | export const CountdownLabel = UUIFunctionComponent({ 39 | name: 'CountdownLabel', 40 | nodes: { 41 | Root: 'label', 42 | }, 43 | propTypes: CountdownLabelPropTypes, 44 | }, (props: CountdownLabelFeatureProps, { nodes }) => { 45 | const { Root } = nodes 46 | 47 | const finalProps = { 48 | frequency: props.frequency || 1000, 49 | format: props.format || 'hh:mm:ss', 50 | allowNegative: props.allowNegative || false, 51 | } 52 | 53 | const generateLabelText = useCallback(() => { 54 | const diff = differenceInMilliseconds(props.until, new Date()) 55 | const duration = (!props.allowNegative && diff && diff < 0) 56 | ? new Date(0) 57 | : addMilliseconds(addHours(new Date(0), 16), diff) 58 | return format(duration, finalProps.format) 59 | }, [finalProps.format, props.allowNegative, props.until]) 60 | 61 | const [text, setText] = useState(generateLabelText()) 62 | useInterval(() => { 63 | setText(generateLabelText()) 64 | }, finalProps.frequency) 65 | 66 | return ( 67 | {text} 68 | ) 69 | }) 70 | 71 | 72 | 73 | export type CountdownLabelProps = UUIFunctionComponentProps 74 | -------------------------------------------------------------------------------- /src/components/Label/DateLabel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { DateFormatterLocale, DateFormatterLocaleKinds, dateFormat } from '../../utils/dateFormatter'; 3 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 4 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 5 | 6 | export interface DateLabelFeatureProps { 7 | /** 8 | * Date value to be displayed. 9 | * 10 | * Only the part of date (year, month, day, week) is valid for this Component 11 | */ 12 | value: Date; 13 | /** 14 | * The locale of display format. 15 | */ 16 | locale: T; 17 | /** 18 | * The display format of date. 19 | * 20 | * DateLabel provides a group of presets of date format, 21 | * These presets refer to the formats in Excel. 22 | */ 23 | kind: DateFormatterLocaleKinds[T][number]; 24 | } 25 | 26 | export const DateLabelPropTypes = createComponentPropTypes>({ 27 | value: PropTypes.instanceOf(Date).isRequired, 28 | locale: PropTypes.string.isRequired, 29 | kind: PropTypes.string.isRequired, 30 | }) 31 | 32 | export const DateLabel = UUIFunctionComponent({ 33 | name: 'DateLabel', 34 | nodes: { 35 | Root: 'label', 36 | }, 37 | propTypes: DateLabelPropTypes, 38 | }, (props: DateLabelFeatureProps, { nodes }) => { 39 | const { Root } = nodes 40 | 41 | const text = dateFormat(props.value, props.locale, props.kind) 42 | return ( 43 | {text} 44 | ) 45 | }) 46 | 47 | export type DateLabelProps = UUIFunctionComponentProps -------------------------------------------------------------------------------- /src/components/Label/MoneyLabel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { formatMoney } from '../../utils/moneyHelper'; 4 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 5 | 6 | export interface MoneyLabelFeatureProps { 7 | /** 8 | * Money value to be displayed. 9 | */ 10 | value: number; 11 | /** 12 | * The symbopl of currency 13 | * @default $ 14 | */ 15 | symbol?: string; 16 | /** 17 | * The maximum number of decimals. 18 | */ 19 | precision?: number; 20 | /** 21 | * Thousands separator 22 | * @default , 23 | */ 24 | thousand?: string; 25 | /** 26 | * Decimal separator 27 | * @default . 28 | */ 29 | decimal?: string; 30 | } 31 | 32 | export const MoneyLabelPropTypes = createComponentPropTypes({ 33 | value: PropTypes.number.isRequired, 34 | symbol: PropTypes.string, 35 | precision: PropTypes.number, 36 | thousand: PropTypes.string, 37 | decimal: PropTypes.string, 38 | }) 39 | 40 | export const MoneyLabel = UUIFunctionComponent({ 41 | name: 'MoneyLabel', 42 | nodes: { 43 | Root: 'label', 44 | }, 45 | propTypes: MoneyLabelPropTypes, 46 | }, (props: MoneyLabelFeatureProps, { nodes }) => { 47 | const { Root } = nodes 48 | 49 | const finalProps = { 50 | symbol: props.symbol || '$', 51 | thousand: props.thousand || ',', 52 | decimal: props.decimal || '.', 53 | precision: props.precision === undefined ? 2 : props.precision, 54 | } 55 | 56 | const text = formatMoney(props.value, finalProps) 57 | 58 | return ( 59 | {text} 60 | ) 61 | }) 62 | 63 | export type MoneyLabelProps = UUIFunctionComponentProps 64 | -------------------------------------------------------------------------------- /src/components/Label/NumberAbbrLabel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { formatMoney } from '../../utils/moneyHelper'; 4 | import { numberAbbr, NumberAbbrUnit, NumberAbbrUnitValue } from '../../utils/numberHelper'; 5 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 6 | 7 | 8 | export interface NumberAbbrLabelFeatureProps { 9 | /** 10 | * number value to be displayed. 11 | * 12 | */ 13 | value: number; 14 | unit: NumberAbbrUnit; 15 | /** 16 | * The maximum number of decimals. 17 | * @default 2 18 | */ 19 | maxPrecision?: number; 20 | } 21 | 22 | export const NumberAbbrLabelPropTypes = createComponentPropTypes({ 23 | value: PropTypes.number.isRequired, 24 | unit: PropTypes.oneOf(Object.keys(NumberAbbrUnitValue)).isRequired, 25 | maxPrecision: PropTypes.number, 26 | }) 27 | 28 | export const NumberAbbrLabel = UUIFunctionComponent({ 29 | name: 'NumberAbbrLabel', 30 | nodes: { 31 | Root: 'abbr', 32 | }, 33 | propTypes: NumberAbbrLabelPropTypes, 34 | }, (props: NumberAbbrLabelFeatureProps, { nodes }) => { 35 | const { Root } = nodes 36 | 37 | const number = formatMoney(props.value, { symbol: '' }) 38 | const abbr = numberAbbr(props.value, props.unit, props.maxPrecision || 2) 39 | 40 | return ( 41 | {abbr}{props.unit} 42 | ) 43 | }) 44 | 45 | export type NumberAbbrLabelProps = UUIFunctionComponentProps -------------------------------------------------------------------------------- /src/components/Label/TimeLabel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { TimeFormatterLocale, TimeFormatterLocaleKinds, timeFormat } from '../../utils/timeFormatter'; 3 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 4 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 5 | 6 | export interface TimeLabelFeatureProps { 7 | /** 8 | * Date value to be displayed. 9 | * 10 | * Only the part of time (hour, minute, second) is valid for this Component 11 | */ 12 | value: Date; 13 | /** 14 | * The locale of display format. 15 | */ 16 | locale: T; 17 | /** 18 | * The display format of date. 19 | * 20 | * DateLabel provides a group of presets of time format, 21 | * These presets refer to the formats in Excel. 22 | */ 23 | kind: TimeFormatterLocaleKinds[T][number]; 24 | } 25 | 26 | export const TimeLabelPropTypes = createComponentPropTypes>({ 27 | value: PropTypes.instanceOf(Date).isRequired, 28 | locale: PropTypes.string.isRequired, 29 | kind: PropTypes.string.isRequired, 30 | }) 31 | 32 | export const TimeLabel = UUIFunctionComponent({ 33 | name: 'TimeLabel', 34 | nodes: { 35 | Root: 'div', 36 | } 37 | }, (props: TimeLabelFeatureProps, { nodes }) => { 38 | const { Root } = nodes 39 | 40 | const text = timeFormat(props.value, props.locale, props.kind) 41 | return ( 42 | {text} 43 | ) 44 | }) 45 | 46 | export type TimeLabelProps = UUIFunctionComponentProps -------------------------------------------------------------------------------- /src/components/Label/index.ts: -------------------------------------------------------------------------------- 1 | export * from './MoneyLabel'; 2 | export * from './DateLabel'; 3 | export * from './TimeLabel'; 4 | export * from './CountdownLabel'; 5 | export * from './NumberAbbrLabel'; -------------------------------------------------------------------------------- /src/components/Layout/Layout.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { LayoutMainProps, LayoutMain } from './LayoutMain'; 4 | import { LayoutFooterProps, LayoutFooter } from './LayoutFooter'; 5 | import { LayoutHeaderProps, LayoutHeader } from './LayoutHeader'; 6 | import { LayoutNavProps, LayoutNav } from './LayoutNav'; 7 | import { createGroupedComponent } from '../../utils/createGroupedComponent'; 8 | import { LayoutAside, LayoutAsideProps } from './LayoutAside'; 9 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 10 | 11 | export interface LayoutFeatureProps { 12 | /** 13 | * The content of tag. 14 | */ 15 | children?: 16 | ( 17 | | React.ReactElement 18 | | React.ReactElement 19 | | React.ReactElement 20 | | React.ReactElement 21 | | React.ReactElement 22 | )[]; 23 | } 24 | 25 | export const LayoutPropTypes = createComponentPropTypes({ 26 | children: PropTypes.node, 27 | }) 28 | 29 | export const _Layout = UUIFunctionComponent({ 30 | name: 'Layout', 31 | nodes: { 32 | Root: 'section', 33 | }, 34 | propTypes: LayoutPropTypes, 35 | }, (props: LayoutFeatureProps, { nodes, NodeDataProps }) => { 36 | const { Root } = nodes 37 | 38 | const { nav, header, footer } = useMemo(() => { 39 | return { 40 | nav: props.children && props.children.find((i: any) => i.type === LayoutNav) as React.ReactElement | undefined, 41 | header: props.children && props.children.find((i: any) => i.type === LayoutHeader) as React.ReactElement | undefined, 42 | footer: props.children && props.children.find((i: any) => i.type === LayoutFooter) as React.ReactElement | undefined, 43 | } 44 | }, [props.children]) 45 | 46 | return ( 47 | 54 | {props.children} 55 | 56 | ) 57 | }) 58 | 59 | export type LayoutProps = UUIFunctionComponentProps 60 | 61 | const Layout = createGroupedComponent(_Layout, { 62 | Nav: LayoutNav, 63 | Header: LayoutHeader, 64 | Main: LayoutMain, 65 | Footer: LayoutFooter, 66 | Aside: LayoutAside, 67 | }) 68 | 69 | export { Layout } 70 | -------------------------------------------------------------------------------- /src/components/Layout/LayoutAside.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 4 | 5 | export interface LayoutAsideFeatureProps { 6 | /** 7 | * The container of LayoutAside. 8 | */ 9 | children?: React.ReactNode; 10 | } 11 | 12 | export const LayoutAsidePropTypes = createComponentPropTypes({ 13 | children: PropTypes.node, 14 | }) 15 | 16 | export const LayoutAside = UUIFunctionComponent({ 17 | name: 'LayoutAside', 18 | nodes: { 19 | Root: 'aside', 20 | }, 21 | propTypes: LayoutAsidePropTypes, 22 | }, (props: LayoutAsideFeatureProps, { nodes }) => { 23 | const { Root } = nodes 24 | return ( 25 | 26 | {props.children} 27 | 28 | ) 29 | }) 30 | 31 | export type LayoutAsideProps = UUIFunctionComponentProps -------------------------------------------------------------------------------- /src/components/Layout/LayoutFooter.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 4 | 5 | export interface LayoutFooterFeatureProps { 6 | /** 7 | * The container of LayoutFooter. 8 | */ 9 | children?: React.ReactNode; 10 | } 11 | 12 | export const LayoutFooterPropTypes = createComponentPropTypes({ 13 | children: PropTypes.node, 14 | }) 15 | 16 | export const LayoutFooter = UUIFunctionComponent({ 17 | name: 'LayoutFooter', 18 | nodes: { 19 | Root: 'footer', 20 | }, 21 | propTypes: LayoutFooterPropTypes, 22 | }, (props: LayoutFooterFeatureProps, { nodes }) => { 23 | const { Root } = nodes 24 | return ( 25 | 26 | {props.children} 27 | 28 | ) 29 | }) 30 | 31 | export type LayoutFooterProps = UUIFunctionComponentProps -------------------------------------------------------------------------------- /src/components/Layout/LayoutHeader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 4 | 5 | export interface LayoutHeaderFeatureProps { 6 | /** 7 | * The container of LayoutHeader. 8 | */ 9 | children?: React.ReactNode; 10 | } 11 | 12 | export const LayoutHeaderPropTypes = createComponentPropTypes({ 13 | children: PropTypes.node, 14 | }) 15 | 16 | export const LayoutHeader = UUIFunctionComponent({ 17 | name: 'LayoutHeader', 18 | nodes: { 19 | Root: 'header', 20 | }, 21 | propTypes: LayoutHeaderPropTypes, 22 | }, (props: LayoutHeaderFeatureProps, { nodes }) => { 23 | const { Root } = nodes 24 | return ( 25 | 26 | {props.children} 27 | 28 | ) 29 | }) 30 | 31 | export type LayoutHeaderProps = UUIFunctionComponentProps -------------------------------------------------------------------------------- /src/components/Layout/LayoutMain.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 4 | 5 | export interface LayoutMainFeatureProps { 6 | /** 7 | * The content of tag. 8 | */ 9 | children?: React.ReactNode; 10 | } 11 | 12 | export const LayoutMainPropTypes = createComponentPropTypes({ 13 | children: PropTypes.node, 14 | }) 15 | 16 | export const LayoutMain = UUIFunctionComponent({ 17 | name: 'LayoutMain', 18 | nodes: { 19 | Root: 'main', 20 | }, 21 | propTypes: LayoutMainPropTypes, 22 | }, (props: LayoutMainFeatureProps, { nodes }) => { 23 | const { Root } = nodes 24 | return ( 25 | 26 | {props.children} 27 | 28 | ) 29 | }) 30 | 31 | export type LayoutMainProps = UUIFunctionComponentProps -------------------------------------------------------------------------------- /src/components/Layout/LayoutNav.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 4 | 5 | export interface LayoutNavFeatureProps { 6 | /** 7 | * The container of LayoutNav. 8 | */ 9 | children?: React.ReactNode; 10 | } 11 | 12 | export const LayoutNavPropTypes = createComponentPropTypes({ 13 | children: PropTypes.node, 14 | }) 15 | 16 | export const LayoutNav = UUIFunctionComponent({ 17 | name: 'LayoutNav', 18 | nodes: { 19 | Root: 'nav', 20 | }, 21 | propTypes: LayoutNavPropTypes, 22 | }, (props: LayoutNavFeatureProps, { nodes }) => { 23 | const { Root } = nodes 24 | return ( 25 | 26 | {props.children} 27 | 28 | ) 29 | }) 30 | 31 | export type LayoutNavProps = UUIFunctionComponentProps -------------------------------------------------------------------------------- /src/components/Layout/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Layout'; -------------------------------------------------------------------------------- /src/components/ListBox/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ListBox'; -------------------------------------------------------------------------------- /src/components/Loading/LoadingCover.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { LoadingSpinner } from './LoadingSpinner'; 4 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 5 | 6 | export interface LoadingCoverFeatureProps { 7 | loading?: boolean; 8 | label?: React.ReactNode; 9 | children: React.ReactNode; 10 | } 11 | 12 | export const LoadingCoverPropTypes = createComponentPropTypes({ 13 | loading: PropTypes.bool, 14 | label: PropTypes.node, 15 | children: PropTypes.node.isRequired, 16 | }) 17 | 18 | export const LoadingCover = UUIFunctionComponent({ 19 | name: 'LoadingCover', 20 | nodes: { 21 | Root: 'div', 22 | Mask: 'div', 23 | Spinner: LoadingSpinner, 24 | Label: 'div', 25 | }, 26 | propTypes: LoadingCoverPropTypes, 27 | }, (props: LoadingCoverFeatureProps, { nodes, NodeDataProps }) => { 28 | const { Root, Mask, Spinner, Label } = nodes 29 | 30 | return ( 31 | 36 | {props.children} 37 | 38 | 39 | {props.label && } 40 | 41 | 42 | ) 43 | }) 44 | 45 | export type LoadingCoverProps = UUIFunctionComponentProps 46 | -------------------------------------------------------------------------------- /src/components/Loading/LoadingSpinner.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { Icons } from '../../icons/Icons'; 4 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 5 | 6 | export type LoadingSpinnerFeatureProps = { 7 | width?: number; 8 | height?: number; 9 | animate?: boolean; 10 | } 11 | 12 | export const LoadingSpinnerPropTypes = createComponentPropTypes({ 13 | width: PropTypes.number, 14 | height: PropTypes.number, 15 | animate: PropTypes.bool, 16 | }) 17 | 18 | export const LoadingSpinner = UUIFunctionComponent({ 19 | name: 'LoadingSpinner', 20 | nodes: { 21 | Root: 'div', 22 | Icon: Icons.Spinner, 23 | }, 24 | propTypes: LoadingSpinnerPropTypes, 25 | }, (props: LoadingSpinnerFeatureProps, { nodes, NodeDataProps }) => { 26 | const { Root, Icon } = nodes 27 | 28 | const finalProps = { 29 | animate: props.animate === undefined || true, 30 | } 31 | 32 | return ( 33 | 38 | 39 | 40 | ) 41 | }) 42 | 43 | export type LoadingSpinnerProps = UUIFunctionComponentProps 44 | -------------------------------------------------------------------------------- /src/components/Loading/index.ts: -------------------------------------------------------------------------------- 1 | export * from './LoadingSpinner'; 2 | export * from './LoadingCover'; -------------------------------------------------------------------------------- /src/components/Menu/Menu.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { createGroupedComponent } from '../../utils/createGroupedComponent'; 4 | import { MenuItem } from './MenuItem'; 5 | import { MenuButton } from './MenuButton'; 6 | import { MenuSeparator } from './MenuSeparator'; 7 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 8 | 9 | export interface MenuFeatureProps { 10 | children: React.ReactNode; 11 | } 12 | 13 | export const MenuPropTypes = createComponentPropTypes({ 14 | children: PropTypes.node.isRequired, 15 | }) 16 | 17 | export const _Menu = UUIFunctionComponent({ 18 | name: 'Menu', 19 | nodes: { 20 | Root: 'div' 21 | }, 22 | propTypes: MenuPropTypes, 23 | }, (props: MenuFeatureProps, { nodes }) => { 24 | const { Root } = nodes 25 | 26 | return ( 27 | 28 | {props.children} 29 | 30 | ) 31 | }) 32 | 33 | export type MenuProps = UUIFunctionComponentProps 34 | 35 | const Menu = createGroupedComponent(_Menu, { 36 | Item: MenuItem, 37 | Button: MenuButton, 38 | Separator: MenuSeparator, 39 | }) 40 | 41 | export { Menu } 42 | -------------------------------------------------------------------------------- /src/components/Menu/MenuButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { Button as UUIButton, ButtonFeatureProps } from '../Button'; 4 | import { pick } from 'lodash-es'; 5 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 6 | 7 | export interface MenuButtonFeatureProps extends Pick< 8 | ButtonFeatureProps, 'children' | 'onClick' | 'loading' | 'disabled' 9 | > { 10 | disabled?: ButtonFeatureProps['disabled']; 11 | onClick?: ButtonFeatureProps['onClick']; 12 | } 13 | 14 | export const MenuButtonPropTypes = createComponentPropTypes({ 15 | disabled: PropTypes.bool, 16 | onClick: PropTypes.func, 17 | children: PropTypes.node, 18 | loading: PropTypes.bool, 19 | }) 20 | 21 | export const MenuButton = UUIFunctionComponent({ 22 | name: 'MenuButton', 23 | nodes: { 24 | Root: 'div', 25 | Button: UUIButton, 26 | }, 27 | propTypes: MenuButtonPropTypes, 28 | }, (props: MenuButtonFeatureProps, { nodes }) => { 29 | const { Root, Button } = nodes 30 | 31 | return ( 32 | 33 | 36 | 37 | ) 38 | }) 39 | 40 | export type MenuButtonProps = UUIFunctionComponentProps 41 | 42 | -------------------------------------------------------------------------------- /src/components/Menu/MenuItem.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 4 | 5 | export interface MenuItemFeatureProps { 6 | children: React.ReactNode; 7 | } 8 | 9 | export const MenuItemPropTypes = createComponentPropTypes({ 10 | children: PropTypes.node.isRequired, 11 | }) 12 | 13 | export const MenuItem = UUIFunctionComponent({ 14 | name: 'MenuItem', 15 | nodes: { 16 | Root: 'div', 17 | }, 18 | propTypes: MenuItemPropTypes, 19 | }, (props: MenuItemFeatureProps, { nodes }) => { 20 | const { Root } = nodes 21 | 22 | return ( 23 | 24 | {props.children} 25 | 26 | ) 27 | }) 28 | 29 | export type MenuItemProps = UUIFunctionComponentProps 30 | 31 | -------------------------------------------------------------------------------- /src/components/Menu/MenuSeparator.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { createComponentPropTypes } from '../../utils/createPropTypes'; 4 | 5 | export interface MenuSeparatorFeatureProps {} 6 | 7 | export const MenuSeparatorPropTypes = createComponentPropTypes({}) 8 | 9 | export const MenuSeparator = UUIFunctionComponent({ 10 | name: 'MenuSeparator', 11 | nodes: { 12 | Root: 'div', 13 | }, 14 | propTypes: MenuSeparatorPropTypes, 15 | }, (props: MenuSeparatorFeatureProps, { nodes }) => { 16 | const { Root } = nodes 17 | 18 | return ( 19 | 20 | ) 21 | }) 22 | 23 | export type MenuSeparatorProps = UUIFunctionComponentProps 24 | 25 | -------------------------------------------------------------------------------- /src/components/Menu/index.ts: -------------------------------------------------------------------------------- 1 | export { Menu } from './Menu'; 2 | export type { MenuFeatureProps, MenuProps } from './Menu'; -------------------------------------------------------------------------------- /src/components/Page/PageAnnotatedSection.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 4 | 5 | export interface PageAnnotatedSectionFeatureProps { 6 | title: string; 7 | description?: string; 8 | /** 9 | * The content of tag. 10 | */ 11 | children?: React.ReactNode; 12 | } 13 | 14 | export const PageAnnotatedSectionPropTypes = createComponentPropTypes({ 15 | title: PropTypes.string, 16 | description: PropTypes.string, 17 | children: PropTypes.node, 18 | }) 19 | 20 | export const PageAnnotatedSection = UUIFunctionComponent({ 21 | name: 'PageAnnotatedSection', 22 | nodes: { 23 | Root: 'div', 24 | InfoWrapper: 'div', 25 | Title: 'h2', 26 | Description: 'p', 27 | Container: 'div', 28 | }, 29 | propTypes: PageAnnotatedSectionPropTypes, 30 | }, (props: PageAnnotatedSectionFeatureProps, { nodes }) => { 31 | const { Root, InfoWrapper, Title, Description, Container } = nodes 32 | return ( 33 | 34 | 35 | {props.title} 36 | {props.description && {props.description}} 37 | 38 | 39 | {props.children} 40 | 41 | 42 | ) 43 | }) 44 | 45 | export type PageAnnotatedSectionProps = UUIFunctionComponentProps -------------------------------------------------------------------------------- /src/components/Page/PageSection.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 4 | 5 | export interface PageSectionFeatureProps { 6 | /** 7 | * The content of tag. 8 | */ 9 | children?: React.ReactNode; 10 | } 11 | 12 | export const PageSectionPropTypes = createComponentPropTypes({ 13 | children: PropTypes.node, 14 | }) 15 | 16 | export const PageSection = UUIFunctionComponent({ 17 | name: 'PageSection', 18 | nodes: { 19 | Root: 'div', 20 | }, 21 | propTypes: PageSectionPropTypes, 22 | }, (props: PageSectionFeatureProps, { nodes }) => { 23 | const { Root } = nodes 24 | return ( 25 | 26 | {props.children} 27 | 28 | ) 29 | }) 30 | 31 | export type PageSectionProps = UUIFunctionComponentProps -------------------------------------------------------------------------------- /src/components/Page/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Page'; 2 | 3 | -------------------------------------------------------------------------------- /src/components/Pagination/PageInfo.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { PaginationContext } from './PaginationContext'; 4 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 5 | 6 | export interface PageInfoFeatureProps { 7 | /** 8 | * custom info display 9 | */ 10 | onRender?: (startItem: number, endItem: number, totalItem: number) => React.ReactNode; 11 | } 12 | 13 | export const PageInfoPropTypes = createComponentPropTypes({ 14 | onRender: PropTypes.func, 15 | }) 16 | 17 | export const PageInfo = UUIFunctionComponent({ 18 | name: 'PageInfo', 19 | nodes: { 20 | Root: 'div', 21 | }, 22 | propTypes: PageInfoPropTypes, 23 | }, (props: PageInfoFeatureProps, { nodes }) => { 24 | const { Root } = nodes 25 | 26 | const context = useContext(PaginationContext) 27 | if (!context) { 28 | console.warn('[UUI] please use in ') 29 | return <> 30 | } 31 | const { pagination } = context 32 | 33 | const startItem = Math.max((pagination.currentPage - 1) * pagination.limit + 1, 1) 34 | const endItem = Math.min(startItem + pagination.limit - 1, pagination.count) 35 | const text = `${startItem}-${endItem} of ${pagination.count} items` 36 | 37 | return ( 38 | 39 | {props.onRender ? props.onRender(startItem, endItem, pagination.count) : text} 40 | 41 | ) 42 | }) 43 | 44 | export type PageInfoProps = UUIFunctionComponentProps -------------------------------------------------------------------------------- /src/components/Pagination/PageJumper.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState, useEffect } from 'react'; 2 | import { NumberField as UUINumberField } from '../Input'; 3 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 4 | import { PaginationContext } from './PaginationContext'; 5 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 6 | 7 | export interface PageJumperFeatureProps { 8 | /** 9 | * Custom jumper label text. 10 | */ 11 | labelText?: string; 12 | } 13 | 14 | export const PageJumperPropTypes = createComponentPropTypes({ 15 | labelText: PropTypes.string, 16 | }) 17 | 18 | export const PageJumper = UUIFunctionComponent({ 19 | name: 'PageJumper', 20 | nodes: { 21 | Root: 'div', 22 | Label: 'div', 23 | NumberField: UUINumberField, 24 | }, 25 | propTypes: PageJumperPropTypes, 26 | }, (props: PageJumperFeatureProps, { nodes }) => { 27 | const { Root, Label, NumberField } = nodes 28 | 29 | const context = useContext(PaginationContext) 30 | if (!context) { 31 | console.warn('[UUI] please use in ') 32 | return <> 33 | } 34 | const { pagination, loading } = context 35 | 36 | useEffect(() => { 37 | setInputValue(null) 38 | }, [pagination.currentPage]) 39 | 40 | const [inputValue, setInputValue] = useState(null) 41 | 42 | return ( 43 | 44 | 45 | { 52 | if (event.key === 'Enter' && inputValue !== null && inputValue !== undefined) { 53 | pagination.toNthPage(inputValue) 54 | } 55 | }, 56 | }, 57 | }} 58 | disabled={loading} 59 | placeholder={`${pagination.currentPage}`} 60 | min={1} 61 | value={inputValue} 62 | onChange={(value) => { setInputValue(value) }} 63 | > 64 | 65 | ) 66 | }) 67 | 68 | export type PageJumperProps = UUIFunctionComponentProps -------------------------------------------------------------------------------- /src/components/Pagination/PageNextButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { Button as UUIButton } from '../Button'; 3 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 4 | import { Icons } from '../../icons/Icons'; 5 | import { PaginationContext } from './PaginationContext'; 6 | import { createComponentPropTypes } from '../../utils/createPropTypes'; 7 | 8 | export interface PageNextButtonFeatureProps { 9 | } 10 | 11 | export const PageNextButtonPropTypes = createComponentPropTypes({}) 12 | 13 | export const PageNextButton = UUIFunctionComponent({ 14 | name: 'PageNextButton', 15 | nodes: { 16 | Root: UUIButton, 17 | NextIcon: Icons.ChevronRight, 18 | }, 19 | propTypes: PageNextButtonPropTypes, 20 | }, (props: PageNextButtonFeatureProps, { nodes }) => { 21 | const { Root, NextIcon } = nodes 22 | 23 | const context = useContext(PaginationContext) 24 | if (!context) { 25 | console.warn('[UUI] please use in ') 26 | return <> 27 | } 28 | const { pagination, loading } = context 29 | 30 | return ( 31 | { pagination.toNextPage() }} 34 | > 35 | 36 | 37 | ) 38 | }) 39 | 40 | export type PageNextButtonProps = UUIFunctionComponentProps 41 | -------------------------------------------------------------------------------- /src/components/Pagination/PagePrevButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { Button as UUIButton } from '../Button'; 3 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 4 | import { Icons } from '../../icons/Icons'; 5 | import { PaginationContext } from './PaginationContext'; 6 | import { createComponentPropTypes } from '../../utils/createPropTypes'; 7 | 8 | export interface PagePrevButtonFeatureProps { 9 | } 10 | 11 | export const PagePrevButtonPropTypes = createComponentPropTypes({}) 12 | 13 | export const PagePrevButton = UUIFunctionComponent({ 14 | name: 'PagePrevButton', 15 | nodes: { 16 | Root: UUIButton, 17 | PrevIcon: Icons.ChevronLeft, 18 | }, 19 | propTypes: PagePrevButtonPropTypes, 20 | }, (props: PagePrevButtonFeatureProps, { nodes }) => { 21 | const { Root, PrevIcon } = nodes 22 | 23 | const context = useContext(PaginationContext) 24 | if (!context) { 25 | console.warn('[UUI] please use in ') 26 | return <> 27 | } 28 | const { pagination, loading } = context 29 | 30 | return ( 31 | { pagination.toPrevPage() }} 34 | > 35 | 36 | 37 | ) 38 | }) 39 | 40 | export type PagePrevButtonProps = UUIFunctionComponentProps 41 | -------------------------------------------------------------------------------- /src/components/Pagination/PageSelector.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { PaginationContext } from './PaginationContext'; 4 | import { HTMLSelect } from '../Select'; 5 | import { range } from 'lodash-es'; 6 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 7 | 8 | export interface PageSelectorFeatureProps { 9 | labelRender?: (currentPage: number, totalPage: number) => string; 10 | } 11 | 12 | export const PageSelectorPropTypes = createComponentPropTypes({ 13 | labelRender: PropTypes.func, 14 | }) 15 | 16 | export const PageSelector = UUIFunctionComponent({ 17 | name: 'PageSelector', 18 | nodes: { 19 | Root: 'div', 20 | Select: HTMLSelect, 21 | }, 22 | propTypes: PageSelectorPropTypes, 23 | }, (props: PageSelectorFeatureProps, { nodes }) => { 24 | const { Root, Select } = nodes 25 | 26 | const context = useContext(PaginationContext) 27 | if (!context) { 28 | console.warn('[UUI] please use in ') 29 | return <> 30 | } 31 | const { pagination, loading } = context 32 | 33 | const pageNumbers = range(1, pagination.totalPage+1) 34 | 35 | return ( 36 | 37 | { 39 | return { 40 | label: props.labelRender ? props.labelRender(i) : `${i} / Page`, 41 | value: i, 42 | } 43 | })} 44 | value={pagination.limit} 45 | onChange={(value) => pagination.changePageSize(value)} 46 | /> 47 | 48 | ) 49 | }) 50 | 51 | export type PageSizeProps = UUIFunctionComponentProps -------------------------------------------------------------------------------- /src/components/Pagination/PaginationContext.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { usePagination } from "../../hooks/usePagination"; 3 | 4 | export interface PaginationContext extends ReturnType { 5 | } 6 | export interface PaginationContextValue { 7 | pagination: PaginationContext; 8 | loading?: boolean; 9 | } 10 | export const PaginationContext = React.createContext(null) -------------------------------------------------------------------------------- /src/components/Pagination/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Pagination'; -------------------------------------------------------------------------------- /src/components/Popover/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Popover'; -------------------------------------------------------------------------------- /src/components/ProgressBar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ProgressBar'; -------------------------------------------------------------------------------- /src/components/Radio/RadioGroupContext.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export interface RadioGroupContextValue { 4 | value: string | number; 5 | onChange: (value: string | number) => void; 6 | focusValue: string | number; 7 | onFocus?: React.FocusEventHandler; 8 | onBlur?: React.FocusEventHandler; 9 | } 10 | export const RadioGroupContext = React.createContext(null) -------------------------------------------------------------------------------- /src/components/Radio/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Radio'; 2 | export * from './RadioGroup'; -------------------------------------------------------------------------------- /src/components/RightClickZone/index.ts: -------------------------------------------------------------------------------- 1 | export * from './RightClickZone' -------------------------------------------------------------------------------- /src/components/SegmentControl/index.ts: -------------------------------------------------------------------------------- 1 | export * from './SegmentControl'; -------------------------------------------------------------------------------- /src/components/Select/index.ts: -------------------------------------------------------------------------------- 1 | export * from './HTMLSelect'; 2 | export * from './Select'; -------------------------------------------------------------------------------- /src/components/Skeleton/Paragraph.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 4 | 5 | export interface SkeletonParagraphFeatureProps { 6 | lines?: number; 7 | } 8 | 9 | export const SkeletonParagraphPropTypes = createComponentPropTypes({ 10 | lines: PropTypes.number, 11 | }) 12 | 13 | export const SkeletonParagraph = UUIFunctionComponent({ 14 | name: 'SkeletonParagraph', 15 | nodes: { 16 | Root: 'div', 17 | Line: 'p', 18 | }, 19 | propTypes: SkeletonParagraphPropTypes, 20 | }, (props: SkeletonParagraphFeatureProps, { nodes }) => { 21 | const { Root, Line } = nodes 22 | const lines = props.lines || 3 23 | return ( 24 | 25 | {(new Array(lines)).fill(1).map((i, index) => { 26 | return ( 27 | 28 | ) 29 | })} 30 | 31 | ) 32 | }) 33 | 34 | export type SkeletonParagraphProps = UUIFunctionComponentProps -------------------------------------------------------------------------------- /src/components/Skeleton/Picture.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { createComponentPropTypes } from '../../utils/createPropTypes'; 4 | 5 | export interface SkeletonPictureFeatureProps { 6 | } 7 | 8 | export const SkeletonPicturePropTypes = createComponentPropTypes({}) 9 | 10 | export const SkeletonPicture = UUIFunctionComponent({ 11 | name: 'SkeletonPicture', 12 | nodes: { 13 | Root: 'div' 14 | }, 15 | propTypes: SkeletonPicturePropTypes, 16 | }, (props: SkeletonPictureFeatureProps, { nodes }) => { 17 | const { Root } = nodes 18 | return ( 19 | 20 | ) 21 | }) 22 | 23 | export type SkeletonPictureProps = UUIFunctionComponentProps -------------------------------------------------------------------------------- /src/components/Skeleton/Skeleton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SkeletonParagraph } from './Paragraph'; 3 | import { SkeletonTitle } from './Title'; 4 | import { SkeletonPicture } from './Picture'; 5 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 6 | import { createGroupedComponent } from '../../utils/createGroupedComponent'; 7 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 8 | 9 | 10 | export interface SkeletonFeatureProps { 11 | children?: React.ReactNode; 12 | } 13 | 14 | export const SkeletonPropTypes = createComponentPropTypes({ 15 | children: PropTypes.node, 16 | }) 17 | 18 | export const _Skeleton = UUIFunctionComponent({ 19 | name: 'Skeleton', 20 | nodes: { 21 | Root: 'div' 22 | }, 23 | propTypes: SkeletonPropTypes, 24 | }, (props: SkeletonFeatureProps, { nodes }) => { 25 | const { Root } = nodes 26 | return ( 27 | {props.children} 28 | ) 29 | }) 30 | 31 | const Skeleton = createGroupedComponent(_Skeleton, { 32 | Paragraph: SkeletonParagraph, 33 | Title: SkeletonTitle, 34 | Picture: SkeletonPicture, 35 | }) 36 | 37 | export { Skeleton } 38 | 39 | export type SkeletonProps = UUIFunctionComponentProps 40 | -------------------------------------------------------------------------------- /src/components/Skeleton/Title.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { createComponentPropTypes } from '../../utils/createPropTypes'; 4 | 5 | export interface SkeletonTitleFeatureProps { 6 | } 7 | 8 | export const SkeletonTitlePropTypes = createComponentPropTypes({}) 9 | 10 | export const SkeletonTitle = UUIFunctionComponent({ 11 | name: 'SkeletonTitle', 12 | nodes: { 13 | Root: 'h3' 14 | }, 15 | propTypes: SkeletonTitlePropTypes, 16 | }, (props: SkeletonTitleFeatureProps, { nodes }) => { 17 | const { Root } = nodes 18 | return ( 19 | 20 | ) 21 | }) 22 | 23 | export type SkeletonTitleProps = UUIFunctionComponentProps -------------------------------------------------------------------------------- /src/components/Skeleton/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Skeleton'; 2 | export * from './Title'; 3 | export * from './Paragraph'; 4 | export * from './Picture'; -------------------------------------------------------------------------------- /src/components/Slider/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Slider'; -------------------------------------------------------------------------------- /src/components/Stepper/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Stepper'; -------------------------------------------------------------------------------- /src/components/Switch/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './Switch' -------------------------------------------------------------------------------- /src/components/Table/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Table'; -------------------------------------------------------------------------------- /src/components/Tabs/Tab.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useRef } from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { TabsContext } from './TabsContext'; 4 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 5 | 6 | export interface TabFeatureProps { 7 | /** 8 | * The label text to display in the tab node. 9 | */ 10 | label: React.ReactNode; 11 | /** 12 | * Value of tab 13 | */ 14 | value: string; 15 | /** 16 | * Content of tab 17 | */ 18 | children: React.ReactNode; 19 | } 20 | 21 | export const TabPropTypes = createComponentPropTypes({ 22 | label: PropTypes.node.isRequired, 23 | value: PropTypes.string.isRequired, 24 | children: PropTypes.node.isRequired, 25 | }) 26 | 27 | export const Tab = UUIFunctionComponent({ 28 | name: 'Tab', 29 | nodes: { 30 | Root: 'div', 31 | }, 32 | propTypes: TabPropTypes, 33 | }, (props: TabFeatureProps, { nodes, NodeDataProps }) => { 34 | const { Root } = nodes 35 | 36 | const context = useContext(TabsContext) 37 | if (!context) { 38 | console.warn('[UUI] please use in ') 39 | return <> 40 | } 41 | const { value, onChange } = context 42 | const isActive = value === props.value 43 | 44 | const ref = useRef(null) 45 | 46 | useEffect(() => { 47 | if (context.focusValue === props.value) { 48 | ref.current?.focus() 49 | if (context.toggleTabWhenFocusChange) { 50 | context.onChange(props.value) 51 | } 52 | } 53 | // eslint-disable-next-line react-hooks/exhaustive-deps 54 | }, [context.focusValue, context.toggleTabWhenFocusChange, props.value]) 55 | 56 | return ( 57 | { onChange(props.value) }} 66 | > 67 | {props.label} 68 | 69 | ) 70 | }) 71 | 72 | export type TabProps = UUIFunctionComponentProps 73 | -------------------------------------------------------------------------------- /src/components/Tabs/TabsContext.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export interface TabsContextValue { 4 | value: string; 5 | onChange: (value: string) => void; 6 | focusValue: string; 7 | toggleTabWhenFocusChange?: boolean; 8 | } 9 | export const TabsContext = React.createContext(null) -------------------------------------------------------------------------------- /src/components/Tabs/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Tabs'; 2 | export * from './Tab'; -------------------------------------------------------------------------------- /src/components/Tag/Tag.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UUIFunctionComponent, UUIFunctionComponentProps } from '../../core'; 3 | import { createComponentPropTypes, PropTypes } from '../../utils/createPropTypes'; 4 | 5 | export interface TagFeatureProps { 6 | /** 7 | * The content of tag. 8 | */ 9 | children?: React.ReactNode; 10 | /** 11 | * Callback invokes when user click this tag. 12 | */ 13 | onClick?: () => void; 14 | } 15 | 16 | export const TagPropTypes = createComponentPropTypes({ 17 | children: PropTypes.node, 18 | onClick: PropTypes.func, 19 | }) 20 | 21 | export const Tag = UUIFunctionComponent({ 22 | name: 'Tag', 23 | nodes: { 24 | Root: 'div', 25 | Content: 'span', 26 | }, 27 | propTypes: TagPropTypes, 28 | }, (props: TagFeatureProps, { nodes, NodeDataProps }) => { 29 | const { Root, Content } = nodes 30 | return ( 31 | { 36 | props.onClick && props.onClick() 37 | }) : undefined} 38 | > 39 | {props.children} 40 | 41 | ) 42 | }) 43 | 44 | export type TagProps = UUIFunctionComponentProps -------------------------------------------------------------------------------- /src/components/Tag/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Tag' -------------------------------------------------------------------------------- /src/components/Toast/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Toaster'; 2 | export * from './Toast'; -------------------------------------------------------------------------------- /src/components/Tooltip/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Tooltip' -------------------------------------------------------------------------------- /src/components/UUIComponentNames.ts: -------------------------------------------------------------------------------- 1 | export enum UUIComponentNames { 2 | Accordion = "Accordion", 3 | AccordionPane = "AccordionPane", 4 | Breadcrumb = "Breadcrumb", 5 | Button = "Button", 6 | Cascader = "Cascader", 7 | Checkbox = "Checkbox", 8 | Collapse = "Collapse", 9 | DatePicker = "DatePicker", 10 | Dialog = "Dialog", 11 | Drawer = "Drawer", 12 | Icon = "Icon", 13 | TextField = "TextField", 14 | NumberField = "NumberField", 15 | TextArea = "TextArea", 16 | CountdownLabel = "CountdownLabel", 17 | DateLabel = "DateLabel", 18 | MoneyLabel = "MoneyLabel", 19 | NumberAbbrLabel = "NumberAbbrLabel", 20 | TimeLabel = "TimeLabel", 21 | Layout = "Layout", 22 | LayoutAside = "LayoutAside", 23 | LayoutFooter = "LayoutFooter", 24 | LayoutHeader = "LayoutHeader", 25 | LayoutMain = "LayoutMain", 26 | LayoutNav = "LayoutNav", 27 | ListBox = "ListBox", 28 | LoadingCover = "LoadingCover", 29 | LoadingSpinner = "LoadingSpinner", 30 | Page = "Page", 31 | PageAnnotatedSection = "PageAnnotatedSection", 32 | PageSection = "PageSection", 33 | Pagination = "Pagination", 34 | PageInfo = "PageInfo", 35 | PageJumper = "PageJumper", 36 | PageList = "PageList", 37 | PageNextButton = "PageNextButton", 38 | PagePrevButton = "PagePrevButton", 39 | PageSelector = "PageSelector", 40 | PageSize = "PageSize", 41 | Popover = "Popover", 42 | ProgressBar = "ProgressBar", 43 | Radio = "Radio", 44 | RadioGroup = "RadioGroup", 45 | SegmentControl = "SegmentControl", 46 | HTMLSelect = "HTMLSelect", 47 | Select = "Select", 48 | Skeleton = "Skeleton", 49 | SkeletonParagraph = "SkeletonParagraph", 50 | SkeletonPicture = "SkeletonPicture", 51 | SkeletonTitle = "SkeletonTitle", 52 | Slider = "Slider", 53 | Stepper = "Stepper", 54 | Switch = "Switch", 55 | Table = "Table", 56 | Tabs = "Tabs", 57 | Tab = "Tab", 58 | Tag = "Tag", 59 | Toast = "Toast", 60 | Toaster = "Toaster", 61 | } -------------------------------------------------------------------------------- /src/core/UUIComponentProxy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { mergeCustomize } from './utils/mergeCustomize'; 3 | import { compileProps } from './utils/compileProps'; 4 | 5 | export function UUIComponentProxy

( 6 | Component: React.ComponentType

, 7 | customize: P['customize'], 8 | ) { 9 | return (props: P) => { 10 | const compiledProps = compileProps(props) 11 | let finalCustomize: any = customize 12 | if (compiledProps.customize !== undefined) { 13 | finalCustomize = mergeCustomize(customize, compiledProps.customize) 14 | } 15 | return 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/core/index.ts: -------------------------------------------------------------------------------- 1 | export { UUIFunctionComponent, UUIClassComponent } from './UUIComponent'; 2 | export { UUIComponentProxy } from './UUIComponentProxy'; 3 | 4 | export type { UUIComponentProps, UUIFunctionComponentProps, UUIClassComponentProps } from './modules/UUIComponentProps'; -------------------------------------------------------------------------------- /src/core/modules/UUIComponentPropTypes.ts: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | export type UUIComponentFeaturePropTypes< 4 | P, 5 | > = { [key in keyof Required

]: any } 6 | 7 | export const UUIConveniencePropTypes = { 8 | id: PropTypes.string, 9 | className: PropTypes.string, 10 | style: PropTypes.object, 11 | } 12 | 13 | export const UUIMetaPropTypes = { 14 | prefix: PropTypes.string, 15 | separator: PropTypes.string, 16 | } 17 | 18 | export const UUICustomizePropTypes = { 19 | customize: PropTypes.object, 20 | } 21 | 22 | export const UUICommonPropTypes = { 23 | ...UUIConveniencePropTypes, 24 | ...UUIMetaPropTypes, 25 | ...UUICustomizePropTypes, 26 | } -------------------------------------------------------------------------------- /src/core/modules/UUIComponentProps.ts: -------------------------------------------------------------------------------- 1 | import { IntrinsicNodeT, FunctionComponentNodeT, ClassComponentNodeT, NodeCustomizeProps } from "./UUICustomizeNode" 2 | import { Ref } from "react" 3 | 4 | export type UUIConvenienceProps = { 5 | /** 6 | * Convenience id props, 7 | * this props will be applied to id of component Root node. 8 | */ 9 | id?: string; 10 | /** 11 | * Convenience ref props, 12 | * this props will be applied to ref of component Root node. 13 | */ 14 | ref?: Ref; 15 | /** 16 | * Convenience className props, 17 | * this props will be applied to append to extendClassName of component Root node customize props. 18 | * @default none 19 | */ 20 | className?: string; 21 | /** 22 | * Convenience style props, 23 | * this props will be applied to merge to extendStyle of component Root node customize props. 24 | * @default none 25 | */ 26 | style?: React.CSSProperties; 27 | 28 | /** 29 | * React natively support data-* attributes type, 30 | * dont need to redeclare again convenience data attributes. 31 | */ 32 | } 33 | export type UUIMetaProps = { 34 | prefix?: string; 35 | separator?: string; 36 | } 37 | export type UUIComponentProps = P & UUIConvenienceProps & UUIComponentCustomizeProps 38 | export type UUIFunctionComponentProps any> = Parameters[0] 39 | export type UUIClassComponentProps> = React.ComponentProps 40 | 41 | export type UUIComponentCustomizeProps< 42 | X extends { [key in string]?: keyof IntrinsicNodeT | FunctionComponentNodeT | ClassComponentNodeT }, 43 | > = { 44 | /** 45 | * Customize component nodes 46 | * @default none 47 | */ 48 | customize?: { 49 | [key in keyof X]?: X[key] extends keyof IntrinsicNodeT 50 | ? NodeCustomizeProps & Partial> 51 | : ( 52 | X[key] extends FunctionComponentNodeT 53 | ? NonNullable[0]['customize']> 54 | : ( 55 | X[key] extends ClassComponentNodeT 56 | ? React.ComponentProps['customize'] 57 | : never 58 | ) 59 | ) 60 | }; 61 | } -------------------------------------------------------------------------------- /src/core/utils/compileProps.ts: -------------------------------------------------------------------------------- 1 | import { clone, pickBy, mapKeys, isEmpty } from "lodash-es"; 2 | import classNames from "classnames"; 3 | 4 | export function compileProps(props: any, ref?: any): any { 5 | const compiledProps = clone(props) 6 | if (!compiledProps.customize) { 7 | compiledProps.customize = {} 8 | } 9 | 10 | const rootCustomizeProps: any = (compiledProps.customize as any)['Root'] || {}; 11 | /** 12 | * Convenience props: className, style 13 | * `className` will be injected into customize.Root { extendClassName: ... } 14 | * `style` will be injected into customize.Root { extendStyle: ... } 15 | * `id` will be injected into customize.Root { overrideId: ... } 16 | * `data-*` will be injected into customize.Root { dataAttributes: ... } 17 | * `aria-*` will be injected into customize.Root { ariaAttributes: ... } 18 | */ 19 | if (compiledProps.className) rootCustomizeProps.extendClassName = classNames(compiledProps.className, rootCustomizeProps.extendClassName); 20 | if (compiledProps.style) rootCustomizeProps.extendStyle = Object.assign(compiledProps.style, rootCustomizeProps.extendStyle); 21 | if (compiledProps.id) rootCustomizeProps.overrideId = compiledProps.id; 22 | if (ref) rootCustomizeProps.ref = ref; 23 | 24 | let dataAttributes = pickBy(compiledProps, (v, k) => k.startsWith('data-')) 25 | dataAttributes = mapKeys(dataAttributes, (v, k) => k.replace('data-', '')) 26 | if (!isEmpty(dataAttributes)) { 27 | rootCustomizeProps.dataAttributes = Object.assign(dataAttributes, rootCustomizeProps.dataAttributes); 28 | } 29 | 30 | let ariaAttributes = pickBy(compiledProps, (v, k) => k.startsWith('aria-')) 31 | ariaAttributes = mapKeys(ariaAttributes, (v, k) => k.replace('aria-', '')) 32 | if (!isEmpty(ariaAttributes)) { 33 | rootCustomizeProps.ariaAttributes = Object.assign(ariaAttributes, rootCustomizeProps.ariaAttributes); 34 | } 35 | 36 | /** 37 | * set undefined if customize is empty 38 | */ 39 | 40 | if (!isEmpty(rootCustomizeProps)) { 41 | (compiledProps.customize as any)['Root'] = rootCustomizeProps; 42 | } 43 | if (isEmpty(compiledProps.customize)) { 44 | compiledProps.customize = undefined; 45 | } 46 | 47 | compiledProps.ref = ref; 48 | 49 | return compiledProps 50 | } -------------------------------------------------------------------------------- /src/core/utils/mergeCustomize.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from "classnames" 3 | import { mergeWith } from 'lodash-es'; 4 | import { ComponentNodeCustomizeProps, IntrinsicNodeCustomizeProps } from '../modules/UUICustomizeNode'; 5 | 6 | export function mergeCustomize(...customizes: Array | undefined>): ComponentNodeCustomizeProps | undefined { 7 | const mergedCustomize: any = {} 8 | 9 | const customizer = (c1: IntrinsicNodeCustomizeProps | undefined, c2: IntrinsicNodeCustomizeProps) => { 10 | if (c1 === undefined) return c2 11 | 12 | const result = { 13 | ...c1, 14 | ...c2, 15 | } 16 | 17 | if (c1.extendClassName && c2.extendClassName) { 18 | result.extendClassName = classNames(c1.extendClassName, c2.extendClassName) 19 | } 20 | if (c1.extendStyle && c2.extendStyle) { 21 | result.extendStyle = { ...c1.extendStyle, ...c2.extendStyle } 22 | } 23 | if (c1.extendChildrenBefore && c2.extendChildrenBefore) { 24 | result.extendChildrenBefore = <>{c1.extendChildrenBefore}{c2.extendChildrenBefore} 25 | } 26 | if (c1.extendChildrenAfter && c2.extendChildrenAfter) { 27 | result.extendChildrenAfter = <>{c1.extendChildrenAfter}{c2.extendChildrenAfter} 28 | } 29 | 30 | return result 31 | } 32 | 33 | for (const customize of customizes) { 34 | mergeWith(mergedCustomize, customize, customizer) 35 | } 36 | 37 | return mergedCustomize 38 | } -------------------------------------------------------------------------------- /src/core/utils/mergeProviderCustomize.ts: -------------------------------------------------------------------------------- 1 | import { UUIProviderContextValue } from "../../UUIProvider" 2 | import { mergeCustomize } from "./mergeCustomize"; 3 | 4 | export function mergeProviderCustomize(options: any, props: any, providerContext: UUIProviderContextValue | null) { 5 | if (providerContext?.customize && providerContext.customize[options.name]) { 6 | const propsCustomize = props.customize 7 | const providerCustomize = providerContext.customize[options.name] 8 | props.customize = mergeCustomize(providerCustomize, propsCustomize) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/core/utils/mergeRefs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Codebase: https://github.com/smooth-code/react-merge-refs/blob/master/src/index.js 3 | */ 4 | 5 | export function mergeRefs(refs: any[]): (instance: unknown) => void { 6 | return (instance: any) => { 7 | refs.forEach((ref: any) => { 8 | if (typeof ref === 'function') { 9 | ref(instance) 10 | } else if (ref != null && typeof ref === 'object') { 11 | ref.current = instance 12 | } else { 13 | // DO NOTHING 14 | } 15 | }) 16 | } 17 | } -------------------------------------------------------------------------------- /src/core/utils/typeHelper.ts: -------------------------------------------------------------------------------- 1 | export interface TypeWithArgs extends Function { new(...args: A): T} 2 | -------------------------------------------------------------------------------- /src/hooks/useGlobalClickAway.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect } from "react" 2 | import { useClickAway } from "react-use" 3 | 4 | export function useGlobalClickAway(active: boolean, elementRef: any, onClickAway?: (event: Event) => void) { 5 | const activateElement = useCallback((element: any) => { 6 | let foundIndex = -1 7 | do { 8 | foundIndex = GlobalActiveElements.findIndex((i) => i === element) 9 | if (foundIndex !== -1) GlobalActiveElements.splice(foundIndex, 1) 10 | } while (foundIndex !== -1); 11 | GlobalActiveElements.push(element) 12 | }, []) 13 | 14 | const deactivateElement = useCallback((element: any) => { 15 | const foundIndex = GlobalActiveElements.findIndex((i) => i === element) 16 | if (foundIndex === -1) return 17 | GlobalActiveElements.splice(foundIndex, Number.MAX_SAFE_INTEGER); 18 | }, []) 19 | 20 | const isCurrentActiveElement = useCallback((element: any) => { 21 | return (GlobalActiveElements.length > 0 && GlobalActiveElements[GlobalActiveElements.length-1] === element) 22 | }, []) 23 | 24 | useEffect(() => { 25 | if (!elementRef.current) return; 26 | const targetElement = elementRef.current 27 | if (active) { 28 | activateElement(targetElement) 29 | } else { 30 | deactivateElement(targetElement) 31 | } 32 | 33 | return () => { 34 | deactivateElement(targetElement) 35 | } 36 | }, [activateElement, deactivateElement, active, elementRef]) 37 | 38 | 39 | useClickAway(elementRef, (event) => { 40 | if (active) { 41 | if (elementRef.current && !isCurrentActiveElement(elementRef.current)) return; 42 | setTimeout(() => { 43 | onClickAway && onClickAway(event) 44 | }, 0) 45 | } 46 | }, ['mouseup', 'touchend']) 47 | } 48 | 49 | const GlobalActiveElements: any[] = []; -------------------------------------------------------------------------------- /src/hooks/usePagination.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { clamp } from 'lodash-es'; 3 | 4 | export interface IPagination { 5 | offset: number; 6 | limit: number; 7 | count: number; 8 | } 9 | 10 | export const EmptyPagination = (): IPagination => ({ 11 | offset: 0, 12 | limit: 50, 13 | count: 0, 14 | }) 15 | 16 | export function usePagination( 17 | value: IPagination, 18 | onChange: (pagination: IPagination) => void, 19 | ) { 20 | const prevOffset = useMemo(() => value.offset - value.limit, [value.offset, value.limit]) 21 | const nextOffset = useMemo(() => value.offset + value.limit, [value.offset, value.limit]) 22 | 23 | const hasPrevious = useMemo(() => prevOffset >= 0, [prevOffset]) 24 | const hasNext = useMemo(() => nextOffset < value.count, [nextOffset, value.count]) 25 | const currentPage = useMemo( 26 | () => { 27 | if (value.offset === 0) return 1 28 | return value.offset && value.limit ? Math.floor(value.offset / value.limit) + 1 : 0 29 | }, 30 | [value.offset, value.limit], 31 | ) 32 | const totalPage = useMemo(() => value.limit ? Math.ceil(value.count / value.limit) : 0, [value.count, value.limit]) 33 | 34 | const toNextPage = () => { 35 | onChange({ offset: nextOffset, limit: value.limit, count: value.count }) 36 | } 37 | const toPrevPage = () => { 38 | onChange({ offset: Math.max(0, prevOffset), limit: value.limit, count: value.count }) 39 | } 40 | const toNthPage = (n: number) => { 41 | onChange({ offset: clamp((n - 1) * value.limit, 0, value.count), limit: value.limit, count: value.count }) 42 | } 43 | const changePageSize = (size: number) => { 44 | onChange({ ...value, limit: Math.max(size, 0) }) 45 | } 46 | 47 | return { 48 | ...value, 49 | currentPage, 50 | totalPage, 51 | toNextPage, 52 | toPrevPage, 53 | toNthPage, 54 | changePageSize, 55 | hasPrevious, 56 | hasNext, 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/hooks/usePendingValue.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useCallback, useEffect } from "react"; 2 | 3 | /** 4 | * An useful tool to manage component's inner state. 5 | */ 6 | export interface UsePendingValueOptions { 7 | resetWhenInitialValueChanged?: boolean; 8 | } 9 | export function usePendingValue(initialValue: T, onFinalChange: (finalValue: T) => void, options?: UsePendingValueOptions) { 10 | const [_innerValue, _setInnerValue] = useState(initialValue) 11 | 12 | const innerValue = _innerValue 13 | const setInnerValue = useCallback((newValue: React.SetStateAction, confirm = false) => { 14 | _setInnerValue((value) => { 15 | if (confirm) { 16 | onFinalChange((typeof newValue === 'function') ? (newValue as any)(value) : newValue) 17 | } 18 | return (typeof newValue === 'function') ? (newValue as any)(value) : newValue 19 | }) 20 | }, [onFinalChange]) 21 | const resetInnerValue = useCallback(() => { setInnerValue(initialValue) }, [initialValue, setInnerValue]) 22 | 23 | useEffect(() => { 24 | if (options?.resetWhenInitialValueChanged) { 25 | _setInnerValue(initialValue) 26 | } 27 | }, [initialValue, options?.resetWhenInitialValueChanged]) 28 | 29 | return [innerValue, setInnerValue, resetInnerValue] as const 30 | } -------------------------------------------------------------------------------- /src/icons/Icons.tsx: -------------------------------------------------------------------------------- 1 | import { IconGallery } from "../components/Icon"; 2 | 3 | import { ReactComponent as Eye } from './assets/eye.svg'; 4 | import { ReactComponent as EyeOff } from './assets/eye-off.svg'; 5 | import { ReactComponent as Loader } from './assets/loader.svg'; 6 | import { ReactComponent as ChevronLeft } from './assets/chevron-left.svg'; 7 | import { ReactComponent as ChevronRight } from './assets/chevron-right.svg'; 8 | import { ReactComponent as ChevronUp } from './assets/chevron-up.svg'; 9 | import { ReactComponent as ChevronDown } from './assets/chevron-down.svg'; 10 | import { ReactComponent as ChevronsLeft } from './assets/chevrons-left.svg'; 11 | import { ReactComponent as ChevronsRight } from './assets/chevrons-right.svg'; 12 | import { ReactComponent as ChevronsUp } from './assets/chevrons-up.svg'; 13 | import { ReactComponent as ChevronsDown } from './assets/chevrons-down.svg'; 14 | import { ReactComponent as Spinner } from './assets/spinner.svg'; 15 | import { ReactComponent as Home } from './assets/home.svg'; 16 | import { ReactComponent as Search } from './assets/search.svg'; 17 | import { ReactComponent as ArrowRight } from './assets/arrow-right.svg'; 18 | import { ReactComponent as Calendar } from './assets/calendar.svg'; 19 | import { ReactComponent as Clock } from './assets/clock.svg'; 20 | 21 | export const Icons = IconGallery({ 22 | Eye: { source: Eye }, 23 | EyeOff: { source: EyeOff }, 24 | Loader: { source: Loader }, 25 | ChevronLeft: { source: ChevronLeft }, 26 | ChevronRight: { source: ChevronRight }, 27 | ChevronUp: { source: ChevronUp }, 28 | ChevronDown: { source: ChevronDown }, 29 | ChevronsLeft: { source: ChevronsLeft }, 30 | ChevronsRight: { source: ChevronsRight }, 31 | ChevronsUp: { source: ChevronsUp }, 32 | ChevronsDown: { source: ChevronsDown }, 33 | ArrowRight: { source: ArrowRight }, 34 | Spinner: { source: Spinner }, 35 | Home: { source: Home }, 36 | Search: { source: Search }, 37 | Calendar: { source: Calendar }, 38 | Clock: { source: Clock }, 39 | }, { width: 16, height: 16, mode: 'svg' }) -------------------------------------------------------------------------------- /src/icons/assets/arrow-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/assets/calendar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/assets/chevron-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/assets/chevron-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/assets/chevron-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/assets/chevron-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/assets/chevrons-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/assets/chevrons-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/assets/chevrons-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/assets/chevrons-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/assets/clock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/assets/eye-off.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/assets/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/assets/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/assets/loader.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/assets/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/assets/spinner.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Core 3 | */ 4 | 5 | export * from './core' 6 | export { UUIProvider } from './UUIProvider'; 7 | 8 | /** 9 | * Component Export 10 | */ 11 | 12 | export * from './components/Accordion' 13 | export * from './components/Breadcrumb' 14 | export * from './components/Button' 15 | export * from './components/Cascader' 16 | export * from './components/Checkbox' 17 | export * from './components/Collapse' 18 | export * from './components/Dialog' 19 | export * from './components/DateTime' 20 | export * from './components/Drawer' 21 | export * from './components/Icon' 22 | export * from './components/Input' 23 | export * from './components/Label' 24 | export * from './components/Layout' 25 | export * from './components/ListBox' 26 | export * from './components/Loading' 27 | export * from './components/Menu' 28 | export * from './components/Page' 29 | export * from './components/Pagination' 30 | export * from './components/Popover' 31 | export * from './components/ProgressBar' 32 | export * from './components/Radio' 33 | export * from './components/RightClickZone' 34 | export * from './components/SegmentControl' 35 | export * from './components/Select' 36 | export * from './components/Skeleton' 37 | export * from './components/Slider' 38 | export * from './components/Stepper' 39 | export * from './components/Switch' 40 | export * from './components/Table' 41 | export * from './components/Tabs' 42 | export * from './components/Tag' 43 | export * from './components/Toast' 44 | export * from './components/Tooltip' 45 | -------------------------------------------------------------------------------- /src/styles/base.scss: -------------------------------------------------------------------------------- 1 | /** normalize.css */ 2 | @import './normalize.scss'; 3 | 4 | /** global style */ 5 | @import './global.scss'; 6 | @import './mixins.scss'; 7 | @import './variables.scss'; 8 | -------------------------------------------------------------------------------- /src/styles/components.scss: -------------------------------------------------------------------------------- 1 | @import './components/Accordion.scss'; 2 | @import './components/Breadcrumb.scss'; 3 | @import './components/Button.scss'; 4 | @import './components/Cascader.scss'; 5 | @import './components/Checkbox.scss'; 6 | @import './components/CheckboxGroup.scss'; 7 | @import './components/Collapse.scss'; 8 | @import './components/Dialog.scss'; 9 | @import './components/DateTime.scss'; 10 | @import './components/Drawer.scss'; 11 | @import './components/Icon.scss'; 12 | @import './components/Label.scss'; 13 | @import './components/Layout.scss'; 14 | @import './components/ListBox.scss'; 15 | @import './components/Loading.scss'; 16 | @import './components/Menu.scss'; 17 | @import './components/Tabs.scss'; 18 | @import './components/TextField.scss'; 19 | @import './components/TextArea.scss'; 20 | @import './components/NumberField.scss'; 21 | @import './components/Page.scss'; 22 | @import './components/Pagination.scss'; 23 | @import './components/Popover.scss'; 24 | @import './components/ProgressBar.scss'; 25 | @import './components/RadioGroup.scss'; 26 | @import './components/Radio.scss'; 27 | @import './components/RightClickZone.scss'; 28 | @import './components/SegmentControl.scss'; 29 | @import './components/HTMLSelect.scss'; 30 | @import './components/Select.scss'; 31 | @import './components/Skeleton.scss'; 32 | @import './components/Slider.scss'; 33 | @import './components/Stepper.scss'; 34 | @import './components/Switch.scss'; 35 | @import './components/Table.scss'; 36 | @import './components/Tag.scss'; 37 | @import './components/Toaster.scss'; 38 | @import './components/Tooltip.scss'; -------------------------------------------------------------------------------- /src/styles/components/Accordion.scss: -------------------------------------------------------------------------------- 1 | 2 | .UUI-Accordion-Root { 3 | @include border; 4 | @include radius; 5 | overflow: hidden; 6 | 7 | .UUI-AccordionPane-Root { 8 | 9 | &[data-uui-disabled="true"] { 10 | 11 | .UUI-AccordionPane-Header { 12 | cursor: not-allowed; 13 | 14 | &:focus { 15 | background-color: #f5f7fa; 16 | } 17 | .UUI-AccordionPane-Icon { 18 | svg { 19 | color: #bbbbbb; 20 | } 21 | } 22 | .UUI-AccordionPane-Title { 23 | color: #bbbbbb; 24 | } 25 | } 26 | } 27 | 28 | &[data-uui-expanded="true"] { 29 | .UUI-AccordionPane-Icon { 30 | transform: rotate(90deg); 31 | } 32 | } 33 | 34 | .UUI-AccordionPane-Header { 35 | display: flex; 36 | flex-direction: row; 37 | padding: 0.5rem; 38 | background-color: #f5f7fa; 39 | cursor: pointer; 40 | 41 | .UUI-AccordionPane-Icon { 42 | margin-right: 0.5rem; 43 | transition: transform 0.2s ease-in-out; 44 | } 45 | 46 | .UUI-AccordionPane-Title { 47 | color: #333333; 48 | } 49 | 50 | &:focus { 51 | background-color: #f0f5fd; 52 | } 53 | } 54 | .UUI-AccordionPane-Collapse { 55 | 56 | .UUI-AccordionPane-Content { 57 | padding: 0.5rem; 58 | } 59 | } 60 | 61 | border-top: 1px solid $borderColor; 62 | 63 | &:first-child { 64 | border-top: none; 65 | } 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /src/styles/components/Breadcrumb.scss: -------------------------------------------------------------------------------- 1 | 2 | .UUI-Breadcrumb-Root { 3 | .UUI-Breadcrumb-List { 4 | margin: 0; 5 | padding: 0; 6 | display: flex; 7 | flex-direction: row; 8 | 9 | .UUI-Breadcrumb-Item { 10 | &:focus { 11 | border-color: $hoverBackgroundColor; 12 | background-color: $hoverBackgroundColor; 13 | } 14 | } 15 | 16 | .UUI-Breadcrumb-Item, .UUI-Breadcrumb-Separator { 17 | padding: 0.25rem; 18 | display: inline; 19 | list-style-type: none; 20 | } 21 | 22 | .UUI-Breadcrumb-Item, .UUI-Breadcrumb-ItemLink, .UUI-Breadcrumb-Separator { 23 | color: #b3b3b3; 24 | } 25 | 26 | .UUI-Breadcrumb-Item[data-uui-active="true"], .UUI-Breadcrumb-Item[data-uui-active="true"] .UUI-Breadcrumb-ItemLink { 27 | color: #333333; 28 | } 29 | 30 | .UUI-Breadcrumb-Item[data-uui-interactive="true"]:hover, .UUI-Breadcrumb-Item[data-uui-interactive="true"] .UUI-Breadcrumb-ItemLink:hover { 31 | color: #2f65d1; 32 | cursor: pointer; 33 | } 34 | 35 | .UUI-Breadcrumb-ItemLink { 36 | text-decoration: none; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/styles/components/Checkbox.scss: -------------------------------------------------------------------------------- 1 | 2 | .UUI-Checkbox-Root { 3 | display: inline-flex; 4 | flex-direction: row; 5 | align-items: center; 6 | cursor: pointer; 7 | 8 | &:focus-within .UUI-Checkbox-Indicator { 9 | box-shadow: lighten($primaryColor, 30%) 0 0 0 3px; 10 | } 11 | 12 | .UUI-Checkbox-Input { 13 | width: 0; 14 | height: 0; 15 | opacity: 0; 16 | display: none; 17 | } 18 | 19 | .UUI-Checkbox-Indicator { 20 | position: relative; 21 | top: 0; 22 | left: 0; 23 | display: block; 24 | width: 16px; 25 | height: 16px; 26 | background-color: #fff; 27 | border: 1px solid #d9d9d9; 28 | border-radius: 2px; 29 | border-collapse: separate; 30 | } 31 | 32 | .UUI-Checkbox-Label { 33 | color: $textDarkColor; 34 | padding-left: 0.5rem; 35 | } 36 | 37 | input[type=checkbox]:checked + .UUI-Checkbox-Indicator { 38 | background-color: $primaryColor; 39 | border-color: $primaryColor; 40 | } 41 | 42 | input[type=checkbox]:checked + .UUI-Checkbox-Indicator:after { 43 | position: absolute; 44 | display: table; 45 | border: 2px solid #fff; 46 | border-top: 0; 47 | border-left: 0; 48 | width: 5.71428571px; 49 | height: 9.14285714px; 50 | top: 45%; 51 | left: 21%; 52 | -webkit-transform: rotate(45deg) scale(1) translate(-50%, -50%); 53 | transform: rotate(45deg) scale(1) translate(-50%, -50%); 54 | opacity: 1; 55 | -webkit-transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s; 56 | transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s; 57 | content: ' '; 58 | } 59 | 60 | &[data-uui-disabled="true"] { 61 | cursor: not-allowed; 62 | 63 | .UUI-Checkbox-Indicator { 64 | border-color: $disabledBorderColor; 65 | background-color: $disabledBackgroundColor; 66 | 67 | &[data-uui-checked="true"]::after { 68 | border-color: darken($disabledBackgroundColor, 30%); 69 | } 70 | } 71 | .UUI-Checkbox-Label { 72 | color: $disabledTextColor; 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /src/styles/components/CheckboxGroup.scss: -------------------------------------------------------------------------------- 1 | .UUI-CheckboxGroup-Root { 2 | display: inline-flex; 3 | flex-direction: column; 4 | } -------------------------------------------------------------------------------- /src/styles/components/Collapse.scss: -------------------------------------------------------------------------------- 1 | .UUI-Collapse-Root { 2 | max-height: 0; 3 | overflow: hidden; 4 | transition: max-height 0.15s ease-in-out; 5 | 6 | &[data-uui-opened="true"] { 7 | max-height: 999px; 8 | transition: max-height 0.25s ease-in-out; 9 | } 10 | } -------------------------------------------------------------------------------- /src/styles/components/Dialog.scss: -------------------------------------------------------------------------------- 1 | 2 | .UUI-Dialog-Backdrop { 3 | position: fixed; 4 | top: 0; 5 | right: 0; 6 | bottom: 0; 7 | left: 0; 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | opacity: 0; 12 | pointer-events: none; 13 | background-color: rgba(0, 0, 0, 0.3); 14 | z-index: 9999; 15 | 16 | .UUI-Dialog-Content { 17 | background-color: white; 18 | border-radius: $borderRadius; 19 | padding: 1rem; 20 | } 21 | 22 | &[data-uui-opened="true"] { 23 | pointer-events: inherit; 24 | opacity: 1; 25 | } 26 | 27 | transition: opacity 0.1s ease-in-out; 28 | } -------------------------------------------------------------------------------- /src/styles/components/Drawer.scss: -------------------------------------------------------------------------------- 1 | 2 | .UUI-Drawer-Portal { 3 | .UUI-Drawer-Backdrop { 4 | display: flex; 5 | position: fixed; 6 | left: 0; 7 | right: 0; 8 | top: 0; 9 | bottom: 0; 10 | background-color: white; 11 | z-index: 99; 12 | background-color: rgba(0, 0, 0, 0.3); 13 | opacity: 0; 14 | pointer-events: none; 15 | 16 | &[data-uui-placement="left"] { 17 | flex-direction: row; 18 | justify-content: flex-start; 19 | } 20 | &[data-uui-placement="right"] { 21 | flex-direction: row; 22 | justify-content: flex-end; 23 | } 24 | &[data-uui-placement="top"] { 25 | flex-direction: column; 26 | justify-content: flex-start; 27 | } 28 | &[data-uui-placement="bottom"] { 29 | flex-direction: column; 30 | justify-content: flex-end; 31 | } 32 | &[data-uui-placement="left"] .UUI-Drawer-Content, 33 | &[data-uui-placement="right"] .UUI-Drawer-Content { 34 | width: 400px; 35 | } 36 | &[data-uui-placement="top"] .UUI-Drawer-Content, 37 | &[data-uui-placement="bottom"] .UUI-Drawer-Content { 38 | width: 100%; 39 | min-height: 200px; 40 | } 41 | 42 | &[data-uui-placement="left"] .UUI-Drawer-Content { 43 | transform: translateX(-100%); 44 | } 45 | &[data-uui-placement="right"] .UUI-Drawer-Content { 46 | transform: translateX(100%); 47 | } 48 | &[data-uui-placement="top"] .UUI-Drawer-Content { 49 | transform: translateY(-100%); 50 | } 51 | &[data-uui-placement="bottom"] .UUI-Drawer-Content { 52 | transform: translateY(100%); 53 | } 54 | &[data-uui-active="true"][data-uui-placement="left"] .UUI-Drawer-Content, 55 | &[data-uui-active="true"][data-uui-placement="right"] .UUI-Drawer-Content { 56 | transform: translateX(0); 57 | } 58 | &[data-uui-active="true"][data-uui-placement="top"] .UUI-Drawer-Content, 59 | &[data-uui-active="true"][data-uui-placement="bottom"] .UUI-Drawer-Content { 60 | transform: translateY(0); 61 | } 62 | 63 | &[data-uui-active="true"] { 64 | pointer-events: auto; 65 | opacity: 1; 66 | } 67 | 68 | transition: opacity 0.2s ease-in-out; 69 | 70 | .UUI-Drawer-Content { 71 | padding: 0.5rem; 72 | background-color: white; 73 | box-shadow: 0 0 8px gray; 74 | 75 | transition: transform 0.2s ease-in-out; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/styles/components/HTMLSelect.scss: -------------------------------------------------------------------------------- 1 | 2 | .UUI-HTMLSelect-Root { 3 | display: flex; 4 | overflow: hidden; 5 | height: 32px; 6 | width: 100%; 7 | padding-right: 0.25rem; 8 | box-sizing: border-box; 9 | background-color: white; 10 | 11 | cursor: pointer; 12 | color: $textDarkColor; 13 | 14 | @include border; 15 | @include radius; 16 | @include focusWithinShadow; 17 | @include focusWithinBorder; 18 | 19 | &:not(.UUI-HTMLSelect-Select:disabled) { 20 | @include hoverBorder; 21 | } 22 | 23 | .UUI-HTMLSelect-Select { 24 | padding-left: 0.25rem; 25 | width: 100%; 26 | cursor: pointer; 27 | 28 | &:disabled { 29 | cursor: not-allowed; 30 | } 31 | } 32 | 33 | .UUI-HTMLSelect-LoadingSpinner { 34 | margin-left: 0.5rem; 35 | color: gray; 36 | } 37 | } -------------------------------------------------------------------------------- /src/styles/components/Icon.scss: -------------------------------------------------------------------------------- 1 | 2 | .UUI-Icon-Root { 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | .UUI-Icon-Container { 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | } 11 | } -------------------------------------------------------------------------------- /src/styles/components/Label.scss: -------------------------------------------------------------------------------- 1 | .UUI-NumberAbbrLabel-Root { 2 | &[title] { 3 | text-decoration: none; 4 | } 5 | } -------------------------------------------------------------------------------- /src/styles/components/Layout.scss: -------------------------------------------------------------------------------- 1 | .UUI-Layout { 2 | &-Root { 3 | position: relative; 4 | display: flex; 5 | width: 100%; 6 | height: 100%; 7 | box-sizing: border-box; 8 | 9 | &[data-uui-has-nav="true"] { 10 | flex-direction: row; 11 | } 12 | &[data-uui-has-nav="true"] > .UUI-LayoutMain-Root { 13 | margin-left: 200px; 14 | } 15 | 16 | &[data-uui-has-header="true"] > .UUI-LayoutMain-Root { 17 | margin-top: 60px; 18 | } 19 | 20 | &[data-uui-has-footer="true"] > .UUI-LayoutMain-Root { 21 | margin-bottom: 60px; 22 | } 23 | } 24 | } 25 | 26 | .UUI-LayoutNav { 27 | &-Root { 28 | width: 200px; 29 | } 30 | 31 | &-Root { 32 | position: absolute; 33 | left: 0; 34 | top: 0; 35 | bottom: 0; 36 | overflow: auto; 37 | } 38 | } 39 | 40 | .UUI-LayoutHeader { 41 | &-Root { 42 | height: 60px; 43 | width: 100%; 44 | } 45 | 46 | &-Root { 47 | position: absolute; 48 | left: 0; 49 | right: 0; 50 | top: 0; 51 | } 52 | } 53 | 54 | .UUI-LayoutMain { 55 | &-Root { 56 | width: 100%; 57 | overflow: auto; 58 | } 59 | } 60 | 61 | .UUI-LayoutFooter { 62 | &-Root { 63 | height: 60px; 64 | width: 100%; 65 | } 66 | 67 | &-Root { 68 | position: absolute; 69 | left: 0; 70 | right: 0; 71 | bottom: 0; 72 | } 73 | } 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/styles/components/ListBox.scss: -------------------------------------------------------------------------------- 1 | 2 | .UUI-ListBox-Root { 3 | display: inline-block; 4 | box-sizing: border-box; 5 | border: 1px solid transparent; 6 | 7 | @include focusWithinBorder; 8 | @include focusWithinShadow; 9 | 10 | .UUI-ListBox-Box { 11 | height: 100%; 12 | overflow-y: auto; 13 | overflow-x: hidden; 14 | 15 | padding: 0; 16 | margin: 0; 17 | 18 | .UUI-ListBox-Item { 19 | list-style-type: none; 20 | padding: 0.5rem; 21 | cursor: pointer; 22 | 23 | &[data-uui-focused="true"], &:hover { 24 | background-color: $focusBackgroundColor; 25 | } 26 | &[data-uui-selected="true"][data-uui-focused="true"], &[data-uui-selected="true"] { 27 | background-color: rgba(lighten($primaryColor, 20%), 0.4); 28 | } 29 | &[data-uui-disabled="true"], &[data-uui-disabled="true"]:hover { 30 | cursor: not-allowed; 31 | color: $disabledTextColor; 32 | background-color: inherit; 33 | } 34 | } 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /src/styles/components/Loading.scss: -------------------------------------------------------------------------------- 1 | 2 | .UUI-LoadingSpinner-Root { 3 | display: inline-flex; 4 | justify-content: center; 5 | align-items: center; 6 | 7 | &[data-uui-animate="true"] { 8 | .UUI-LoadingSpinner-Icon { 9 | animation-iteration-count: infinite; 10 | animation-fill-mode: forwards; 11 | animation: rotate .8s linear infinite; 12 | @keyframes rotate { 100% { transform: rotate(360deg); } } 13 | } 14 | } 15 | } 16 | 17 | .UUI-LoadingCover-Root { 18 | position: relative; 19 | 20 | & > .UUI-LoadingCover-Mask { 21 | display: none; 22 | position: absolute; 23 | top: 0; 24 | bottom: 0; 25 | left: 0; 26 | right: 0; 27 | background-color: hsla(0,0%,100%,.9); 28 | transition: opacity .3s; 29 | justify-content: center; 30 | align-items: center; 31 | 32 | & > .UUI-LoadingCover-Spinner { 33 | color: lighten($primaryColor, 10%); 34 | } 35 | & > .UUI-LoadingCover-Label { 36 | user-select: none; 37 | margin-left: 0.5rem; 38 | color: lighten($primaryColor, 10%); 39 | } 40 | } 41 | 42 | &[data-uui-loading="true"] { 43 | & > .UUI-LoadingCover-Mask { 44 | display: flex; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/styles/components/Menu.scss: -------------------------------------------------------------------------------- 1 | .UUI-Menu-Root { 2 | display: inline-block; 3 | min-width: 8rem; 4 | 5 | .UUI-MenuItem-Root { 6 | display: flex; 7 | flex-direction: column; 8 | align-items: flex-start; 9 | justify-content: center; 10 | min-height: 1.75rem; 11 | padding: 0 1rem; 12 | } 13 | 14 | .UUI-MenuButton-Root { 15 | min-height: 1.75rem; 16 | width: 100%; 17 | 18 | .UUI-Button-Root { 19 | width: 100%; 20 | border: none; 21 | border-radius: inherit; 22 | box-shadow: none; 23 | color: #333333; 24 | justify-content: flex-start; 25 | 26 | &:hover, &:focus { 27 | color: #333333; 28 | } 29 | 30 | &:hover { 31 | background-color: #f5f5f5; 32 | } 33 | 34 | &:disabled { 35 | background-color: white; 36 | color: #999999; 37 | 38 | cursor: not-allowed; 39 | } 40 | } 41 | } 42 | 43 | .UUI-MenuSeparator-Root { 44 | background-color: #e2e6e9; 45 | height: 1px; 46 | margin: 5px auto; 47 | } 48 | } -------------------------------------------------------------------------------- /src/styles/components/NumberField.scss: -------------------------------------------------------------------------------- 1 | 2 | .UUI-NumberField-Root { 3 | display: flex; 4 | flex-direction: row; 5 | align-items: center; 6 | width: 100%; 7 | height: 32px; 8 | box-sizing: border-box; 9 | 10 | overflow: hidden; 11 | color: $textDarkColor; 12 | 13 | @include border; 14 | @include radius; 15 | @include hoverBorder; 16 | @include focusWithinShadow; 17 | @include focusWithinBorder; 18 | 19 | .UUI-NumberField-Input { 20 | width: 100%; 21 | height: 100%; 22 | padding: 0.5rem; 23 | box-sizing: border-box; 24 | flex: 1; 25 | } 26 | 27 | .UUI-NumberField-LoadingSpinner { 28 | padding: 0.5rem; 29 | color: gray; 30 | } 31 | } -------------------------------------------------------------------------------- /src/styles/components/Page.scss: -------------------------------------------------------------------------------- 1 | .UUI-Page { 2 | &-Root { 3 | max-width: 800px; 4 | margin: 0 auto; 5 | } 6 | 7 | &-Header { 8 | display: flex; 9 | flex-direction: column; 10 | padding: 1rem 0; 11 | // border-bottom: 1px solid #dfe3e8; 12 | } 13 | 14 | &-HeaderWrapper { 15 | display: flex; 16 | flex-direction: row; 17 | justify-content: space-between; 18 | align-items: center; 19 | } 20 | 21 | &-InfoWrapper { 22 | display: flex; 23 | flex-direction: row; 24 | align-items: center; 25 | } 26 | 27 | &-Thumbnail { 28 | width: 100px; 29 | display: flex; 30 | align-items: center; 31 | margin-right: 1rem; 32 | } 33 | 34 | &-Title { 35 | margin: 0; 36 | font-size: 2rem; 37 | } 38 | 39 | &-Description { 40 | margin: 0; 41 | font-size: 1rem; 42 | color: #212b36; 43 | font-weight: 400; 44 | } 45 | 46 | &-PrimaryActions { 47 | display: flex; 48 | flex-direction: row; 49 | justify-content: flex-end; 50 | } 51 | 52 | &-SecondaryActions { 53 | margin-top: 0.5rem; 54 | display: flex; 55 | flex-direction: row; 56 | justify-content: flex-start; 57 | } 58 | 59 | &-Container { 60 | margin: 2rem 0; 61 | } 62 | } 63 | 64 | .UUI-PageSection-Root, .UUI-PageAnnotatedSection-Root { 65 | border-top: 1px solid #dfe3e8; 66 | margin-top: 1rem; 67 | padding-top: 1rem; 68 | } 69 | 70 | .UUI-PageAnnotatedSection { 71 | &-Root { 72 | display: flex; 73 | flex-direction: row; 74 | align-items: flex-start; 75 | } 76 | 77 | &-InfoWrapper { 78 | width: 300px; 79 | padding: 1rem 1rem 1rem 0; 80 | } 81 | 82 | &-Title { 83 | margin: 0; 84 | } 85 | 86 | &-Description { 87 | margin: 0; 88 | margin-top: 1rem; 89 | color: #637381; 90 | } 91 | 92 | &-Container { 93 | flex: 1; 94 | padding: 1rem 0 1rem 1rem; 95 | } 96 | } -------------------------------------------------------------------------------- /src/styles/components/Pagination.scss: -------------------------------------------------------------------------------- 1 | 2 | .UUI-Pagination-Root { 3 | display: flex; 4 | flex-direction: row; 5 | align-items: center; 6 | } 7 | 8 | .UUI-PageList-Root { 9 | display: flex; 10 | flex-direction: row; 11 | 12 | .UUI-PageList-PageButton { 13 | padding: 0.5rem 0.25rem; 14 | min-width: 3em; 15 | margin-left: 0.25rem; 16 | margin-right: 0.25rem; 17 | 18 | &[data-uui-active="true"] { 19 | border: 1px solid $primaryColor; 20 | color: $primaryColor; 21 | } 22 | } 23 | } 24 | 25 | .UUI-PagePrevButton-Root, 26 | .UUI-PageNextButton-Root { 27 | margin: 0 0.25rem; 28 | padding: 0.5rem 0.25rem; 29 | min-width: 3em; 30 | } 31 | 32 | .UUI-PagePrevButton-Root { 33 | display: flex; 34 | justify-content: center; 35 | align-items: center; 36 | margin-right: 0.25rem; 37 | } 38 | 39 | .UUI-PageNextButton-Root { 40 | display: flex; 41 | justify-content: center; 42 | align-items: center; 43 | margin-left: 0.25rem; 44 | } 45 | 46 | .UUI-PageJumper-Root { 47 | display: flex; 48 | flex-direction: row; 49 | margin-left: 0.5rem; 50 | margin-right: 0.5rem; 51 | align-items: center; 52 | 53 | .UUI-PageJumper-Label { 54 | flex-wrap: nowrap; 55 | padding-right: 0.25rem; 56 | } 57 | } 58 | 59 | .UUI-PageInfo-Root { 60 | display: flex; 61 | flex-direction: row; 62 | margin-left: 0.5rem; 63 | margin-right: 0.5rem; 64 | } 65 | -------------------------------------------------------------------------------- /src/styles/components/Popover.scss: -------------------------------------------------------------------------------- 1 | 2 | .UUI-Popover-Root { 3 | display: inline-block; 4 | 5 | .UUI-Popover-Activator { 6 | height: 100%; 7 | } 8 | } 9 | 10 | .UUI-Popover-Content { 11 | @include border; 12 | @include shadow; 13 | @include radius; 14 | 15 | z-index: 9999; 16 | background-color: white; 17 | 18 | transition: opacity 0s ease-in-out; 19 | 20 | display: none; 21 | visibility: hidden; 22 | pointer-events: none; 23 | 24 | &[data-uui-active="true"] { 25 | transition: opacity 0.1s ease-in-out; 26 | 27 | display: inherit; 28 | visibility: inherit; 29 | pointer-events: inherit; 30 | } 31 | } -------------------------------------------------------------------------------- /src/styles/components/Radio.scss: -------------------------------------------------------------------------------- 1 | 2 | .UUI-Radio-Root { 3 | display: inline-flex; 4 | flex-direction: row; 5 | align-items: center; 6 | cursor: pointer; 7 | 8 | &:focus-within .UUI-Radio-Indicator { 9 | box-shadow: lighten($primaryColor, 30%) 0 0 0 3px; 10 | } 11 | 12 | .UUI-Radio-Input { 13 | width: 0; 14 | height: 0; 15 | opacity: 0; 16 | } 17 | 18 | .UUI-Radio-Indicator { 19 | position: relative; 20 | top: 0; 21 | left: 0; 22 | display: block; 23 | width: 16px; 24 | height: 16px; 25 | background-color: #fff; 26 | border: 1px solid $borderColor; 27 | border-radius: 2px; 28 | border-collapse: separate; 29 | border-radius: 50%; 30 | } 31 | 32 | .UUI-Radio-Label { 33 | padding-left: 0.5rem; 34 | } 35 | 36 | &[data-uui-disabled="true"] { 37 | cursor: not-allowed; 38 | } 39 | 40 | .UUI-Radio-Input:checked { 41 | 42 | & + .UUI-Radio-Indicator { 43 | background-color: white; 44 | border-color: $primaryColor; 45 | } 46 | 47 | & + .UUI-Radio-Indicator:after { 48 | position: absolute; 49 | content: ''; 50 | width: 9px; 51 | height: 9px; 52 | background-color: $primaryColor; 53 | position: absolute; 54 | top: 50%; 55 | left: 50%; 56 | transform: translate(-50%,-50%); 57 | border-radius: 50%; 58 | } 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/styles/components/RadioGroup.scss: -------------------------------------------------------------------------------- 1 | .UUI-RadioGroup-Root { 2 | display: flex; 3 | flex-direction: row; 4 | align-items: center; 5 | } -------------------------------------------------------------------------------- /src/styles/components/RightClickZone.scss: -------------------------------------------------------------------------------- 1 | .UUI-RightClickZone-Root { 2 | position: relative; 3 | .UUI-RightClickZone-Origin { 4 | position: absolute; 5 | top: 0; 6 | left: 0; 7 | width: 0; 8 | height: 0; 9 | } 10 | } 11 | 12 | .UUI-RightClickZone-ContextMenu { 13 | display: none; 14 | 15 | &[data-uui-active="true"] { 16 | display: inherit; 17 | } 18 | 19 | padding: 0.25rem; 20 | background-color: white; 21 | border-radius: 4px; 22 | box-shadow: 0px 6px 20px rgba(0, 0, 0, 0.2); 23 | } -------------------------------------------------------------------------------- /src/styles/components/SegmentControl.scss: -------------------------------------------------------------------------------- 1 | .UUI-SegmentControl-Root { 2 | display: inline-block; 3 | padding: 4px; 4 | border-radius: $borderRadius * 2; 5 | background-color: $backgroundColor; 6 | min-width: 200px; 7 | height: 32px; 8 | box-sizing: border-box; 9 | border: 1px solid transparent; 10 | 11 | @include focusWithinShadow; 12 | @include focusWithinBorder; 13 | 14 | .UUI-SegmentControl-Container { 15 | position: relative; 16 | display: flex; 17 | flex-direction: row; 18 | height: 100%; 19 | } 20 | 21 | .UUI-SegmentControl-Thumb { 22 | position: absolute; 23 | top: 0; 24 | bottom: 0; 25 | width: 0; 26 | margin-left: 0; 27 | background-color: white; 28 | border-radius: $borderRadius; 29 | z-index: 1; 30 | box-sizing: border-box; 31 | 32 | @include shadow; 33 | 34 | transition: margin 0.2s ease-in-out; 35 | } 36 | 37 | .UUI-SegmentControl-Option { 38 | flex-basis: 100%; 39 | display: flex; 40 | justify-content: center; 41 | align-items: center; 42 | background-color: transparent; 43 | z-index: 2; 44 | cursor: pointer; 45 | margin: 0 4px; 46 | overflow: hidden; 47 | text-overflow: ellipsis; 48 | white-space: nowrap; 49 | font-size: 14px; 50 | height: 100%; 51 | 52 | color: #666666; 53 | 54 | &[data-uui-active="true"] { 55 | color: $textDarkColor; 56 | } 57 | 58 | transition: color 0.2s ease-in-out; 59 | } 60 | } -------------------------------------------------------------------------------- /src/styles/components/Skeleton.scss: -------------------------------------------------------------------------------- 1 | 2 | .UUI-Skeleton-Root { 3 | margin: 8px; 4 | 5 | @keyframes SkeletonAnimation { 6 | 0% { 7 | opacity: 0.45; 8 | } 9 | 100% { 10 | opacity: 0.9; 11 | } 12 | } 13 | 14 | 15 | .UUI-SkeletonParagraph-Line, .UUI-SkeletonTitle-Root, .UUI-SkeletonPicture-Root { 16 | margin-bottom: 16px; 17 | width: 100%; 18 | background: #dfe3e8; 19 | animation: SkeletonAnimation 1s linear infinite alternate; 20 | } 21 | 22 | .UUI-SkeletonParagraph-Line { 23 | height: 16px; 24 | 25 | &:last-child:not(:first-child):not(:nth-child(2)) { 26 | width: 80%; 27 | margin-bottom: none; 28 | } 29 | } 30 | 31 | .UUI-SkeletonPicture-Root { 32 | margin-right: 16px; 33 | width: 360px; 34 | height: 270px; 35 | } 36 | 37 | .UUI-SkeletonTitle-Root { 38 | width: 44%; 39 | height: 26px; 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /src/styles/components/Slider.scss: -------------------------------------------------------------------------------- 1 | 2 | $ThumbRadius: 8px; 3 | $ThumbBorderWidth: 2px; 4 | $RemarkRadius: 4px; 5 | $RemarkBorderWidth: 2px; 6 | 7 | .UUI-Slider-Root { 8 | width: 400px; 9 | height: 20px; 10 | 11 | .UUI-Slider-Container { 12 | position: relative; 13 | display: flex; 14 | flex-direction: row; 15 | align-items: center; 16 | height: 100%; 17 | cursor: pointer; 18 | 19 | margin-left: $ThumbRadius; 20 | margin-right: $ThumbRadius; 21 | 22 | .UUI-Slider-ActiveLine, .UUI-Slider-InactiveLine { 23 | height: 4px; 24 | background-color: rgb(200, 198, 196); 25 | } 26 | 27 | .UUI-Slider-ActiveLine { 28 | background-color: $primaryColor; 29 | } 30 | 31 | .UUI-Slider-Thumb { 32 | position: absolute; 33 | width: $ThumbRadius * 2; 34 | height: $ThumbRadius * 2; 35 | background-color: white; 36 | border-radius: 50%; 37 | border: $ThumbBorderWidth solid $primaryColor; 38 | cursor: pointer; 39 | 40 | &:focus { 41 | border-color: darken($primaryColor, 2%); 42 | @include activeShadow; 43 | transition: box-shadow 0.2s ease-in-out; 44 | } 45 | } 46 | 47 | .UUI-Slider-Remark { 48 | position: absolute; 49 | width: $RemarkRadius * 2; 50 | height: $RemarkRadius * 2; 51 | background-color: white; 52 | border-radius: 50%; 53 | border: $RemarkBorderWidth solid rgb(200, 198, 196); 54 | 55 | &[data-uui-active="true"] { 56 | border: $RemarkBorderWidth solid $primaryColor; 57 | } 58 | } 59 | 60 | .UUI-Slider-RemarkLabel { 61 | position: absolute; 62 | top: $RemarkRadius * 2; 63 | margin-top: 2px; 64 | transform: translateX(-1px); 65 | } 66 | } 67 | 68 | &[data-uui-disabled="true"] { 69 | .UUI-Slider-Thumb { 70 | cursor: not-allowed; 71 | border: 2px solid #7d7d7d; 72 | } 73 | .UUI-Slider-ActiveLine { 74 | background-color: #7d7d7d; 75 | } 76 | } 77 | 78 | &[data-uui-vertical="true"] { 79 | height: 300px; 80 | width: 20px; 81 | 82 | .UUI-Slider-Container { 83 | flex-direction: column; 84 | } 85 | 86 | .UUI-Slider-ActiveLine, .UUI-Slider-InactiveLine { 87 | width: 4px; 88 | } 89 | 90 | .UUI-Slider-RemarkLabel { 91 | margin-top: 0; 92 | top: $RemarkRadius; 93 | left: 20px; 94 | transform: translateY(-50%); 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /src/styles/components/Switch.scss: -------------------------------------------------------------------------------- 1 | 2 | .UUI-Switch-Root { 3 | display: inline-block; 4 | 5 | .UUI-Switch-Button { 6 | width: 52px; 7 | height: 26px; 8 | border-radius: 99999px; 9 | padding-top: 0; 10 | padding-bottom: 0; 11 | padding-left: 2px; 12 | padding-right: 2px; 13 | background-color: $borderColor; 14 | 15 | .UUI-Button-Content { 16 | display: inline-block; 17 | } 18 | 19 | &:hover { 20 | border: none; 21 | } 22 | } 23 | 24 | .UUI-Switch-Thumb { 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | height: 20px; 29 | width: 20px; 30 | border-radius: 99999px; 31 | background-color: white; 32 | margin-left: calc(50% - 22px); 33 | transition: margin 0.2s ease-in-out; 34 | border: none; 35 | } 36 | 37 | .UUI-Switch-LoadingSpinner { 38 | color: gray; 39 | } 40 | 41 | &[data-uui-checked="true"] { 42 | .UUI-Switch-Button { 43 | background-color: $primaryColor; 44 | } 45 | 46 | .UUI-Switch-Thumb { 47 | margin-left: calc(50% + 2px); 48 | background-color: white; 49 | } 50 | 51 | .UUI-Switch-LoadingSpinner { 52 | color: $primaryColor; 53 | } 54 | } 55 | 56 | &[data-uui-disabled="true"] { 57 | opacity: .6; 58 | 59 | .UUI-Switch-Button { 60 | cursor: not-allowed; 61 | &:focus-within { 62 | box-shadow: none; 63 | } 64 | &:hover, &:active, &:focus { 65 | border: none; 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/styles/components/Table.scss: -------------------------------------------------------------------------------- 1 | 2 | .UUI-Table-Root { 3 | 4 | .UUI-Table-Table { 5 | width: 100%; 6 | border: 1px solid $borderColor; 7 | border-collapse: collapse; 8 | } 9 | 10 | .UUI-Table-Row { 11 | &:hover { 12 | background-color: #fafafa; 13 | } 14 | } 15 | 16 | .UUI-Table-HeadCell, 17 | .UUI-Table-DataCell { 18 | border: 1px solid $borderColor; 19 | padding: 0.75rem; 20 | } 21 | 22 | .UUI-Table-HeadCell[data-uui-column="selection"], 23 | .UUI-Table-DataCell[data-uui-column="selection"] { 24 | width: 1px; 25 | 26 | .UUI-Checkbox-Root { 27 | display: flex; 28 | } 29 | } 30 | 31 | .UUI-Table-HeadCell { 32 | color: rgba(0,0,0,.85); 33 | text-align: left; 34 | background-color: #fafafa; 35 | } 36 | .UUI-Table-DataCell { 37 | color: rgba(0,0,0,.65); 38 | } 39 | 40 | .UUI-Table-EmptyView { 41 | height: 10rem; 42 | display: flex; 43 | align-items: center; 44 | justify-content: center; 45 | } 46 | 47 | &[data-uui-empty="true"] { 48 | .UUI-Table-Row:hover, .UUI-Table-DataCell:hover { 49 | background-color: white; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/styles/components/Tag.scss: -------------------------------------------------------------------------------- 1 | 2 | .UUI-Tag-Root { 3 | display: inline-flex; 4 | justify-content: center; 5 | align-items: center; 6 | align-content: center; 7 | background-color: #718096; 8 | height: 32px; 9 | padding: 0 0.5rem; 10 | border-radius: 0.25rem; 11 | 12 | .UUI-Tag-Content { 13 | color: white; 14 | } 15 | 16 | &[data-uui-interactive="true"] { 17 | cursor: pointer; 18 | user-select: none; 19 | } 20 | 21 | &[data-uui-interactive="true"]:hover { 22 | background-color: rgba(92,112,128,.85); 23 | } 24 | } -------------------------------------------------------------------------------- /src/styles/components/TextArea.scss: -------------------------------------------------------------------------------- 1 | 2 | .UUI-TextArea-Root { 3 | position: relative; 4 | display: flex; 5 | flex-direction: row; 6 | align-items: center; 7 | width: 100%; 8 | box-sizing: border-box; 9 | 10 | overflow: hidden; 11 | color: $textDarkColor; 12 | 13 | @include border; 14 | @include radius; 15 | @include hoverBorder; 16 | @include focusWithinShadow; 17 | @include focusWithinBorder; 18 | 19 | .UUI-TextArea-Textarea { 20 | width: 100%; 21 | padding: 0.5rem; 22 | flex: 1; 23 | align-self: stretch; 24 | min-height: 120px; 25 | } 26 | 27 | &[data-uui-has-indicator="true"] { 28 | .UUI-TextArea-Textarea { 29 | padding-bottom: 2rem; 30 | } 31 | } 32 | .UUI-TextArea-Info { 33 | position: absolute; 34 | right: 0.5rem; 35 | bottom: 0.5rem; 36 | display: flex; 37 | align-items: center; 38 | } 39 | 40 | .UUI-TextArea-LengthIndicator { 41 | padding-left: 0.5rem; 42 | color: gray; 43 | } 44 | 45 | .UUI-TextArea-LoadingSpinner { 46 | color: gray; 47 | } 48 | } -------------------------------------------------------------------------------- /src/styles/components/TextField.scss: -------------------------------------------------------------------------------- 1 | 2 | .UUI-TextField-Root { 3 | display: flex; 4 | flex-direction: row; 5 | align-items: center; 6 | width: 100%; 7 | height: 32px; 8 | box-sizing: border-box; 9 | 10 | overflow: hidden; 11 | color: $textDarkColor; 12 | 13 | @include border; 14 | @include radius; 15 | @include hoverBorder; 16 | @include focusWithinShadow; 17 | @include focusWithinBorder; 18 | 19 | .UUI-TextField-Input { 20 | width: 100%; 21 | padding: .5rem; 22 | flex: 1; 23 | &::placeholder { 24 | color: lightgray; 25 | } 26 | } 27 | 28 | .UUI-TextField-LoadingSpinner, .UUI-TextField-LengthIndicator { 29 | padding: 0.5rem; 30 | color: gray; 31 | } 32 | 33 | .UUI-TextField-TogglePasswordVisibleButton { 34 | display: flex; 35 | justify-content: center; 36 | align-items: center; 37 | padding: 0.5rem; 38 | border: none; 39 | background-color: white; 40 | border: none; 41 | color: $textDarkColor; 42 | } 43 | 44 | transition: border, box-shadow 0.2s ease-in-out; 45 | } -------------------------------------------------------------------------------- /src/styles/components/Toaster.scss: -------------------------------------------------------------------------------- 1 | 2 | .UUI-Toaster-Portal { 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | right: 0; 7 | bottom: 0; 8 | box-sizing: border-box; 9 | pointer-events: none; 10 | } 11 | 12 | .UUI-Toaster-Root { 13 | pointer-events: none; 14 | z-index: 999; 15 | padding: 0.5rem; 16 | height: 100%; 17 | box-sizing: border-box; 18 | display: flex; 19 | flex-direction: column; 20 | 21 | &[data-uui-position="top"] { 22 | align-items: center; 23 | } 24 | &[data-uui-position="top_left"] { 25 | align-items: flex-start; 26 | } 27 | &[data-uui-position="top_right"] { 28 | align-items: flex-end; 29 | } 30 | &[data-uui-position="bottom"] { 31 | align-items: center; 32 | justify-content: flex-end; 33 | } 34 | &[data-uui-position="bottom_left"] { 35 | align-items: flex-start; 36 | justify-content: flex-end; 37 | } 38 | &[data-uui-position="bottom_right"] { 39 | align-items: flex-end; 40 | justify-content: flex-end; 41 | } 42 | &[data-uui-position="center_left"] { 43 | align-items: flex-start; 44 | justify-content: center; 45 | } 46 | &[data-uui-position="center_right"] { 47 | align-items: flex-end; 48 | justify-content: center; 49 | } 50 | &[data-uui-position="center"] { 51 | align-items: center; 52 | justify-content: center; 53 | } 54 | } 55 | 56 | .UUI-Toast-Root { 57 | width: fit-content; 58 | pointer-events: all; 59 | padding: 0.5rem 0.75rem; 60 | margin: 0.5rem 0.5rem; 61 | background-color: white; 62 | @include border; 63 | @include shadow; 64 | @include radius; 65 | } -------------------------------------------------------------------------------- /src/styles/components/Tooltip.scss: -------------------------------------------------------------------------------- 1 | .UUI-Tooltip-Root { 2 | position: relative; 3 | display: inline-block; 4 | 5 | .UUI-Tooltip-Tip { 6 | visibility: hidden; 7 | background: #333; 8 | color: white; 9 | font-weight: bold; 10 | padding: 4px 8px; 11 | font-size: 13px; 12 | border-radius: 4px; 13 | } 14 | 15 | &:hover { 16 | .UUI-Tooltip-Tip { 17 | visibility: inherit; 18 | } 19 | } 20 | .UUI-Tooltip-Arrow { 21 | text-align: left; 22 | } 23 | .UUI-Tooltip-Arrow, 24 | .UUI-Tooltip-Arrow::before { 25 | position: absolute; 26 | width: 8px; 27 | height: 8px; 28 | z-index: -1; 29 | } 30 | 31 | .UUI-Tooltip-Arrow::before { 32 | content: ''; 33 | transform: rotate(45deg); 34 | background: #333; 35 | } 36 | 37 | .UUI-Tooltip-Tip[data-popper-placement^="top"] > .UUI-Tooltip-Arrow { 38 | bottom: -4px; 39 | } 40 | 41 | .UUI-Tooltip-Tip[data-popper-placement^="bottom"] > .UUI-Tooltip-Arrow { 42 | top: -4px; 43 | } 44 | 45 | .UUI-Tooltip-Tip[data-popper-placement^="left"] > .UUI-Tooltip-Arrow { 46 | right: -4px; 47 | } 48 | 49 | .UUI-Tooltip-Tip[data-popper-placement^="right"] > .UUI-Tooltip-Arrow { 50 | left: -4px; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/styles/global.scss: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,Helvetica Neue,Arial,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji; 3 | line-height: 1.5; 4 | } 5 | 6 | input, textarea, select { 7 | border: none; 8 | } 9 | 10 | *, *:focus, *:active { 11 | outline: none; 12 | } -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './base.scss'; 2 | 3 | @import './components.scss'; -------------------------------------------------------------------------------- /src/styles/mixins.scss: -------------------------------------------------------------------------------- 1 | @import "./variables.scss"; 2 | 3 | @mixin radius { 4 | border-radius: $borderRadius; 5 | } 6 | 7 | @mixin border { 8 | border: 1px solid $borderColor; 9 | } 10 | @mixin hoverBorder { 11 | &:hover { 12 | border: 1px solid lighten($primaryColor, 10%); 13 | } 14 | } 15 | @mixin activeBorder { 16 | &:active { 17 | border: 1px solid $primaryColor; 18 | } 19 | } 20 | @mixin focusWithinBorder { 21 | &:focus-within { 22 | border: 1px solid $primaryColor; 23 | } 24 | } 25 | 26 | @mixin shadow { 27 | box-shadow: 0 1px 3px 0 rgba(0,0,0,.1), 28 | 0 1px 2px 0 rgba(0,0,0,.06); 29 | } 30 | @mixin activeShadow { 31 | box-shadow: rgba($primaryColor, 0.16) 0 0 0 3px; 32 | } 33 | @mixin focusWithinShadow { 34 | &:focus-within { 35 | @include activeShadow; 36 | } 37 | transition: box-shadow 0.2s ease-in-out; 38 | } 39 | 40 | @mixin borderless { 41 | border: none; 42 | 43 | &:hover, &:focus, &:focus-within { 44 | border: none; 45 | box-shadow: none; 46 | } 47 | } -------------------------------------------------------------------------------- /src/styles/variables.scss: -------------------------------------------------------------------------------- 1 | $primaryColor: #409EFF !default; 2 | $textLightColor: white !default; 3 | $textDarkColor: #333333 !default; 4 | $textDarkColor2: #666666 !default; 5 | $borderColor: #d9d9d9 !default; 6 | $backgroundColor: #d9d9d9 !default; 7 | 8 | $hoverBackgroundColor: #f5f5f5 !default; 9 | $activeBackgroundColor: lighten($primaryColor, 25%) !default; 10 | $focusBackgroundColor: #f6f6f6 !default; 11 | 12 | $disabledBorderColor: #dadada !default; 13 | $disabledBackgroundColor: #f6f6f6 !default; 14 | $disabledTextColor: rgba(0, 0, 0, 0.4) !default; 15 | 16 | $borderRadius: 0.25rem !default; 17 | -------------------------------------------------------------------------------- /src/utils/ReactHelper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | /** 4 | * The join() method creates and returns a new ReactNode by concatenating all of the elements in an array, separated by a specified separator element. 5 | * If the array has only one item, then that item will be returned without using the separator. 6 | */ 7 | const join = (nodes: React.ReactNode[], separator: React.ReactNode) => { 8 | return nodes.map((node, index) => { 9 | return ( 10 | 11 | {node} 12 | {index !== nodes.length - 1 && separator} 13 | 14 | ) 15 | }) 16 | } 17 | 18 | /** 19 | * in SSR (Server-Side Render), global `window` could be undefined. 20 | */ 21 | const _window: (typeof window) | undefined = ((typeof window !== 'undefined') ? window : undefined) 22 | /** 23 | * in SSR (Server-Side Render), global `document` could be undefined. 24 | */ 25 | const _document: (typeof document) | undefined = (typeof document !== 'undefined') ? document : undefined 26 | 27 | 28 | const ReactHelper = { 29 | join, 30 | window: _window, 31 | document: _document, 32 | } 33 | 34 | export default ReactHelper; 35 | -------------------------------------------------------------------------------- /src/utils/componentHelper.ts: -------------------------------------------------------------------------------- 1 | import React, { Children } from "react"; 2 | 3 | /** 4 | * Returns true if the given JSX element matches the given component type. 5 | * 6 | * NOTE: This function only checks equality of `displayName` for performance and 7 | * to tolerate multiple minor versions of a component being included in one 8 | * application bundle. 9 | * @param ComponentType desired component type of element 10 | * @param element JSX element in question 11 | */ 12 | // export function isElementOfType

(ComponentType: React.ComponentType

, element: any): element is React.ComponentType

{ 13 | // return ( 14 | // element != null && 15 | // element.type != null && 16 | // element.type.displayName != null && 17 | // element.type.displayName === ComponentType.displayName 18 | // ); 19 | // } 20 | 21 | export function isElementOfType

(ComponentType: React.ComponentType

, element: any): element is React.ReactElement

{ 22 | return ( 23 | element != null && 24 | element.type != null && 25 | element.type.displayName != null && 26 | element.type.displayName === ComponentType.displayName 27 | ); 28 | } 29 | 30 | export function getValidTypeChildren

(ComponentType: React.ComponentType

, children: React.ReactNode) { 31 | return Children.toArray(children).filter((child): child is React.ReactElement

=> isElementOfType(ComponentType, child)) 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/utils/createGroupedComponent.ts: -------------------------------------------------------------------------------- 1 | export function createGroupedComponent< 2 | M extends any, 3 | K extends string | number, 4 | X extends any, 5 | P extends { [key in K]: X }, 6 | >(MainComponent: M, SubComponents: P) { 7 | for (const [key, sub] of Object.entries(SubComponents)) { 8 | (MainComponent as any)[key] = sub 9 | } 10 | return MainComponent as M & P 11 | } -------------------------------------------------------------------------------- /src/utils/createPropTypes.ts: -------------------------------------------------------------------------------- 1 | import { UUIComponentFeaturePropTypes } from '../core/modules/UUIComponentPropTypes'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export { PropTypes } 5 | 6 | export function createComponentPropTypes< 7 | T extends { [key in string]: any } 8 | >(data: UUIComponentFeaturePropTypes) { 9 | return data 10 | } 11 | 12 | function RecursiveShapeType( 13 | shape: S, 14 | childAttr: A, 15 | mapper = >>(shape: X) => PropTypes.arrayOf(shape), 16 | ) { 17 | const children = mapper(PropTypes.shape(shape)); 18 | (shape as any)[childAttr] = children; 19 | const tagPropTypes = PropTypes.shape(shape); 20 | return tagPropTypes as PropTypes.Requireable> 21 | } 22 | 23 | function NullableType(propType: any) { 24 | return (props: any, propName: any, ...rest: any) => props[propName] === null ? null : propType(props, propName, ...rest); 25 | } 26 | 27 | const NullType = PropTypes.oneOf([null]) 28 | 29 | export const ExtraPropTypes = { 30 | null: NullType, 31 | nullable: NullableType, 32 | recursiveShape: RecursiveShapeType, 33 | } -------------------------------------------------------------------------------- /src/utils/keyboardHelper.tsx: -------------------------------------------------------------------------------- 1 | export enum KeyCode { 2 | Enter = 13, 3 | Escape = 27, 4 | SpaceBar = 32, 5 | PageUp = 33, 6 | PageDown = 34, 7 | End = 35, 8 | Home = 36, 9 | ArrowLeft = 37, 10 | ArrowUp = 38, 11 | ArrowRight = 39, 12 | ArrowDown = 40, 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/utils/moneyHelper.ts: -------------------------------------------------------------------------------- 1 | export function formatMoney(value: number, options?: { 2 | symbol?: string; 3 | precision?: number; 4 | thousand?: string; 5 | decimal?: string; 6 | }) { 7 | const finalOptions = { 8 | symbol: options?.symbol === undefined ? '$' : options?.symbol, 9 | thousand: options?.thousand || ',', 10 | decimal: options?.decimal || '.', 11 | precision: options?.precision === undefined ? 2 : options.precision, 12 | } 13 | const regex = `\\d(?=(\\d{${3}})+${finalOptions.precision > 0 ? '\\D' : '$'})` 14 | let result = value.toFixed(Math.max(0, ~~finalOptions.precision)) 15 | result = finalOptions.decimal ? result.replace('.', finalOptions.decimal) : result 16 | result = result.replace(new RegExp(regex, 'g'), `$&${finalOptions.thousand}`) 17 | result = `${finalOptions.symbol}${result}` 18 | return result 19 | } -------------------------------------------------------------------------------- /src/utils/numberHelper.ts: -------------------------------------------------------------------------------- 1 | import { clamp } from "lodash-es" 2 | 3 | export function limitPrecision(value: string, precision?: number) { 4 | if (precision === undefined) return value 5 | const dotIndex = value.indexOf('.') 6 | if (dotIndex === -1) return value 7 | return value.slice(0, dotIndex + (precision === 0 ? 0 : 1 + precision)) 8 | } 9 | export function limitRange(value: number, min?: number, max?: number) { 10 | return clamp(value, min === undefined ? Number.NEGATIVE_INFINITY : min, max === undefined ? Number.POSITIVE_INFINITY : max) 11 | } 12 | 13 | export type NumberAbbrUnit = 'k' | 'K' | 'm' | 'M' | 'b' | 'B'; 14 | export const NumberAbbrUnitValue: { 15 | [key in NumberAbbrUnit]: number; 16 | } = { 17 | 'k': 1000, 18 | 'K': 1000, 19 | 'm': 1000_000, 20 | 'M': 1000_000, 21 | 'b': 1000_000_000, 22 | 'B': 1000_000_000, 23 | }; 24 | export function numberAbbr(value: number, unit: NumberAbbrUnit, maxPrecision = 2) { 25 | const precisionNumber = Math.pow(10, Math.round(clamp(maxPrecision, 0, Number.MAX_SAFE_INTEGER))) 26 | return Math.round(value / NumberAbbrUnitValue[unit] * precisionNumber) / precisionNumber 27 | } -------------------------------------------------------------------------------- /stories/assets/cookies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackPlan/UUI/31ddbb5da92748a2192f53c94b8f06282283a969/stories/assets/cookies.png -------------------------------------------------------------------------------- /stories/assets/donuts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackPlan/UUI/31ddbb5da92748a2192f53c94b8f06282283a969/stories/assets/donuts.png -------------------------------------------------------------------------------- /stories/assets/email.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stories/assets/hotdog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackPlan/UUI/31ddbb5da92748a2192f53c94b8f06282283a969/stories/assets/hotdog.png -------------------------------------------------------------------------------- /stories/assets/pudding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackPlan/UUI/31ddbb5da92748a2192f53c94b8f06282283a969/stories/assets/pudding.png -------------------------------------------------------------------------------- /stories/assets/strawberry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackPlan/UUI/31ddbb5da92748a2192f53c94b8f06282283a969/stories/assets/strawberry.png -------------------------------------------------------------------------------- /stories/assets/sushi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackPlan/UUI/31ddbb5da92748a2192f53c94b8f06282283a969/stories/assets/sushi.png -------------------------------------------------------------------------------- /stories/components/Accordion.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks'; 2 | import { Accordion } from '../../src'; 3 | import { useState } from 'react'; 4 | import { Icons } from '../../src/icons/Icons'; 5 | 6 | 7 | 8 | # 手风琴 Accordion 9 | 10 | ### 简单使用 11 | 12 | 13 | 14 | 15 | 原子性 Atomicity} test={"asdasd"}> 16 | 一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。 17 | 18 | 一致性 Consistency}> 19 | 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。 20 | 21 | 22 | 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提交读(Read uncommitted)、提交读(read committed)、可重复读(repeatable read)和串行化(Serializable)。 23 | 24 | 25 | 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。 26 | 27 | 28 | 29 | 30 | 31 | ### 键盘控制 32 | 33 | * `Enter` `Space` 当焦点位于手风琴面板的标题上时,展开或折叠关联的面板。如果实现只允许一个面板扩展,而另一面板扩展,则折叠该面板。 34 | * `Tab` 将焦点移到下一个可聚焦元素;手风琴中所有可聚焦的元素都包含在Tab序列页面中。 35 | * `Shift + Tab` 将焦点移至上一个可聚焦元素;手风琴中所有可聚焦的元素都包含在Tab序列页面中。 36 | * `Down Arrow` 如果焦点位于手风琴标题上,则将焦点移到下一个手风琴标题上。如果焦点位于最后一个手风琴头上,则不执行任何操作或将焦点移至第一个手风琴头上。 37 | * `Down Arrow` 如果焦点位于手风琴标题上,则将焦点移到上一个手风琴标题上。如果焦点位于第一个手风琴头上,则不执行任何操作或将焦点移至最后一个手风琴头上。 38 | * `Home` 当焦点位于手风琴标题上时,将焦点移到第一个手风琴标题上。 39 | * `End` 当焦点位于手风琴标题上时,将焦点移到最后一个手风琴标题上。 40 | 41 | ### Props 42 | 43 | -------------------------------------------------------------------------------- /stories/components/Breadcrumb.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks'; 2 | import { Breadcrumb } from '../../src'; 3 | import { useState } from 'react'; 4 | import { Icons } from '../../src/icons/Icons'; 5 | 6 | 7 | 8 | # 面包屑 Breadcrumb 9 | 10 | 面包屑用于显示当前页面在网页应用层级结构中的位置,并且能向上级某个层级返回。 11 | 12 | ### 简单使用 13 | 14 | 15 | 16 |

Home
}, 19 | { key: 'form', label: 'Form', onAction: () => {} }, 20 | { key: 'login', label: 'Login', active: true }, 21 | ]} 22 | /> 23 | 24 | 25 | 26 | ### 键盘控制 27 | 28 | * `Tab` 将焦点移到下一个可聚焦元素。 29 | * `Shift + Tab` 将焦点移至上一个可聚焦元素。 30 | 31 | ### Props 32 | 33 | -------------------------------------------------------------------------------- /stories/components/Checkbox.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks'; 2 | import { Checkbox, CheckboxGroup } from '../../src'; 3 | import { useState } from 'react'; 4 | 5 | 6 | 7 | # 多选框 Checkbox 8 | 9 | 在一组选择项中进行多选。 10 | 11 | ### 简单使用 12 | 13 | 14 | 15 | {() => { 16 | const [flag, setFlag] = useState(false) 17 | return ( 18 | { console.log(value); setFlag(value) }} 22 | /> 23 | ) 24 | }} 25 | 26 | 27 | 28 | ### 不可用状态 29 | 30 | 31 | 32 | {() => { 33 | const [flag, setFlag] = useState(false) 34 | return ( 35 |
36 | { console.log(value); setFlag(value) }} 41 | disabled 42 | /> 43 | { console.log(value); setFlag(value) }} 48 | disabled 49 | /> 50 |
51 | ) 52 | }} 53 |
54 |
55 | 56 | ### 一组多选框 57 | 58 | 59 | 60 | {() => { 61 | const [n, setN] = useState(["apple"]) 62 | return ( 63 | { setN(value) }}> 64 | 65 | 66 | 67 | 68 | ) 69 | }} 70 | 71 | 72 | 73 | ### Props 74 | 75 | -------------------------------------------------------------------------------- /stories/components/Form.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas } from '@storybook/addon-docs/blocks'; 2 | import { FormControlledMode, FormUncontrolledMode } from './Form.stories.tsx'; 3 | 4 | 5 | 6 | # 表单 Form 7 | 8 | 表单相关的组件同时支持受控模式和非受控模式。 9 | 10 | ### Controlled mode 11 | 12 | 13 | {FormControlledMode()} 14 | 15 | 16 | ### Uncontrolled mode 17 | 18 | 19 | {FormUncontrolledMode()} 20 | 21 | -------------------------------------------------------------------------------- /stories/components/Icon.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks'; 2 | import { Icon } from '../../src'; 3 | import { IconSource, IconSvgProps, IconSize, IconIconGallery } from './Icon.stories.tsx'; 4 | 5 | 6 | 7 | # 图标 Icon 8 | 9 | 渲染图标工具,这个组件本身没有任何预置的图表库。 10 | 11 | > 文档演示图片来自网络: https://www.iconfont.cn/collections/detail?cid=15329 12 | 13 | 14 | ### 多种数据源导入为图标 15 | 16 | ```typescript 17 | const icon1 = require('./assets/icon_xxx.png'); 18 | import icon2, { ReactComponent as icon3 } from './assets/icon_xxx.svg'; 19 | /** 20 | * icon1: string 21 | * icon2: string 22 | * icon3: SvgrComponent 23 | */ 24 | ``` 25 | 26 | 可以打开控制台工具看看这三种方式渲染出来的 DOM 区别。 27 | 28 | 29 | {IconSource()} 30 | 31 | 32 | ### 内联 SVG 支持传入更多参数 33 | 34 | 35 | {IconSvgProps()} 36 | 37 | 38 | 39 | ### 自定义图标大小 40 | 41 | 42 | {IconSize()} 43 | 44 | 45 | ### IconGallery 46 | 47 | 使用 `IconGallery` 工具组织管理图标库 48 | 49 | 50 | {IconIconGallery()} 51 | 52 | 53 | 54 | ### Props 55 | 56 | -------------------------------------------------------------------------------- /stories/components/Layout.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks'; 2 | import { Layout, Header, Content, Footer, Sider } from '../../src'; 3 | import { useState } from 'react'; 4 | import { Layout1, Layout2, Layout3 } from './Layout.stories.tsx'; 5 | 6 | 7 | 8 | # 框架 Layout 9 | 10 | 11 | {Layout1()} 12 | 13 | 14 | 15 | {Layout2()} 16 | 17 | 18 | 19 | {Layout3()} 20 | 21 | 22 | ### Props 23 | 24 | -------------------------------------------------------------------------------- /stories/components/Layout.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Layout } from '../../src'; 3 | import { range } from 'lodash-es'; 4 | 5 | export const Layout1 = () => { 6 | return ( 7 |
8 | 9 | Header 10 | Main 11 | Footer 12 | 13 |
14 | ) 15 | } 16 | 17 | Layout1.storyName = 'Layout with Vertical Header Footer' 18 | 19 | export const Layout2 = () => { 20 | return ( 21 |
22 | 23 | 24 | {range(1, 100).map((i) => { 25 | return
nav item {i}
26 | })} 27 |
28 | 29 | {range(1, 100).map((i) => { 30 | return
content item {i}
31 | })} 32 |
33 |
34 |
35 | ) 36 | } 37 | 38 | Layout2.storyName = 'Layout with Horizontal Nav' 39 | 40 | export const Layout3 = () => { 41 | return ( 42 |
43 | 44 | Nav 45 | 46 | 47 | Header 48 | Main 49 | Footer 50 | 51 | 52 |
53 |
54 |
55 | ) 56 | } 57 | 58 | Layout3.storyName = 'Layout with Mixed' 59 | -------------------------------------------------------------------------------- /stories/components/ListBox.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks'; 2 | import { ListBox } from '../../src'; 3 | import { useState } from 'react'; 4 | 5 | 6 | 7 | # 基础列表 ListBox 8 | 9 | 10 | ### 简单使用 11 | 12 | 13 | 14 | {() => { 15 | const data = [ 16 | { name: "Ptunium" }, 17 | { name: "Plutonium" }, 18 | { name: "Americium" }, 19 | { name: "Curium" }, 20 | { name: "Berkelium" }, 21 | { name: "Californium" }, 22 | { name: "Einsteinium" }, 23 | { name: "Fermium" }, 24 | { name: "Mendelevium" }, 25 | { name: "Nobelium" }, 26 | { name: "Lawrencium" }, 27 | { name: "Rutherfordium" }, 28 | { name: "Dubnium" }, 29 | { name: "Seaborgium" }, 30 | { name: "Bohrium" }, 31 | { name: "Hassium" }, 32 | { name: "Meitnerium" }, 33 | { name: "Darmstadtium" }, 34 | { name: "Roentgenium" }, 35 | { name: "Copernicium" }, 36 | { name: "Nihonium" }, 37 | { name: "Flerovium" }, 38 | { name: "Moscovium" }, 39 | { name: "Livermorium" }, 40 | { name: "Tennessine" }, 41 | { name: "Oganesson" }, 42 | ] 43 | const items = data.map((i) => ({ id: i.name, content: i.name })) 44 | const [selectedIds, setSelectedIds] = useState([]) 45 | return ( 46 | { 53 | setSelectedIds(value) 54 | }} 55 | /> 56 | ) 57 | }} 58 | 59 | 60 | 61 | ### Props 62 | 63 | -------------------------------------------------------------------------------- /stories/components/Loading.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks'; 2 | import { LoadingSpinner, LoadingCover, Table, Switch } from '../../src'; 3 | import { useState } from 'react'; 4 | 5 | 6 | 7 | # 加载 Loading 8 | 9 | 异步操作时(比如网络获取数据)显示正在加载指示。 10 | 11 | 加载组件有两个,分别是 `LoadingSpinner` 和 `LoadingCover`。这两个组件用于不同粒度的加载显示。 12 | 13 | ### 加载指示器 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ### 加载区域 22 | 23 | 24 | 25 | {() => { 26 | const [loading, switchLoading] = useState(true) 27 | const data = [ 28 | { id: 'asdvciu', name: "Bobby P. Morton", age: "63", gender: "male", mobile: "785-481-2375", email: "morton@example.com", profession: "Doctor" }, 29 | { id: 'aserghd', name: "Levi R. Oglesby", age: "35", gender: "female", mobile: "631-285-1780", email: "oglesby@example.com", profession: "Police" }, 30 | { id: 'fgher4t', name: "John S. Cassidy", age: "19", gender: "male", mobile: "719-328-5475", email: "cassidy@example.com", profession: "Student" }, 31 | ] 32 | const columns = [ 33 | { key: 'name', title: "Name" }, 34 | { key: 'age', title: "Age" }, 35 | { key: 'gender', title: "Gender" }, 36 | { key: 'mobile', title: "Mobile" }, 37 | { key: 'email', title: "Email" }, 38 | { key: 'profession', title: "Profession" }, 39 | ] 40 | const rows = data.map((i) => { 41 | return { 42 | id: i.id, 43 | cells: [ 44 | i.name, 45 | i.age, 46 | i.gender, 47 | i.mobile, 48 | i.email, 49 | i.profession, 50 | ], 51 | } 52 | }) 53 | const onRowId = (row) => row.id 54 | return ( 55 |
56 | switchLoading(value)} /> 57 | 58 | 63 | 64 | 65 | ) 66 | }} 67 | 68 | 69 | 70 | ### Props 71 | 72 | -------------------------------------------------------------------------------- /stories/components/Menu.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks'; 2 | import { Menu, MenuButton, MenuSeparator, MenuItem, Checkbox, RadioGroup, Radio } from '../../src'; 3 | import { useState } from 'react'; 4 | 5 | 6 | 7 | # 菜单 Menu 8 | 9 | ### 简单使用 10 | 11 | 12 | 13 | {() => { 14 | const [n, setN] = useState("a") 15 | return ( 16 | 17 | Button 1 18 | Button 2 19 | 20 | Disabled Button 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | { setN(value) }}> 34 | 35 | 36 | 37 | 38 | 39 | 40 | ) 41 | }} 42 | 43 | 44 | 45 | ### Props 46 | 47 | -------------------------------------------------------------------------------- /stories/components/Overview.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas } from '@storybook/addon-docs/blocks'; 2 | 3 | 4 | 5 | # 组件总览 Overview 6 | 7 |
8 |
9 |
10 |
11 |
12 | 13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | -------------------------------------------------------------------------------- /stories/components/Page.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks'; 2 | import { Page } from '../../src'; 3 | import { Button, TextField, HTMLSelect } from '../../src'; 4 | import { useState } from 'react'; 5 | 6 | 7 | 8 | # 页面 Page 9 | 10 | ## 简单使用 11 | 12 | 13 | 14 | {() => { 15 | return ( 16 |
17 | 20 | } 21 | title={'多会测试活动'} 22 | description={'上海徐汇区东方之珠'} 23 | primaryActions={[ 24 | , 25 | 26 | ]} 27 | secondaryActions={[ 28 | , 29 | , 30 | ]} 31 | > 32 | 33 |
34 | Section Content 35 |
36 |
37 | 41 |
42 |
国家/地区
43 | 44 |
城市
45 | 46 |
47 |
48 |
49 |
50 | ) 51 | }} 52 |
53 |
54 | 55 | ### Props 56 | 57 | -------------------------------------------------------------------------------- /stories/components/Radio.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks'; 2 | import { Radio, RadioGroup } from '../../src'; 3 | import { useState } from 'react'; 4 | 5 | 6 | 7 | # 单选框 Radio 8 | 9 | 10 | ### 单个单选框 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ### 一组单选框 19 | 20 | 21 | 22 | {() => { 23 | const [n, setN] = useState("a") 24 | return ( 25 | { setN(value) }}> 26 | 27 | 28 | 29 | 30 | ) 31 | }} 32 | 33 | 34 | 35 | ### Props 36 | 37 | -------------------------------------------------------------------------------- /stories/components/SegmentControl.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks'; 2 | import { SegmentControl } from '../../src'; 3 | import { useState } from 'react'; 4 | 5 | 6 | 7 | # 分段控制器 SegmentControl 8 | 9 | 10 | ## 简单使用 11 | 12 | 13 | 14 | {() => { 15 | const [value, setValue] = useState('hourly') 16 | return ( 17 | { console.log(value); setValue(value) }} 26 | /> 27 | ) 28 | }} 29 | 30 | 31 | 32 | ### Props 33 | 34 | -------------------------------------------------------------------------------- /stories/components/Skeleton.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks'; 2 | import { Skeleton } from '../../src'; 3 | 4 | 5 | 6 | # 骨架 Skeleton 7 | 8 | ### Skeleton 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 |
20 | 21 |
22 |
23 |
24 | 25 | ### Props 26 | 27 | -------------------------------------------------------------------------------- /stories/components/Stepper.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks'; 2 | import { Stepper } from '../../src'; 3 | import { useState } from 'react'; 4 | 5 | 6 | 7 | # 计数器 Stepper 8 | 9 | 10 | ### 简单使用 11 | 12 | 13 | 14 | {() => { 15 | const [n, setN] = useState(null) 16 | return ( 17 | { 20 | setN(value) 21 | }} 22 | > 23 | 24 | ) 25 | }} 26 | 27 | 28 | 29 | ### 按钮位置 30 | 31 | 可以选择控制按钮的位置,设置 `controlPosition={'right'}` 使控制按钮显示在右侧 32 | 33 | > 设置不同的按钮位置时,组件内部的 Nodes 结构会有所不同。 34 | > 使用右侧控制按钮布局时,两个控制按钮会被 `RightControlsContainer` 的 Node 包一层,如果你需要自定义组件的样式,要注意一下这个多出来的 Node。 35 | 36 | 37 | 38 | {() => { 39 | const [n, setN] = useState(null) 40 | return ( 41 | { 45 | setN(value) 46 | }} 47 | > 48 | 49 | ) 50 | }} 51 | 52 | 53 | 54 | ### 键盘控制 55 | 56 | * `Down Arrow` `Right Arrow` 增加值。 57 | * `Up Arrow` `Left Arrow` 减小值。 58 | * `Home` 如果计数器具有最小值,则将其值设置为最小值。 59 | * `End` 如果计数器具有最大值,则将该值设置为其最大值。 60 | * `Page Up` 与 `Up Arrow` `Left Arrow` 相比,将值增加更大的幅度。 61 | * `Page Down` 与 `Down Arrow` `Right Arrow` 相比,将值减小幅度更大。 62 | 63 | ### Props 64 | 65 | -------------------------------------------------------------------------------- /stories/components/Switch.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks'; 2 | import { Switch } from '../../src'; 3 | import { useState } from 'react'; 4 | 5 | 6 | 7 | # 开关 Switch 8 | 9 | 10 | ### 简单使用 11 | 12 | 13 | 14 | {() => { 15 | const [checked, setChecked] = useState(false) 16 | return ( 17 |
18 | { setChecked(value); console.log('onChange', value) }} 22 | > 23 | 24 | { setChecked(value); console.log('onChange', value) }} 27 | disabled 28 | > 29 | 30 |
31 | ) 32 | }} 33 |
34 |
35 | 36 | -------------------------------------------------------------------------------- /stories/components/Tag.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks'; 2 | import { Tag } from '../../src'; 3 | import { useState } from 'react'; 4 | 5 | 6 | 7 | # 标签 Tag 8 | 9 | ### Tag 10 | 11 | 12 | 13 | Tag 14 | 15 | 16 | 17 | ### 自定义样式 18 | 19 | 20 | 21 | append element 29 | }, 30 | Content: { 31 | extendClassName: 'text-green-800' 32 | }, 33 | }} 34 | >ABC 35 | 36 | 37 | {}} 39 | customize={{ 40 | Root: { 41 | overrideClassName: 'uui-tag inline-block p-6 rounded-full bg-gray-600', 42 | } 43 | }} 44 | >ABC 45 | 46 | 47 | 48 | ### Props 49 | 50 | -------------------------------------------------------------------------------- /stories/components/Toast.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks'; 2 | import { Toast, Toaster, ToasterPosition } from '../../src'; 3 | import { useState, useRef } from 'react'; 4 | import { Toast as Toast1, ToastCustomStyle } from './Toast.stories.tsx'; 5 | 6 | 7 | 8 | # 全局提示 Toast 9 | 10 | ### 简单使用 11 | 12 | `Toast` 和 `Toaster` 是两个不同的组件,分别负责不同的功能。`Toast` 是具体的功能实现,负责渲染和管理内部状态;`Toaster` 则负责全局管理所有的提示。 13 | 14 | 通过 `Toaster` 创建出一个示例,可以直接导出全局使用。`Toaster` 可以创建出多个示例,这些示例之间互相不影响。 15 | 16 | ```tsx 17 | const AppToaster1 = Toaster.create({ 18 | maxToasts: 5, 19 | position: ToasterPosition.TopRight, 20 | }) 21 | const AppToaster2 = Toaster.create({ 22 | maxToasts: 5, 23 | position: ToasterPosition.TopRight, 24 | }) 25 | ``` 26 | 27 | 28 | {Toast1()} 29 | 30 | 31 | ### 手动关闭提示 32 | 33 | 通过调用 `AppToaster1.show` 可以显示出一个提示。 34 | 这个方法接受第二个可选参数,手动设置当前这个提示(Toast)的 id。 35 | 有了这个 id,就可以调用 `AppToaster1.dismiss(id)` 来手动关闭当前提示。 36 | 37 | ```tsx 38 | AppToaster1.show(props: any, id?: string | undefined): string | undefined 39 | 40 | const id = AppToaster1.show({ 41 | message: 'Ohiyo!', 42 | }) 43 | AppToaster1.dismiss(id) 44 | ``` 45 | 46 | ### 自定义样式 47 | 48 | 49 | {ToastCustomStyle()} 50 | 51 | 52 | ### Props 53 | 54 | -------------------------------------------------------------------------------- /stories/components/Toast.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Toaster, ToasterPosition } from '../../src'; 3 | import { Button } from '../../src'; 4 | 5 | const AppToaster1 = Toaster.create({ 6 | maxToasts: 5, 7 | position: ToasterPosition.BottomLeft, 8 | }) 9 | const AppToaster2 = Toaster.create({ 10 | maxToasts: 5, 11 | position: ToasterPosition.TopRight, 12 | }) 13 | 14 | export const Toast = () => { 15 | return ( 16 | 23 | ) 24 | } 25 | 26 | Toast.storyName = 'Toast' 27 | 28 | export const ToastCustomStyle = () => { 29 | return ( 30 | 43 | ) 44 | } 45 | 46 | ToastCustomStyle.storyName = 'CustomStyle Toast' -------------------------------------------------------------------------------- /stories/components/Tooltip.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks'; 2 | import { Tooltip, Button } from '../../src'; 3 | import { useState } from 'react'; 4 | 5 | 6 | 7 | # 文字提示 Tooltip 8 | 9 | ### Tooltip 10 | 11 | 12 | 13 | {() => { 14 | const grid = [ 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 | return ( 40 |
41 | {grid.map((row, rowIndex) => { 42 | return ( 43 |
44 | {row.map((cell, cellIndex) => { 45 | const element = typeof cell === 'string' ? : cell 46 | return ( 47 |
{element}
48 | ) 49 | })} 50 |
51 | ) 52 | })} 53 |
54 | ) 55 | }} 56 |
57 |
58 | 59 | ### Props 60 | 61 | -------------------------------------------------------------------------------- /stories/demo/ComponentsCombination.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/addon-docs/blocks'; 2 | import { ComponentsCombination } from './ComponentsCombination.stories.tsx'; 3 | 4 | 5 | 6 | 7 | {ComponentsCombination()} 8 | -------------------------------------------------------------------------------- /stories/dev/Contributing.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/addon-docs/blocks'; 2 | import ReactMarkdown from 'react-markdown'; 3 | import CONTRIBUTING from '../../docs/CONTRIBUTING.zh-CN.md'; 4 | 5 | 6 | 7 |
8 | 9 |
10 | -------------------------------------------------------------------------------- /stories/dev/Deployment.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/addon-docs/blocks'; 2 | import ReactMarkdown from 'react-markdown'; 3 | import DEPLOYMENT from '../../docs/DEPLOYMENT.zh-CN.md'; 4 | 5 | 6 | 7 |
8 | 9 |
10 | -------------------------------------------------------------------------------- /stories/dev/DevTips.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/addon-docs/blocks'; 2 | import ReactMarkdown from 'react-markdown'; 3 | import DEVTIPS from '../../docs/DEVTIPS.zh-CN.md'; 4 | 5 | 6 | 7 |
8 | 9 |
10 | -------------------------------------------------------------------------------- /stories/dev/Principle.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/addon-docs/blocks'; 2 | import ReactMarkdown from 'react-markdown'; 3 | import Principle from '../../docs/PRINCIPLE.zh-CN.md'; 4 | 5 | 6 | 7 |
8 | 9 |
-------------------------------------------------------------------------------- /stories/docs/Changelog.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/addon-docs/blocks'; 2 | import ReactMarkdown from 'react-markdown'; 3 | import Changelog from '../../docs/CHANGELOG.zh-CN.md'; 4 | 5 | 6 | 7 |
8 | 9 |
10 | -------------------------------------------------------------------------------- /stories/docs/GettingStarted.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/addon-docs/blocks'; 2 | import ReactMarkdown from 'react-markdown'; 3 | import GettingStarted from '../../docs/GETTING_STARTED.zh-CN.md'; 4 | 5 | 6 | 7 |
8 | 9 |
10 | -------------------------------------------------------------------------------- /stories/docs/Introduction.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/addon-docs/blocks'; 2 | import ReactMarkdown from 'react-markdown'; 3 | import Introduction from '../../docs/INTRODUCTION.zh-CN.md'; 4 | 5 | 6 | 7 |
8 | 9 |
10 | -------------------------------------------------------------------------------- /stories/docs/UsingCustomize.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Canvas, Story } from '@storybook/addon-docs/blocks'; 2 | import ReactMarkdown from 'react-markdown'; 3 | import { useState } from 'react'; 4 | import { Stepper, Slider, Switch, TextField } from '../../src'; 5 | import { ComponentComparator } from '../utils/ComponentComparator'; 6 | import GettingStarted from '../../docs/USING_CUSTOMIZE.zh-CN.md'; 7 | 8 | 9 | 10 | 11 |
12 | 13 |
-------------------------------------------------------------------------------- /stories/docs/Welcome.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/addon-docs/blocks'; 2 | import ReactMarkdown from 'react-markdown'; 3 | import README from '../../docs/README.zh-CN.md'; 4 | 5 | 6 | 7 |
8 | 9 |
10 | -------------------------------------------------------------------------------- /stories/style/CustomizeDemoStepper.scss: -------------------------------------------------------------------------------- 1 | .UUI-Stepper-Root.CustomizeDemo { 2 | border: 1px solid #7b9eff; 3 | 4 | .UUI-Stepper-MinusButton, .UUI-Stepper-PlusButton { 5 | background-color: #e5f3ff; 6 | color: #7b9eff; 7 | } 8 | .UUI-Stepper-MinusButton, .UUI-Stepper-PlusButton, .UUI-Stepper-Input { 9 | border: 1px solid #7b9eff; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /stories/style/storybook.scss: -------------------------------------------------------------------------------- 1 | code { 2 | color: orangered !important; 3 | } 4 | 5 | pre code { 6 | color: inherit !important; 7 | } 8 | 9 | ul li { 10 | list-style-type: disc; 11 | } 12 | 13 | @import '../../node_modules/github-markdown-css/github-markdown.css'; 14 | 15 | @import './CustomizeDemoStepper.scss'; -------------------------------------------------------------------------------- /stories/style/tailwind.css: -------------------------------------------------------------------------------- 1 | /* Tailwind Styles */ 2 | /* @import 'tailwindcss/base'; */ 3 | @import 'tailwindcss/components'; 4 | @import 'tailwindcss/utilities'; 5 | -------------------------------------------------------------------------------- /stories/style/uui.scss: -------------------------------------------------------------------------------- 1 | @import '../../src/styles/base.scss'; 2 | 3 | $primaryColor: orange; 4 | 5 | @import '../../src/styles/components.scss'; -------------------------------------------------------------------------------- /stories/utils/ComponentComparator.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export function ComponentComparator(props: { 4 | before: React.ReactNode 5 | after: React.ReactNode 6 | }) { 7 | return ( 8 |
9 |
10 |
{props.before}
11 |
BEFORE
12 |
13 |
14 |
{props.after}
15 |
AFTER
16 |
17 |
18 | ) 19 | } -------------------------------------------------------------------------------- /stories/utils/PreviewBox.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export interface PreviewBoxProps { 4 | title?: string 5 | description?: string | React.ReactNode 6 | children: React.ReactNode 7 | } 8 | 9 | export function PreviewBox(props: PreviewBoxProps) { 10 | return ( 11 |
12 |
13 | {props.title && (
{props.title}
)} 14 | {props.description && (
{props.description}
)} 15 |
16 | {props.children} 17 |
18 | ) 19 | } -------------------------------------------------------------------------------- /tests/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackPlan/UUI/31ddbb5da92748a2192f53c94b8f06282283a969/tests/components/.gitkeep -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "rootDirs": ["src"], 3 | "compilerOptions": { 4 | "baseUrl": "src", 5 | "target": "ES6", 6 | "outDir": "lib", 7 | "sourceMap": true, 8 | "declaration": true, 9 | "allowJs": true, 10 | "esModuleInterop": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "react", 17 | "declarationMap": true 18 | }, 19 | "include": [ 20 | "src/**/*.ts", 21 | "src/**/*.tsx", 22 | "typing/*" 23 | ], 24 | "exclude": [ 25 | "node_modules", 26 | "lib" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /typing/svgr.d.ts: -------------------------------------------------------------------------------- 1 | interface SvgrComponent extends React.StatelessComponent> {} 2 | 3 | declare module '*.svg' { 4 | const ReactComponent: SvgrComponent; 5 | const url: string 6 | export default url; 7 | export { ReactComponent }; 8 | } --------------------------------------------------------------------------------