├── .circleci
└── config.yml
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .github
└── workflows
│ └── chromatic.yml
├── .gitignore
├── .prettierrc
├── .release-it.json
├── .storybook
├── main.js
├── manager-head.html
├── manager.js
├── preview-body.html
├── preview-head.html
├── preview.js
└── theme.js
├── .vscode
├── extensions.json
├── launch.json
└── settings.json
├── README.md
├── build-storybook.log
├── config-overrides.js
├── docs
├── css
│ ├── css-in-js.md
│ └── wiloke-styles.md
├── general
│ ├── commands.md
│ ├── customize-theme.md
│ └── start.md
├── img
│ ├── 1.png
│ └── 2.png
└── js
│ ├── redux-saga.md
│ ├── reselect.md
│ └── routing.md
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── rc-generate.config.js
├── src
├── @types
│ ├── react-router-dom.d.ts
│ ├── react-router-overrides.d.ts
│ └── type.ts
├── App.tsx
├── components
│ ├── Alert
│ │ ├── Alert.tsx
│ │ ├── index.ts
│ │ └── styles.ts
│ ├── Avatar
│ │ ├── Avatar.tsx
│ │ ├── AvatarLoading.tsx
│ │ ├── avatarColors.ts
│ │ ├── index.ts
│ │ └── styles.ts
│ ├── Button
│ │ ├── Button.tsx
│ │ ├── index.ts
│ │ └── styles.ts
│ ├── Checkbox
│ │ ├── Checkbox.tsx
│ │ ├── CheckboxLoading.tsx
│ │ ├── index.ts
│ │ └── styles.ts
│ ├── CodeHighlight
│ │ ├── CodeHighlight.tsx
│ │ ├── index.ts
│ │ └── vendors
│ │ │ ├── prism.css
│ │ │ └── prism.js
│ ├── Collapse
│ │ ├── Collapse.tsx
│ │ ├── Panel.tsx
│ │ ├── context
│ │ │ └── CollapseContext.tsx
│ │ ├── hooks
│ │ │ ├── useCollapseDispatch.ts
│ │ │ └── useCollapseState.ts
│ │ ├── index.ts
│ │ ├── styles.ts
│ │ └── utils
│ │ │ └── parseValueToArray.ts
│ ├── ColorPicker
│ │ ├── ColorPicker.tsx
│ │ ├── ColorPickerLoading.tsx
│ │ ├── decimalToHex.ts
│ │ ├── hexToRgb.ts
│ │ ├── index.ts
│ │ ├── styles.ts
│ │ └── utils
│ │ │ ├── rgbToHex.ts
│ │ │ ├── rgbaObjectToString.ts
│ │ │ └── rgbaStringToObject.ts
│ ├── ColorPickerBeauty
│ │ ├── ColorPickerBeauty.tsx
│ │ ├── ColorPickerBeautyLoading.tsx
│ │ ├── index.ts
│ │ └── styles.ts
│ ├── Field
│ │ ├── Field.tsx
│ │ ├── index.ts
│ │ └── styles.ts
│ ├── FieldBox
│ │ ├── FieldBox.tsx
│ │ ├── index.ts
│ │ └── styles.ts
│ ├── HoverLazy
│ │ ├── HoverLazy.tsx
│ │ └── index.ts
│ ├── IconText
│ │ ├── IconText.tsx
│ │ ├── index.ts
│ │ └── styles.ts
│ ├── LinkPrefetch
│ │ ├── LinkPrefetch.tsx
│ │ └── index.ts
│ ├── NumberInput
│ │ ├── Actions.tsx
│ │ ├── NumberInput.tsx
│ │ ├── NumberInputLoading.tsx
│ │ ├── index.ts
│ │ ├── styles.ts
│ │ └── useCount.ts
│ ├── PostCard
│ │ ├── PostCard.tsx
│ │ ├── PostCardLoading.tsx
│ │ ├── index.ts
│ │ └── styles.ts
│ ├── Radio
│ │ ├── Radio.tsx
│ │ ├── RadioButton.tsx
│ │ ├── RadioGroup.tsx
│ │ ├── context.tsx
│ │ ├── index.ts
│ │ ├── styles.ts
│ │ └── useMergedState.ts
│ ├── Section
│ │ ├── Section.tsx
│ │ ├── index.ts
│ │ └── styles.ts
│ ├── SectionTitle
│ │ ├── SectionTitle.tsx
│ │ └── index.ts
│ ├── Slider
│ │ ├── Slider.tsx
│ │ ├── SliderLoading.tsx
│ │ ├── index.ts
│ │ └── styles.ts
│ ├── Switch
│ │ ├── Switch.tsx
│ │ ├── index.ts
│ │ └── styles.ts
│ ├── SwitchBeauty
│ │ ├── SwitchBeauty.tsx
│ │ ├── TextStatus
│ │ │ ├── TextStatus.tsx
│ │ │ ├── index.ts
│ │ │ └── styles.ts
│ │ ├── index.ts
│ │ └── styles.ts
│ ├── Tabs
│ │ ├── TabPane
│ │ │ ├── TabPaneBase.tsx
│ │ │ └── index.ts
│ │ ├── Tabs.tsx
│ │ ├── index.ts
│ │ └── styles.ts
│ ├── TextInput
│ │ ├── TextInput.tsx
│ │ ├── TextInputLoading.tsx
│ │ ├── index.ts
│ │ └── styles.ts
│ └── TextUnderline
│ │ ├── TextUnderline.tsx
│ │ ├── index.ts
│ │ └── styles.ts
├── configureApp.json
├── containers
│ ├── AboutPage
│ │ ├── AboutPage.tsx
│ │ └── index.ts
│ ├── AppContent
│ │ ├── AppContent.tsx
│ │ ├── index.ts
│ │ └── styles.ts
│ ├── Header
│ │ ├── Header.tsx
│ │ ├── actions
│ │ │ └── actionNightMode.ts
│ │ ├── index.ts
│ │ ├── reducers
│ │ │ └── reducerNightMode.ts
│ │ ├── slice
│ │ │ └── sliceDirection.ts
│ │ └── styles.ts
│ ├── HomePage
│ │ ├── HomePage.tsx
│ │ ├── actions
│ │ │ ├── actionExample.ts
│ │ │ └── actionTodolist.ts
│ │ ├── index.ts
│ │ ├── reducers
│ │ │ ├── reducerExample.ts
│ │ │ └── reducerTodolist.ts
│ │ └── sagas
│ │ │ └── watchTodolist.ts
│ └── NotFoundPage
│ │ ├── NotFoundPage.tsx
│ │ └── index.ts
├── hooks
│ ├── .gitkeep
│ ├── useMergedState.ts
│ └── useTranslation.ts
├── index.tsx
├── models
│ └── Todolist.ts
├── react-app-env.d.ts
├── reportWebVitals.ts
├── routes
│ ├── LocationSearch.ts
│ ├── LocationStates.ts
│ ├── index.tsx
│ └── types.ts
├── serviceWorker.ts
├── setupTests.ts
├── store
│ ├── configureStore.ts
│ ├── rootReducers.ts
│ ├── rootSagas.ts
│ └── selectors.ts
├── stories
│ ├── 1-Start
│ │ ├── 1-Welcome.stories.tsx
│ │ └── App.stories.tsx
│ ├── 2-UIBase
│ │ ├── ActivityIndicator.stories.tsx
│ │ ├── CountTo.stories.tsx
│ │ ├── Divider.stories.tsx
│ │ ├── GridSmart.stories.tsx
│ │ ├── Image.stories.tsx
│ │ ├── LineAwesome.stories.tsx
│ │ ├── MaterialIcon.stories.tsx
│ │ ├── OuterTrigger.stories.tsx
│ │ ├── ProgressLoader.stories.tsx
│ │ ├── Responsive.stories.tsx
│ │ ├── Space.stories.tsx
│ │ ├── Sticky.stories.tsx
│ │ ├── Text.stories.tsx
│ │ ├── View.stories.tsx
│ │ ├── ViewportTracking.stories.tsx
│ │ └── base
│ │ │ ├── ActivityIndicator.tsx
│ │ │ ├── CountTo.tsx
│ │ │ ├── Divider.tsx
│ │ │ ├── GridSmart.tsx
│ │ │ ├── Image.tsx
│ │ │ ├── LineAwesome.tsx
│ │ │ ├── MaterialIcon.tsx
│ │ │ ├── OuterTrigger.tsx
│ │ │ ├── ProgressLoader.tsx
│ │ │ ├── Responsive.tsx
│ │ │ ├── Space.tsx
│ │ │ ├── Sticky.tsx
│ │ │ ├── Text.tsx
│ │ │ ├── View.tsx
│ │ │ └── ViewportTracking.tsx
│ ├── 3-Components
│ │ ├── Alert.stories.tsx
│ │ ├── Avatar.stories.tsx
│ │ ├── Button.stories.tsx
│ │ ├── Checkbox.stories.tsx
│ │ ├── CodeHighlight.stories.tsx
│ │ ├── Collapse.stories.tsx
│ │ ├── ColorPicker.stories.tsx
│ │ ├── ColorPickerBeauty.stories.tsx
│ │ ├── Field.stories.tsx
│ │ ├── IconText.stories.tsx
│ │ ├── NumberInput.stories.tsx
│ │ ├── Radio.stories.tsx
│ │ ├── Slider.stories.tsx
│ │ ├── Switch.stories.tsx
│ │ ├── SwitchBeauty.stories.tsx
│ │ ├── Tabs.stories.tsx
│ │ ├── TextInput.stories.tsx
│ │ └── TextUnderline.stories.tsx
│ └── utils
│ │ ├── getOptions.ts
│ │ ├── getWithStylesProps.ts
│ │ ├── lineAwesome.ts
│ │ └── materialIcon.ts
├── styles
│ └── base.ts
├── types.d.ts
├── types
│ └── Endpoints.ts
└── utils
│ ├── constants
│ └── defaultTranslation.ts
│ └── functions
│ ├── createTranslation
│ ├── createTranslation.ts
│ ├── index.ts
│ └── types.ts
│ └── fetchAPI
│ ├── ConfigureAxios.ts
│ └── index.ts
├── tsconfig.json
└── tsconfig.paths.json
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | defaults: &defaults
2 | #working_directory: ./circleci
3 | docker:
4 | - image: circleci/node:8
5 |
6 | version: 2
7 | jobs:
8 | prepare:
9 | <<: *defaults
10 | steps:
11 | - checkout
12 | # Download and cache dependencies
13 | - restore_cache:
14 | keys:
15 | - v1-dependencies-{{ checksum "package.json" }}
16 | - run: npm install
17 | - save_cache:
18 | paths:
19 | - node_modules
20 | key: v1-dependencies-{{ checksum "package.json" }}
21 | - persist_to_workspace:
22 | root: .
23 | paths:
24 | - node_modules
25 | build:
26 | <<: *defaults
27 | steps:
28 | - checkout
29 | - attach_workspace:
30 | at: .
31 | - run: npm run build
32 | - persist_to_workspace:
33 | root: .
34 | paths:
35 | - build
36 | lint:
37 | <<: *defaults
38 | steps:
39 | - checkout
40 | - attach_workspace:
41 | at: .
42 | - run: npm run eslint
43 | ts:
44 | <<: *defaults
45 | steps:
46 | - checkout
47 | - attach_workspace:
48 | at: .
49 | - run: npm run tsc --noEmit
50 | test:
51 | <<: *defaults
52 | steps:
53 | - checkout
54 | - attach_workspace:
55 | at: .
56 | - run: npm run test
57 | workflows:
58 | version: 2
59 | build_accept_deploy:
60 | jobs:
61 | - prepare:
62 | filters:
63 | branches:
64 | ignore: gh-pages
65 | - lint:
66 | requires:
67 | - prepare
68 | - ts:
69 | requires:
70 | - prepare
71 | # - test:
72 | # requires:
73 | # - prepare
74 | - build:
75 | requires:
76 | - lint
77 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_size = 2
7 | indent_style = space
8 | insert_final_newline = true
9 | max_line_length = 80
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | max_line_length = 0
14 | trim_trailing_whitespace = false
15 |
16 | [COMMIT_EDITMSG]
17 | max_line_length = 0
18 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 | .eslintcache
25 |
26 | # Dependency directory
27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
28 | node_modules
29 |
30 | # OSX
31 | .DS_Store
32 |
33 | # flow-typed
34 | flow-typed
35 |
36 | # App packaged
37 | release
38 | build
39 |
40 | .idea
41 | npm-debug.log.*
42 | __snapshots__
43 |
44 | # Package.json
45 | package.json
46 | .travis.yml
47 | .cỉcleci
48 | config-overrides.js
49 | webpack-plugins
50 | rc-generate.config.js
51 | tailwind.config.js
52 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 | *.png binary
3 | *.jpg binary
4 | *.jpeg binary
5 | *.ico binary
6 | *.icns binary
7 |
--------------------------------------------------------------------------------
/.github/workflows/chromatic.yml:
--------------------------------------------------------------------------------
1 | # .github/workflows/chromatic.yml
2 | # name of our action
3 | name: 'Chromatic Deployment'
4 | # the event that will trigger the action
5 | on: push
6 |
7 | # what the action will do
8 | jobs:
9 | test:
10 | # the operating system it will run on
11 | runs-on: ubuntu-latest
12 | # the list of steps that the action will go through
13 | steps:
14 | - uses: actions/checkout@v1
15 | - run: yarn
16 | - uses: chromaui/action@v1
17 | # options required to the GitHub chromatic action
18 | with:
19 | # our project token, to see how to obtain it
20 | # refer to https://www.learnstorybook.com/intro-to-storybook/react/en/deploy/
21 | projectToken: ${{ secrets.CHROMATIC }}
22 | token: ${{ secrets.GITHUB_TOKEN }}
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 | build-storybook.log
25 | yarn.lock
26 | .eslintcache
27 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "printWidth": 150,
5 | "arrowParens": "avoid"
6 | }
7 |
--------------------------------------------------------------------------------
/.release-it.json:
--------------------------------------------------------------------------------
1 | {
2 | "src": {
3 | "tagName": "v%s",
4 | "commitArgs": "-S",
5 | "tagArgs": "-S"
6 | },
7 | "github": {
8 | "release": true,
9 | "releaseName": "Release %s",
10 | "tokenRef": "GITHUB_TOKEN",
11 | "assets": ["*.zip"]
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const createCompiler = require('@storybook/addon-docs/mdx-compiler-plugin');
3 |
4 | module.exports = {
5 | stories: ['../src/**/*.stories.@(js|tsx|ts|mdx)'],
6 | addons: [
7 | '@storybook/preset-create-react-app',
8 | '@storybook/addon-actions',
9 | '@storybook/addon-links',
10 | '@storybook/components',
11 | '@storybook/addon-knobs',
12 | '@storybook/addon-docs',
13 | '@storybook/addon-viewport',
14 | {
15 | name: '@storybook/addon-storysource',
16 | options: {
17 | parser: 'typescript',
18 | rule: {
19 | // test: [/\.stories\.jsx?$/], This is default
20 | include: [path.resolve(__dirname, '../src')], // You can specify directories
21 | },
22 | loaderOptions: {
23 | prettierConfig: { printWidth: 80, singleQuote: false },
24 | },
25 | },
26 | },
27 | ],
28 | webpackFinal: config => {
29 | config.module.rules.push({
30 | // 2a. Load `.stories.mdx` / `.story.mdx` files as CSF and generate
31 | // the docs page from the markdown
32 | test: /\.(stories|story)\.mdx$/,
33 | use: [
34 | {
35 | loader: 'babel-loader',
36 | // may or may not need this line depending on your app's setup
37 | options: {
38 | plugins: ['@babel/plugin-transform-react-jsx'],
39 | },
40 | },
41 | {
42 | loader: '@mdx-js/loader',
43 | options: {
44 | compilers: [createCompiler({})],
45 | },
46 | },
47 | ],
48 | });
49 | // 2b. Run `source-loader` on story files to show their source code
50 | // automatically in `DocsPage` or the `Source` doc block.
51 | config.module.rules.push({
52 | test: /\.(stories|story)\.[tj]sx?$/,
53 | loader: require.resolve('@storybook/source-loader'),
54 | exclude: [/node_modules/],
55 | enforce: 'pre',
56 | });
57 |
58 | config.resolve.extensions.push('.ts', '.tsx', '.mdx');
59 |
60 | config.resolve.modules = [...(config.resolve.modules || []), path.resolve(__dirname, '../'), path.resolve(__dirname, '../src')];
61 |
62 | config.node = {
63 | fs: 'empty',
64 | };
65 |
66 | return config;
67 | },
68 | };
69 |
--------------------------------------------------------------------------------
/.storybook/manager-head.html:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/.storybook/manager.js:
--------------------------------------------------------------------------------
1 | import { addons } from '@storybook/addons';
2 | import theme from './theme';
3 |
4 | addons.setConfig({
5 | theme,
6 | isFullscreen: false,
7 | showNav: true,
8 | showPanel: true,
9 | panelPosition: 'right',
10 | sidebarAnimations: true,
11 | enableShortcuts: true,
12 | isToolshown: true,
13 | selectedPanel: undefined,
14 | initialActive: 'sidebar',
15 | showRoots: false,
16 | });
17 |
--------------------------------------------------------------------------------
/.storybook/preview-body.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/long-hp/react-typescript-boilerplate/545789ef7ce543a9fa09a891bc6856100c0f3c0a/.storybook/preview-body.html
--------------------------------------------------------------------------------
/.storybook/preview-head.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.storybook/preview.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { addDecorator, addParameters } from '@storybook/react';
3 | import { withKnobs } from '@storybook/addon-knobs';
4 | import { MemoryRouter } from 'react-router-dom';
5 | import { Provider } from 'react-redux';
6 | import { PersistGate } from 'redux-persist/integration/react';
7 | import { getUseDispatchRedux } from 'wiloke-react-core/utils';
8 | import { useDispatch, useSelector } from 'react-redux';
9 | import { ThemeProvider } from 'wiloke-react-core';
10 | import { themeOverrides, CSSGlobal } from 'containers/AppContent/AppContent';
11 |
12 | getUseDispatchRedux(useDispatch);
13 | import { store, persistor } from 'store/configureStore';
14 |
15 | const AppContent: FC = ({ children }) => {
16 | const nightMode = useSelector((state: AppState) => state.nightMode);
17 | const direction = useSelector((state: AppState) => state.direction);
18 | return (
19 |
20 |
21 | {children}
22 |
23 |
24 | );
25 | };
26 |
27 | const withThemeContext = storyFn => (
28 |
29 | } persistor={persistor}>
30 |
31 | {storyFn()}
32 |
33 |
34 |
35 | );
36 |
37 | addDecorator(withThemeContext);
38 |
39 | addDecorator(withKnobs);
40 |
41 | addParameters({
42 | options: {
43 | showRoots: true,
44 | },
45 | });
46 |
--------------------------------------------------------------------------------
/.storybook/theme.js:
--------------------------------------------------------------------------------
1 | import { create } from '@storybook/theming/create';
2 |
3 | export default create({
4 | base: 'light',
5 | brandTitle: 'Wiloke',
6 | brandImage: 'https://placehold.it/350x150',
7 | });
8 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "esbenp.prettier-vscode",
4 | "dbaeumer.vscode-eslint",
5 | "mrmlnc.vscode-scss",
6 | "jpoissonnier.vscode-styled-components"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "chrome",
9 | "request": "launch",
10 | "name": "Launch Chrome against localhost",
11 | "url": "http://localhost:3000",
12 | "webRoot": "${workspaceFolder}"
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "**/.git": true,
4 | "**/.svn": true,
5 | "**/.hg": true,
6 | "**/CVS": true,
7 | "**/.DS_Store": true,
8 | "node_modules": true
9 | },
10 | "editor.codeActionsOnSave": {
11 | "source.fixAll.eslint": true,
12 | "source.fixAll": true
13 | },
14 | "scss.scannerExclude": [
15 | "**/.git"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Hướng dẫn sử dụng react-typescript-boilerplate được tạo ra từ create-react-app + wiloke-react-core
2 |
3 | React-typescript-boilerplate được xây dựng và phát triển bởi [Wiloke](http://wiloke.com/). Tài liệu này nhằm cung cấp
4 | cho bạn một chút về cách kiến trúc của react-typescript-boilerplate cũng như cách sử dụng [wiloke-react-core](https://www.npmjs.com/package/wiloke-react-core) tích hợp bên trong.
5 |
6 | ##### [Bắt đầu dự án](docs/general/start.md)
7 |
8 | ##### [Xem các CLI](docs/general/commands.md)
9 |
10 | ##### [Danh sách các UI Base + Components](https://5f5b43872be3560022d03ffc-yvzedefutx.chromatic.com/?path=/story/ui-base-activityindicator--with-props)
11 |
12 | ##### [Theme Provider](docs/general/customize-theme.md)
13 |
14 | ##### [CSS In JS ( render -> Atomic CSS )](docs/css/css-in-js.md)
15 |
16 | ##### [Wiloke Styles](docs/css/wiloke-styles.md)
17 |
18 | ### Quickstart
19 |
20 | Đầu tiên, hãy bắt đầu bằng cách cài đặt
21 |
22 | `npm install` hoặc `yarn`
23 |
24 | ### Development
25 |
26 | Chạy `npm start` để xem ứng dụng của bạn tại `http://localhost:3000`
27 |
28 | ### Mở cổng storybook
29 |
30 | `yarn storybook` hoặc `npm run storybook`
31 |
32 | Mở `http://localhost:9009` để xem trên trình duyệt.
33 | Triển khai Storybook và kiểm tra giao diện người dùng với Chromatic [Open Demo](https://5f5b43872be3560022d03ffc-yvzedefutx.chromatic.com/?path=/story/start-welcome--colors)
34 |
35 | ### Building
36 |
37 | `npm run build` hoặc `yarn build`
38 |
39 | [Xem thêm Create React App](https://github.com/facebook/create-react-app)
40 |
--------------------------------------------------------------------------------
/config-overrides.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
4 |
5 | const configuration = {
6 | jsOutput: {
7 | filename: 'static/js/[name].js',
8 | chunkFilename: 'static/js/[name].chunk.js',
9 | },
10 | cssOutput: {
11 | filename: 'static/css/[name].css',
12 | chunkFilename: 'static/css/[name].chunk.css',
13 | },
14 | };
15 |
16 | function rewireProduction(config, type) {
17 | return {
18 | ...config,
19 | devtool: false,
20 | output: {
21 | ...config.output,
22 | filename: configuration.jsOutput.filename,
23 | chunkFilename: configuration.jsOutput.chunkFilename,
24 | },
25 | plugins: [
26 | ...config.plugins.map((item, index) => {
27 | if (!(item.options && item.options.filename && item.options.filename.includes('.css'))) {
28 | return item;
29 | }
30 | return new MiniCssExtractPlugin({
31 | filename: configuration.cssOutput.filename,
32 | chunkFilename: configuration.cssOutput.chunkFilename,
33 | });
34 | }),
35 | ],
36 | optimization: {
37 | runtimeChunk: 'single',
38 | splitChunks: {
39 | maxInitialRequests: Infinity,
40 | minSize: 40000,
41 | cacheGroups: {
42 | vendor: {
43 | test: /[\\/]node_modules[\\/]/,
44 | chunks(chunk) {
45 | return chunk.name === 'main';
46 | },
47 | name(module, chunks, cacheGroupKey) {
48 | const moduleFileName = module
49 | .identifier()
50 | .split('/')
51 | .reduceRight(item => item);
52 | const allChunksNames = chunks.map(item => item.name).join('~');
53 | const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
54 | return type === 'function' ? `${cacheGroupKey}-${allChunksNames}-${moduleFileName}` : packageName.replace('@', '');
55 | },
56 | },
57 | },
58 | },
59 | },
60 | };
61 | }
62 |
63 | function rewireDevelopment(config) {
64 | return config;
65 | }
66 |
67 | module.exports = function override(config, env) {
68 | const isDev = env === 'development';
69 | if (isDev) {
70 | return rewireDevelopment(config);
71 | }
72 | return rewireProduction(config);
73 | };
74 |
--------------------------------------------------------------------------------
/docs/css/css-in-js.md:
--------------------------------------------------------------------------------
1 | [3]: ../general/customize-theme.md
2 | [5]: ./wiloke-styles.md
3 |
4 |
5 |
6 | # CSS IN JS ( Atomic CSS )
7 |
8 | # COMING SOON
9 |
10 | [Prev][3] | [Next][5]
11 |
--------------------------------------------------------------------------------
/docs/css/wiloke-styles.md:
--------------------------------------------------------------------------------
1 | [3]: ../general/customize-theme.md
2 | [5]: ./tachyons.md
3 |
4 |
5 |
6 | # Wiloke Styles
7 |
8 | Thuộc loại css có sẵn hỗ trợ chủ yếu hover cha vào con
9 |
10 | ## Effect Hover
11 |
12 | ```tsx
13 | 'child-translateX-10' | 'child-translateX-20'
14 | ...
15 | 'child-translateX-90' | 'child-translateX-100'
16 |
17 | 'child-translateX--10' | 'child-translateX--20'
18 | ...
19 | 'child-translateX--90' | 'child-translateX--100'
20 |
21 | 'child-translateY-10' | 'child-translateY-20'
22 | ...
23 | 'child-translateY-90' | 'child-translateY-100'
24 |
25 | 'child-translateY--10' | 'child-translateY--20'
26 | ...
27 | 'child-translateY--90' | 'child-translateY--100'
28 |
29 | 'child-scale-80-100' | 'child-scale-90-100'
30 | 'child-scale-100-110' | 'child-scale-100-120'
31 | 'child-scale-120-100' | 'child-scale-110-100'
32 |
33 | 'child-fadein-0' | 'child-fadein-10'
34 | 'child-fadein-20' | 'child-fadein-30'
35 | 'child-fadein-40' | 'child-fadein-50'
36 |
37 | 'child-fadeout-0' | 'child-fadeout-10'
38 | 'child-fadeout-20' | 'child-fadeout-30'
39 | 'child-fadeout-40' | 'child-fadeout-50'
40 |
41 | 'child-show' |'child-hide'
42 | 'child-visible' | 'child-hidden' | 'child'
43 |
44 | 'delay-0' | 'delay-10' | 'delay-20' | 'delay-30'
45 | 'delay-40' | 'delay-50' | 'delay-60' | 'delay-70'
46 | 'delay-80' | 'delay-90' | 'delay-100'
47 | ```
48 |
49 | - Ví dụ
50 |
51 | ```tsx
52 | import React from 'react';
53 | import { View } from 'wiloke-react-core';
54 |
55 | const App = () => {
56 | return (
57 |
58 |
59 |
60 | );
61 | }
62 |
63 | export default App;
64 | ```
65 |
66 |
67 |
68 | [Prev][3] | [Next][5]
69 |
--------------------------------------------------------------------------------
/docs/general/commands.md:
--------------------------------------------------------------------------------
1 | [1]: ./start.md
2 | [3]: ./customize-theme.md
3 |
4 |
5 |
6 | # UI Base + CLI trong wiloke-react-core
7 |
8 | ## UI Base
9 |
10 | UI Base là các components có sẵn chỉ cần import trong [wiloke-react-core](https://www.npmjs.com/package/wiloke-react-core)
11 |
12 | Trước hết ta cần gõ lệnh `yarn stories-eject` để eject storybook cho UI Base ( Sau khi thực hiện xong hãy kiểm tra thư mục src/stories của dự án hoặc thực hiện lệnh `yarn storybook` để mở storybook )
13 |
14 | 
15 |
16 | #### Ví dụ như sau
17 |
18 | ```tsx
19 | import { View, Image } from 'wiloke-react-core';
20 |
21 | const App = () => {
22 | return (
23 |
24 |
25 |
26 | )
27 | }
28 | ```
29 |
30 | ## Components Eject
31 |
32 | Ngoài UI Base ra thì ta còn có thể sử dụng lệnh `yarn ce` để eject components có sẵn và nó sẽ xuất hiện trong thư mục components của dự án ( những components này sẽ xuất hiện trong dự án giúp ta có thể custom lại style cũng như code logic nếu cần )
33 |
34 | Nếu bạn mở file package.json sẽ thấy dòng này `"ce": "components-eject src",`
35 |
36 | #### Để chạy CLI
37 |
38 | ```shell
39 | yarn ce
40 | ```
41 |
42 | Và sau đó terminal xuất hiện như sau:
43 | 
44 |
45 | ## Generators
46 |
47 | Cho phép bạn tự động tạo mã soạn sẵn cho các phần chung của ứng dụng, cụ thể là các components và containers. Xem thêm tại [https://github.com/wiloke1/rc-generate](https://github.com/wiloke1/rc-generate)
48 |
49 | ### Easy use with npx
50 |
51 | ```Shell
52 | npx rc-generate --style scss --redux saga --component:name components/Buttons
53 | ```
54 |
55 | ### Or use with npm global
56 |
57 | ```Shell
58 | rc-generate --style scss --redux saga --component:name components/Button
59 | ```
60 |
61 |
62 |
63 | [Prev][1] | [Next][3]
64 |
--------------------------------------------------------------------------------
/docs/general/customize-theme.md:
--------------------------------------------------------------------------------
1 | [2]: ./commands.md
2 | [4]: ../css/css-in-js.md
3 |
4 | # Theme Provider
5 |
6 | Theme Provider cho phép ta tùy chỉnh mã để đáp ứng sự đa dạng về giao diện người dùng theo yêu cầu của doanh nghiệp hoặc khách hàng, bao gồm color, nightModeColor, direction v.v.
7 |
8 | Example
9 |
10 | ```tsx
11 | import { ThemeOverrides, ThemeProvider } from 'wiloke-react-core';
12 |
13 | const themeOverrides: ThemeOverrides = {
14 | fonts: {
15 | primary: 'Roboto, sans-serif',
16 | secondary: 'Roboto, sans-serif',
17 | ...
18 | },
19 | colors: {
20 | primary: '#5353C5',
21 | secondary: '#2DDE98',
22 | tertiary: '#D14A66',
23 | quaternary: '#FFC20E',
24 | light: '#ffffff',
25 | gray1: '#F7F6F9',
26 | gray2: '#F0F0F2',
27 | gray3: '#ECEBEE',
28 | gray4: '#DBDADE',
29 | gray5: '#a9a7ad',
30 | gray6: '#929099',
31 | gray7: '#75737C',
32 | gray8: '#3D3D47',
33 | gray9: '#27262B',
34 | dark: '#19181b',
35 | },
36 | nightModeColors: {
37 | dark: '#ffffff',
38 | gray9: '#F7F6F9',
39 | gray8: '#F0F0F2',
40 | gray7: '#ECEBEE',
41 | gray6: '#DBDADE',
42 | gray5: '#a9a7ad',
43 | gray4: '#929099',
44 | gray3: '#75737C',
45 | gray2: '#3D3D47',
46 | gray1: '#27262B',
47 | light: '#202024',
48 | },
49 | round: 6,
50 | ...
51 | };
52 |
53 | const AppContent = () => {
54 | return content...;
55 | };
56 | ```
57 |
58 | [Prev][2] | [Next][4]
59 |
--------------------------------------------------------------------------------
/docs/img/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/long-hp/react-typescript-boilerplate/545789ef7ce543a9fa09a891bc6856100c0f3c0a/docs/img/1.png
--------------------------------------------------------------------------------
/docs/img/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/long-hp/react-typescript-boilerplate/545789ef7ce543a9fa09a891bc6856100c0f3c0a/docs/img/2.png
--------------------------------------------------------------------------------
/docs/js/reselect.md:
--------------------------------------------------------------------------------
1 | [9]: ./routing.md
2 |
3 |
4 |
5 | # Reselect
6 |
7 | Đọc thêm reselect tại đây [https://github.com/reduxjs/reselect](https://github.com/reduxjs/reselect)
8 |
9 | ```tsx
10 | export const todolistSelector = (state: AppState) => state.todolist;
11 | ```
12 |
13 |
14 |
15 | [Prev][9] | [Xem thêm UI Base + Components tại đây](https://5f5b43872be3560022d03ffc-yvzedefutx.chromatic.com/?path=/story/start-welcome--colors)
16 |
--------------------------------------------------------------------------------
/docs/js/routing.md:
--------------------------------------------------------------------------------
1 | [8]: ./redux-saga.md
2 | [10]: ./reselect.md
3 |
4 |
5 |
6 | # Routing
7 |
8 | Để thêm một route mới:
9 |
10 | - Trong routes/index.tsx định nghĩa đường dẫn và component
11 |
12 | ```tsx
13 | import React from 'react';
14 | import { BrowserRouter, Switch, Route } from 'react-router-dom';
15 | import { DemoPage } from 'containers/DemoPage';
16 | import { NotFoundPage } from 'containers/NotFoundPage';
17 | import { HomePage } from 'containers/HomePage';
18 | import { Page } from './types';
19 |
20 | export const pages: Page[] = [
21 | {
22 | path: '/',
23 | exact: true,
24 | component: HomePage,
25 | },
26 | {
27 | path: '/demo',
28 | exact: true,
29 | component: DemoPage,
30 | },
31 | ];
32 |
33 | const Routes = () => {
34 | return (
35 |
36 |
37 | {pages.map(({ component, path, exact }) => {
38 | return ;
39 | })}
40 |
41 |
42 |
43 | );
44 | };
45 |
46 | export default Routes;
47 | ```
48 |
49 | - Trong routes/types.tsx định nghĩa các location state
50 |
51 | ```tsx
52 | import { ComponentType } from 'react';
53 |
54 | export interface HomePageLocationState {}
55 | export interface AboutPageLocationState {}
56 | export interface DemoPageLocationState {}
57 |
58 | export interface LocationStates {
59 | '/'?: HomePageLocationState;
60 | '/demo'?: DemoPageLocationState;
61 | '/about': AboutPageLocationState;
62 | }
63 |
64 | export type PathName = keyof LocationStates;
65 |
66 | export interface Page {
67 | path: PathName;
68 | exact?: boolean;
69 | component: ComponentType;
70 | }
71 |
72 | export type Role = 'admin' | 'user';
73 |
74 | ```
75 |
76 |
77 |
78 | [Prev][8] | [Next][10]
79 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/long-hp/react-typescript-boilerplate/545789ef7ce543a9fa09a891bc6856100c0f3c0a/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
15 |
16 |
18 |
19 |
22 |
24 |
26 |
28 |
37 | React App
38 |
39 |
40 |
41 |
42 |
43 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/long-hp/react-typescript-boilerplate/545789ef7ce543a9fa09a891bc6856100c0f3c0a/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/long-hp/react-typescript-boilerplate/545789ef7ce543a9fa09a891bc6856100c0f3c0a/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/rc-generate.config.js:
--------------------------------------------------------------------------------
1 | const styles = `
2 | .container {
3 | position: relative;
4 | }
5 | `;
6 | const actions = `
7 | const getTodo = () => {
8 | return {
9 | type: 'GET_TODO',
10 | payload: {}
11 | }
12 | }
13 | `;
14 | const reducers = ``;
15 | const sagas = ``;
16 | const thunks = ``;
17 |
18 | const config = {
19 | baseUrl: 'src',
20 | typescript: true,
21 | reactNative: false,
22 | createIndexFile: true,
23 | templates: {
24 | styles,
25 | actions,
26 | reducers,
27 | sagas,
28 | thunks,
29 | },
30 | };
31 | module.exports = config;
32 |
--------------------------------------------------------------------------------
/src/@types/react-router-dom.d.ts:
--------------------------------------------------------------------------------
1 | import { match } from 'react-router';
2 | import * as React from 'react';
3 | import * as H from 'history';
4 | import { PathName } from 'routes/types';
5 | import { GetState, LiteralUnion, LiteralUnionAndObject } from './type';
6 |
7 | declare module 'react-router-dom' {
8 | export interface Location {
9 | pathname: LiteralUnion
;
10 | state?: GetState
;
11 | search?: H.Search;
12 | hash: H.Hash;
13 | key?: H.LocationKey;
14 | }
15 |
16 | export interface LocationDescriptorObject
{
17 | pathname: LiteralUnion
;
18 | state: GetState
;
19 | search?: H.Search;
20 | hash?: H.Hash;
21 | key?: H.LocationKey;
22 | }
23 |
24 | export type LocationDescriptor
= LiteralUnionAndObject
| LocationDescriptorObject
;
25 |
26 | export {
27 | generatePath,
28 | Prompt,
29 | MemoryRouter,
30 | RedirectProps,
31 | Redirect,
32 | RouteChildrenProps,
33 | RouteComponentProps,
34 | RouteProps,
35 | Route,
36 | Router,
37 | StaticRouter,
38 | SwitchProps,
39 | Switch,
40 | match,
41 | matchPath,
42 | withRouter,
43 | RouterChildContext,
44 | useHistory,
45 | useLocation,
46 | useParams,
47 | useRouteMatch,
48 | } from 'react-router';
49 |
50 | export interface BrowserRouterProps {
51 | basename?: string;
52 | getUserConfirmation?: (message: string, callback: (ok: boolean) => void) => void;
53 | forceRefresh?: boolean;
54 | keyLength?: number;
55 | }
56 | export class BrowserRouter extends React.Component {}
57 |
58 | export interface HashRouterProps {
59 | basename?: string;
60 | getUserConfirmation?: (message: string, callback: (ok: boolean) => void) => void;
61 | hashType?: 'slash' | 'noslash' | 'hashbang';
62 | }
63 | export class HashRouter extends React.Component {}
64 |
65 | export interface LinkProps extends React.AnchorHTMLAttributes {
66 | component?: React.ComponentType;
67 | to: LocationDescriptor | ((location: Location) => LocationDescriptorObject);
68 | replace?: boolean;
69 | innerRef?: React.Ref;
70 | }
71 | export class Link extends React.Component, any> {}
72 |
73 | export interface NavLinkProps> extends LinkProps
{
74 | activeClassName?: string;
75 | activeStyle?: React.CSSProperties;
76 | exact?: boolean;
77 | strict?: boolean;
78 | isActive?(match: match, location: Location): boolean;
79 | location?: Location
;
80 | }
81 | export class NavLink
extends React.Component, any> {}
82 |
83 | export interface RedirectProps {
84 | to: LocationDescriptor;
85 | push?: boolean;
86 | from?: string;
87 | path?: string;
88 | exact?: boolean;
89 | strict?: boolean;
90 | }
91 | export class Redirect extends React.Component> {}
92 | }
93 |
--------------------------------------------------------------------------------
/src/@types/react-router-overrides.d.ts:
--------------------------------------------------------------------------------
1 | import * as H from 'history';
2 | import { PathName } from 'routes/types';
3 | import { LiteralUnion, GetState } from 'type';
4 | declare module 'react-router' {
5 | export interface LocationDescriptorObject {
6 | pathname: LiteralUnion
;
7 | state: GetState
;
8 | search?: H.Search;
9 | hash?: H.Hash;
10 | key?: H.LocationKey;
11 | }
12 | export interface Location
{
13 | readonly ancestorOrigins: DOMStringList;
14 | hash: string;
15 | host: string;
16 | hostname: string;
17 | href: string;
18 | toString(): string;
19 | readonly origin: string;
20 | pathname: P;
21 | port: string;
22 | protocol: string;
23 | search: string;
24 | assign(url: LiteralUnion): void;
25 | reload(): void;
26 | /** @deprecated */
27 | reload(forcedReload: boolean): void;
28 | replace(url: LiteralUnion): void;
29 | }
30 | export interface History {
31 | length: number;
32 | action: H.Action;
33 | location: Location
;
34 | push
(location: LocationDescriptorObject
): void;
35 | push
(path: LiteralUnion
, state: GetState
): void;
36 | replace
(location: LocationDescriptorObject
): void;
37 | replace
(path: LiteralUnion
, state: GetState
): void;
38 | go(n: number): void;
39 | goBack(): void;
40 | goForward(): void;
41 | block(prompt?: boolean | string | H.TransitionPromptHook): H.UnregisterCallback;
42 | listen(listener: H.LocationListener): H.UnregisterCallback;
43 | createHref(location: LocationDescriptorObject
): H.Href;
44 | }
45 |
46 | export function useHistory
(): History
;
47 | export function useLocation
(): Location
;
48 | }
49 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Provider } from 'react-redux';
3 | import { PersistGate } from 'redux-persist/lib/integration/react';
4 | import { AppContent } from 'containers/AppContent';
5 | import { store, persistor } from './store/configureStore';
6 |
7 | function App() {
8 | return (
9 |
10 | } persistor={persistor}>
11 |
12 |
13 |
14 | );
15 | }
16 |
17 | export default App;
18 |
--------------------------------------------------------------------------------
/src/components/Alert/Alert.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef, ReactNode } from 'react';
2 | import { LineAwesome, LineAwesomeName, Text, View, ViewProps } from 'wiloke-react-core';
3 | import * as css from './styles';
4 |
5 | export type AlertType = 'success' | 'info' | 'warning' | 'danger';
6 | export interface AlertProps extends Omit {
7 | /** Bật tắt nút ( X ) */
8 | closable?: boolean;
9 | /** Đoạn text mô tả */
10 | description?: string;
11 | /** Đoạn text size to giống title */
12 | message: string;
13 | /** Bật tắt icon phía bên trái */
14 | showIcon?: boolean;
15 | /** Chọn kiểu alert */
16 | type?: AlertType;
17 | /** Chọn kích thước */
18 | size?: 'small' | 'medium' | 'large';
19 | /** Render Icon */
20 | Icon?: ReactNode;
21 | /** Bấm nút close */
22 | onClose?: (event: React.MouseEvent) => void;
23 | }
24 |
25 | const Alert = forwardRef(
26 | ({ closable = true, description, message, showIcon = true, type = 'info', size = 'medium', Icon, onClose, borderWidth = 1, ...rest }, ref) => {
27 | const iconNameMapping: Record = {
28 | info: 'exclamation-circle',
29 | success: 'check-circle-o',
30 | warning: 'exclamation-triangle',
31 | danger: 'times-circle',
32 | };
33 |
34 | const renderAlertIcon = () => {
35 | if (!showIcon) {
36 | return null;
37 | }
38 | if (Icon) {
39 | return Icon;
40 | }
41 | return ;
42 | };
43 |
44 | const renderClose = () => {
45 | if (!closable) {
46 | return null;
47 | }
48 | return (
49 | ) => onClose?.(event)}
56 | />
57 | );
58 | };
59 |
60 | return (
61 |
68 | {renderAlertIcon()}
69 |
70 | {message}
71 |
72 | {description && (
73 |
74 | {description}
75 |
76 | )}
77 | {renderClose()}
78 |
79 |
80 | );
81 | },
82 | );
83 |
84 | export default Alert;
85 |
--------------------------------------------------------------------------------
/src/components/Alert/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Alert';
2 | // eslint-disable-next-line
3 | export type { AlertProps } from './Alert';
4 |
--------------------------------------------------------------------------------
/src/components/Alert/styles.ts:
--------------------------------------------------------------------------------
1 | import { css } from 'wiloke-react-core';
2 |
3 | export type Size = 'small' | 'medium' | 'large';
4 |
5 | const getSizeMapping = (...nums: number[]): Record => {
6 | return {
7 | small: nums[0],
8 | medium: nums[1],
9 | large: nums[2],
10 | };
11 | };
12 |
13 | export const container = css`
14 | debug: Alert-container;
15 | position: relative;
16 | overflow: hidden;
17 | padding: 15px;
18 | margin-bottom: 15px;
19 | border-style: solid;
20 | `;
21 |
22 | export const close = css`
23 | debug: Alert-close;
24 | position: absolute;
25 | top: 10px;
26 | right: 10px;
27 | cursor: pointer;
28 | `;
29 |
30 | export const enableClose = (closable: boolean) => {
31 | if (!closable) {
32 | return {};
33 | }
34 | return css`
35 | padding-right: 30px;
36 | `;
37 | };
38 |
39 | export const bgOverlay = css`
40 | debug: Alert-bgOverlay;
41 | position: absolute;
42 | top: 0;
43 | right: 0;
44 | bottom: 0;
45 | left: 0;
46 | z-index: -1;
47 | opacity: 0.05;
48 | `;
49 |
50 | export const icon = (size: Size) => css`
51 | debug: Alert-icon;
52 | position: absolute;
53 | left: 10px;
54 | cursor: pointer;
55 | top: ${getSizeMapping(15, 15, 16)[size]}px;
56 | font-size: ${getSizeMapping(18, 26, 30)[size]}px !important;
57 | `;
58 |
59 | export const message = (size: Size) => css`
60 | debug: Alert-message;
61 | display: block;
62 | font-size: ${getSizeMapping(14, 16, 20)[size]}px;
63 | `;
64 |
65 | export const description = (size: Size) => css`
66 | debug: Alert-description;
67 | margin-top: 4px;
68 | line-height: 22px;
69 | font-size: ${getSizeMapping(13, 14, 15)[size]}px;
70 | `;
71 |
72 | export const showIcon = (size: Size, showIcon: boolean) => {
73 | if (!showIcon) {
74 | return {};
75 | }
76 | return css`
77 | padding-left: ${size === 'small' ? 37 : 60}px;
78 | `;
79 | };
80 |
81 | export const small = css`
82 | debug: Alert-small;
83 | padding-top: 8px;
84 | padding-bottom: 8px;
85 | `;
86 |
--------------------------------------------------------------------------------
/src/components/Avatar/Avatar.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { View, Text, Image, ViewProps } from 'wiloke-react-core';
3 | import { memoization } from 'wiloke-react-core/utils';
4 | import avatarColors from './avatarColors';
5 | import AvatarLoading from './AvatarLoading';
6 | import * as css from './styles';
7 |
8 | export interface AvatarProps extends Pick {
9 | /** Kích thước của avatar */
10 | size?: number;
11 | /** Tên user */
12 | name?: string;
13 | /** Avatar uri */
14 | uri?: string;
15 | }
16 |
17 | interface AvatarStatic {
18 | Loading: typeof AvatarLoading;
19 | }
20 |
21 | const Avatar: FC & AvatarStatic = ({ uri, size = 30, name = '', ...rest }) => {
22 | const textSize = size / 2 < 30 ? size / 2 : 30;
23 | const nameMatch = name.match(/^[^0-9]|\d/g);
24 | const text = !!name ? (!!nameMatch ? nameMatch[0].toUpperCase() : '') : '';
25 | const backgroundIndex = Math.floor(text.charCodeAt(0) % avatarColors.length);
26 | const backgroundColor = avatarColors[backgroundIndex];
27 |
28 | if (!!uri) {
29 | return ;
30 | }
31 |
32 | return (
33 |
34 | {!!name && (
35 |
36 | {text}
37 |
38 | )}
39 |
40 | );
41 | };
42 |
43 | Avatar.Loading = AvatarLoading;
44 |
45 | export default memoization(Avatar);
46 |
--------------------------------------------------------------------------------
/src/components/Avatar/AvatarLoading.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo, FC } from 'react';
2 | import { ColorNames, View } from 'wiloke-react-core';
3 | import * as css from './styles';
4 |
5 | export interface AvatarLoadingProps {
6 | size?: number;
7 | color?: ColorNames;
8 | }
9 |
10 | const AvatarLoading: FC = ({ size = 30, color = 'gray2' }) => {
11 | return ;
12 | };
13 |
14 | export default memo(AvatarLoading);
15 |
--------------------------------------------------------------------------------
/src/components/Avatar/avatarColors.ts:
--------------------------------------------------------------------------------
1 | const avatarColors = [
2 | '#ffdd00',
3 | '#fbb034',
4 | '#ff4c4c',
5 | '#c1d82f',
6 | '#f48924',
7 | '#7ac143',
8 | '#30c39e',
9 | '#06BCAE',
10 | '#0695BC',
11 | '#037ef3',
12 | '#146eb4',
13 | '#8e43e7',
14 | '#ea1d5d',
15 | '#fc636b',
16 | '#ff6319',
17 | '#e01f3d',
18 | '#a0ac48',
19 | '#00d1b2',
20 | '#472f92',
21 | '#388ed1',
22 | '#a6192e',
23 | '#4a8594',
24 | '#7B9FAB',
25 | '#1393BD',
26 | '#5E13BD',
27 | '#E208A7',
28 | ] as const;
29 |
30 | export default avatarColors;
31 |
--------------------------------------------------------------------------------
/src/components/Avatar/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Avatar';
2 | // eslint-disable-next-line
3 | export type { AvatarProps } from './Avatar';
4 |
--------------------------------------------------------------------------------
/src/components/Avatar/styles.ts:
--------------------------------------------------------------------------------
1 | import { css } from 'wiloke-react-core';
2 |
3 | export const container = (size: number) => css`
4 | debug: Avatar-container;
5 | display: flex;
6 | align-items: center;
7 | justify-content: center;
8 | width: ${size}px;
9 | height: ${size}px;
10 | `;
11 |
12 | export const background = (backgroundColor: string) => css`
13 | background-color: ${backgroundColor};
14 | `;
15 |
--------------------------------------------------------------------------------
/src/components/Button/Button.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode, DOMAttributes, ButtonHTMLAttributes, forwardRef, Ref } from 'react';
2 | import { View, ActivityIndicator, Size, Text, useStyleSheet, ViewProps } from 'wiloke-react-core';
3 | import { classNames } from 'wiloke-react-core/utils';
4 | import * as css from './styles';
5 |
6 | export interface ButtonProps extends ViewProps {
7 | /** React children */
8 | children: ReactNode;
9 | /** Các kích thước của button */
10 | size?: Size;
11 | /** Bật lên sẽ dài full 100% */
12 | block?: boolean;
13 | /** Thuộc tính href của thẻ a */
14 | href?: string;
15 | /** Thuộc tính target của thẻ a nhưng bỏ "_" ở trước */
16 | target?: 'blank' | 'self' | 'parent' | 'top';
17 | /** Set css font-size */
18 | fontSize?: number;
19 | /** Khi bật disabled thì nút mờ đi và không thể thực hiện event */
20 | disabled?: boolean;
21 | /** Khi bật lên thì sẽ hiển thị icon loading bên trái */
22 | loading?: boolean;
23 | /** Thuộc tính type của thẻ button */
24 | type?: ButtonHTMLAttributes['type'];
25 | /** Sự kiện click */
26 | onClick?: DOMAttributes['onClick'];
27 | }
28 |
29 | const Button = forwardRef(
30 | (
31 | {
32 | href,
33 | children,
34 | target = 'self',
35 | className,
36 | size = 'medium',
37 | block = false,
38 | onClick,
39 | disabled = false,
40 | loading = false,
41 | type = 'button',
42 | fontSize,
43 | style,
44 | borderWidth,
45 | backgroundColor = 'primary',
46 | color = 'light',
47 | radius = 'square',
48 | ...rest
49 | },
50 | ref,
51 | ) => {
52 | const { styles } = useStyleSheet();
53 | const props: ViewProps = {
54 | ...rest,
55 | className: classNames(styles(css.container(size, borderWidth), css.block(block), css.disabled(disabled), css.fontSize(fontSize)), className),
56 | style,
57 | backgroundColor,
58 | radius,
59 | color,
60 | ...(disabled ? {} : { onClick }),
61 | };
62 | const renderChildren = () => {
63 | return (
64 | <>
65 | {loading && }
66 |
67 | {children}
68 |
69 | >
70 | );
71 | };
72 | if (!!href) {
73 | return (
74 | } href={href} rel="noopener noreferrer" target={`_${target}`} {...props}>
75 | {renderChildren()}
76 |
77 | );
78 | }
79 | return (
80 | } type={type} {...props}>
81 | {renderChildren()}
82 |
83 | );
84 | },
85 | );
86 |
87 | export default Button;
88 |
--------------------------------------------------------------------------------
/src/components/Button/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Button';
2 | // eslint-disable-next-line
3 | export type { ButtonProps } from './Button';
4 |
--------------------------------------------------------------------------------
/src/components/Button/styles.ts:
--------------------------------------------------------------------------------
1 | import { css, Size } from 'wiloke-react-core';
2 |
3 | interface StypeMapping {
4 | fontSize: number;
5 | paddingVertical: number;
6 | paddingHorizontal: number;
7 | }
8 |
9 | const getSizeStyleMapping = (borderWidth = 0): Record => {
10 | return {
11 | 'extra-small': {
12 | fontSize: 12,
13 | paddingHorizontal: 8 - borderWidth,
14 | paddingVertical: 15 - borderWidth,
15 | },
16 | small: {
17 | fontSize: 14,
18 | paddingHorizontal: 12 - borderWidth,
19 | paddingVertical: 18 - borderWidth,
20 | },
21 | medium: {
22 | fontSize: 15,
23 | paddingHorizontal: 13 - borderWidth,
24 | paddingVertical: 22 - borderWidth,
25 | },
26 | large: {
27 | fontSize: 16,
28 | paddingHorizontal: 20 - borderWidth,
29 | paddingVertical: 38 - borderWidth,
30 | },
31 | };
32 | };
33 |
34 | export const container = (size: Size, borderWidth?: number) => css`
35 | debug: Button-container;
36 | display: inline-block;
37 | box-shadow: none;
38 | outline: none;
39 | cursor: pointer;
40 | appearance: none;
41 | line-height: 1.5;
42 | font-weight: 500;
43 | border: 0;
44 | transition: all 0.3s ease;
45 | font-size: ${getSizeStyleMapping(borderWidth)[size].fontSize}px;
46 | padding: ${getSizeStyleMapping(borderWidth)[size].paddingHorizontal}px ${getSizeStyleMapping(borderWidth)[size].paddingVertical}px;
47 | `;
48 |
49 | export const loading = css`
50 | debug: Button-loading;
51 | vertical-align: middle;
52 | margin-right: 8px;
53 | * {
54 | color: inherit !important;
55 | }
56 | `;
57 |
58 | export const text = css`
59 | debug: Button-text;
60 | vertical-align: middle;
61 | `;
62 |
63 | export const block = (block: boolean) => {
64 | if (!block) {
65 | return {};
66 | }
67 | return css`
68 | debug: Button-block;
69 | width: 100%;
70 | `;
71 | };
72 |
73 | export const disabled = (disabled: boolean) => {
74 | if (!disabled) {
75 | return {};
76 | }
77 | return css`
78 | debug: Button-disabled;
79 | opacity: 0.4;
80 | cursor: no-drop;
81 | `;
82 | };
83 |
84 | export const fontSize = (fontSize?: number) => {
85 | if (!fontSize) {
86 | return {};
87 | }
88 | return css`
89 | debug: Button-fontSize;
90 | font-size: ${fontSize}px;
91 | `;
92 | };
93 |
--------------------------------------------------------------------------------
/src/components/Checkbox/CheckboxLoading.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, memo } from 'react';
2 | import { Radius, View } from 'wiloke-react-core';
3 |
4 | export interface CheckboxLoadingProps {
5 | radius?: Radius;
6 | }
7 |
8 | const CheckboxLoading: FC = ({ radius = 'square' }) => {
9 | return (
10 |
11 |
12 |
13 |
14 | );
15 | };
16 |
17 | export default memo(CheckboxLoading);
18 |
--------------------------------------------------------------------------------
/src/components/Checkbox/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Checkbox';
2 | // eslint-disable-next-line
3 | export type { CheckboxProps } from './Checkbox';
--------------------------------------------------------------------------------
/src/components/Checkbox/styles.ts:
--------------------------------------------------------------------------------
1 | import { css, Size } from 'wiloke-react-core';
2 |
3 | const controlSizeMapping: Record = {
4 | large: 36,
5 | medium: 30,
6 | small: 24,
7 | 'extra-small': 18,
8 | };
9 |
10 | export const container = (disabled: boolean, size: Size) => css`
11 | font-size: ${size === 'extra-small' ? 12 : 14}px;
12 | cursor: ${disabled ? 'not-allowed' : 'pointer'};
13 | opacity: ${disabled ? 0.4 : 1};
14 | `;
15 |
16 | export const control = (size: Size) => css`
17 | position: relative;
18 | top: 0;
19 | left: 0;
20 | display: flex;
21 | overflow: hidden;
22 | justify-content: center;
23 | align-items: center;
24 | width: ${controlSizeMapping[size]}px;
25 | height: ${controlSizeMapping[size]}px;
26 | `;
27 |
28 | export const icon = css`
29 | position: relative;
30 | z-index: 1;
31 | display: flex;
32 | justify-content: center;
33 | align-items: center;
34 | `;
35 |
36 | export const bgIcon = css`
37 | position: absolute;
38 | top: 0;
39 | right: 0;
40 | bottom: 0;
41 | left: 0;
42 | `;
43 |
44 | export const text = css`
45 | margin-left: 5px;
46 | display: inline-block;
47 | vertical-align: middle;
48 | `;
49 |
50 | export const innerWrap = css`
51 | position: relative;
52 | display: inline-block;
53 | vertical-align: middle;
54 | `;
55 |
56 | export const input = css`
57 | position: absolute;
58 | top: 0;
59 | right: 0;
60 | bottom: 0;
61 | left: 0;
62 | z-index: 1;
63 | opacity: 0;
64 | `;
65 |
--------------------------------------------------------------------------------
/src/components/CodeHighlight/CodeHighlight.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, memo, useEffect, useRef } from 'react';
2 | import './vendors/prism';
3 | import './vendors/prism.css';
4 |
5 | export interface CodeHighlightProps {
6 | /** Thay đổi ngôn ngữ lập trình */
7 | language?: 'css' | 'scss' | 'js' | 'jsx' | 'tsx' | 'php' | 'go' | 'java' | 'python' | 'typescript' | 'c' | 'cpp';
8 | /** Đoạn code cần hiển thị */
9 | children: string;
10 | }
11 |
12 | const CodeHighlight: FC = ({ language = 'js', children }) => {
13 | const _codeRef = useRef(null);
14 | const _prevChildren = useRef('');
15 |
16 | const _handleCodeHighlight = (): void => {
17 | if (_codeRef.current) {
18 | (window as any).Prism.highlightElement(_codeRef.current);
19 | }
20 | };
21 |
22 | useEffect(() => {
23 | if (_prevChildren.current !== children) {
24 | _handleCodeHighlight();
25 | }
26 | _prevChildren.current = children;
27 | }, [children]);
28 |
29 | return (
30 |
31 |
32 | {children.trim()}
33 |
34 |
35 | );
36 | };
37 |
38 | export default memo(CodeHighlight);
39 |
--------------------------------------------------------------------------------
/src/components/CodeHighlight/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './CodeHighlight';
2 | // eslint-disable-next-line
3 | export type { CodeHighlightProps } from './CodeHighlight';
4 |
--------------------------------------------------------------------------------
/src/components/Collapse/Collapse.tsx:
--------------------------------------------------------------------------------
1 | import React, { Children, FC, ReactElement, useCallback, useState } from 'react';
2 | import { CollapseDispatchProvider, CollapseStateProvider } from './context/CollapseContext';
3 | import { useCollapseDispatch } from './hooks/useCollapseDispatch';
4 | import Panel, { PanelProps } from './Panel';
5 | import { parseValueToArray } from './utils/parseValueToArray';
6 |
7 | type KeyStateType = string | string[];
8 |
9 | export interface CollapseProps {
10 | /** Active key của panel */
11 | activeKey?: KeyStateType;
12 | /** Default Active key của panel có thể điền 1 hoặc nhiều key. VD: defaultActiveKey="1" | defaultActiveKey={['0','1']} */
13 | defaultActiveKey?: KeyStateType;
14 | /** disabled panel */
15 | disabled?: boolean;
16 | /** Sự kiện onChange */
17 | onChange?: (panelKey: KeyStateType) => void;
18 | }
19 |
20 | interface CollapseStatic {
21 | Panel: typeof Panel;
22 | }
23 |
24 | const Collapse: FC & CollapseStatic = ({ activeKey, defaultActiveKey, disabled, onChange, children }) => {
25 | const dispatchContext = useCollapseDispatch();
26 | const currentActiveKey = activeKey ? activeKey : defaultActiveKey;
27 | const [activeStateKey, setActiveStateKey] = useState(parseValueToArray(currentActiveKey as KeyStateType));
28 |
29 | const _handleChange = (key: KeyStateType) => {
30 | if (!activeKey) {
31 | setActiveStateKey(key as string[]);
32 | }
33 | onChange?.(key);
34 | dispatchContext?.onChange?.(key);
35 | };
36 |
37 | const _handlePanelClick = useCallback(
38 | (key: string) => {
39 | const _activeKey = [...activeStateKey];
40 | const idx = _activeKey.indexOf(key);
41 | const isActive = idx > -1;
42 |
43 | if (isActive) {
44 | _activeKey.splice(idx, 1); // remove key when panel is not active
45 | } else {
46 | _activeKey.push(key); // add key to array when click
47 | }
48 |
49 | _handleChange(_activeKey);
50 | },
51 | // eslint-disable-next-line react-hooks/exhaustive-deps
52 | [activeStateKey],
53 | );
54 |
55 | const _renderNewChild = (child: ReactElement, index: string | number) => {
56 | if (!child) return null;
57 | const activeKey = activeStateKey;
58 | const key = String(index);
59 | let isActive = false;
60 | isActive = (activeKey as KeyStateType)?.includes(key);
61 |
62 | const props: PanelProps = {
63 | panelKey: key,
64 | active: isActive,
65 | onClick: _handlePanelClick,
66 | };
67 | const newElement = React.cloneElement(child, props);
68 | return newElement;
69 | };
70 |
71 | const _renderChildren = () => {
72 | return Children.map(children as ReactElement, _renderNewChild);
73 | };
74 |
75 | return (
76 |
77 | {_renderChildren()}
78 |
79 | );
80 | };
81 |
82 | Collapse.Panel = Panel;
83 |
84 | export default Collapse;
85 |
--------------------------------------------------------------------------------
/src/components/Collapse/Panel.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, ReactNode, useState } from 'react';
2 | import { LineAwesome, Text, View, ViewProps } from 'wiloke-react-core';
3 | import { classNames, memoization } from 'wiloke-react-core/utils';
4 | import { useCollapseState } from './hooks/useCollapseState';
5 | import * as css from './styles';
6 |
7 | export interface PanelProps
8 | extends Pick {
9 | children?: ReactNode;
10 | /** Title header của panel */
11 | header?: ReactNode;
12 | /** Bật lên sẽ hiển thị mũi tên bên góc phải */
13 | showArrow?: boolean;
14 | /** Panel đang được active sẽ hiển thị */
15 | active?: boolean;
16 | /** Id key của panel */
17 | panelKey?: string;
18 | /** Bật lên panel sẽ bị mờ và không bấm được vào */
19 | disabled?: boolean;
20 | /** Sự kiện onClick */
21 | onClick?: (panelKey: string) => void;
22 | }
23 |
24 | const Panel: FC = ({
25 | showArrow = true,
26 | active = false,
27 | disabled = false,
28 | panelKey,
29 | header = 'Panel header',
30 | style,
31 | className,
32 | children,
33 | radius = 5,
34 | backgroundColor = 'gray2',
35 | borderColor = 'gray5',
36 | borderWidth = 1,
37 | borderStyle = 'solid',
38 | onClick,
39 | }) => {
40 | const [activePanel, setActive] = useState(active);
41 | const stateContext = useCollapseState();
42 | const expandIcon = activePanel ? : ;
43 |
44 | if (stateContext) {
45 | disabled = disabled || (stateContext.disabled as boolean);
46 | }
47 |
48 | const _handleClick = () => {
49 | if (disabled) {
50 | return;
51 | }
52 | setActive(activePanel => !activePanel);
53 | onClick?.(panelKey as string);
54 | };
55 |
56 | return (
57 |
68 | {/* header */}
69 |
70 |
71 |
72 | {header}
73 |
74 |
75 | {showArrow && expandIcon}
76 |
77 |
78 | {/* content */}
79 |
80 | {children}
81 |
82 |
83 | );
84 | };
85 |
86 | export default memoization(Panel);
87 |
--------------------------------------------------------------------------------
/src/components/Collapse/context/CollapseContext.tsx:
--------------------------------------------------------------------------------
1 | import { createContext } from 'react';
2 |
3 | type KeyStateValue = string | string[];
4 | export interface CollapseState {
5 | /** Active key của panel */
6 | activeKey?: KeyStateValue;
7 | /** Default Active key của panel */
8 | defaultActiveKey?: KeyStateValue;
9 | /** disabled panel */
10 | disabled?: boolean;
11 | }
12 | export interface CollapseDispatch {
13 | onChange?: (activeKey: KeyStateValue) => void;
14 | }
15 |
16 | export const CollapseStateContext = createContext(null);
17 | export const CollapseDispatchContext = createContext(null);
18 |
19 | export const CollapseStateProvider = CollapseStateContext.Provider;
20 | export const CollapseDispatchProvider = CollapseDispatchContext.Provider;
21 |
--------------------------------------------------------------------------------
/src/components/Collapse/hooks/useCollapseDispatch.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import { CollapseDispatchContext } from '../context/CollapseContext';
3 |
4 | export const useCollapseDispatch = () => {
5 | const action = useContext(CollapseDispatchContext);
6 | if (action === undefined) {
7 | throw new Error('useCollapseDispatch phải được dùng bên trong CollapseDispatchProvider');
8 | }
9 | return action;
10 | };
11 |
--------------------------------------------------------------------------------
/src/components/Collapse/hooks/useCollapseState.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import { CollapseStateContext } from '../context/CollapseContext';
3 |
4 | export const useCollapseState = () => {
5 | const state = useContext(CollapseStateContext);
6 | if (state === undefined) {
7 | throw new Error('useCollapseState phải được dùng bên trong CollapseStateProvider');
8 | }
9 | return state;
10 | };
11 |
--------------------------------------------------------------------------------
/src/components/Collapse/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Collapse';
2 | // eslint-disable-next-line
3 | export type { CollapseProps } from './Collapse';
--------------------------------------------------------------------------------
/src/components/Collapse/styles.ts:
--------------------------------------------------------------------------------
1 | import { css } from 'wiloke-react-core';
2 |
3 | export const container = (activePanel: boolean) => css`
4 | min-height: 46px;
5 | padding-left: 12px;
6 | padding-right: 12px;
7 | padding-bottom: ${activePanel ? 16 : 0}px;
8 | margin-bottom: 8px;
9 | `;
10 |
11 | export const disabled = (disabled: boolean) => {
12 | if (!disabled) {
13 | return {};
14 | }
15 | return css`
16 | opacity: 0.4;
17 | cursor: no-drop;
18 | `;
19 | };
20 |
21 | export const headerContainer = css`
22 | display: flex;
23 | align-items: center;
24 | justify-content: space-between;
25 | `;
26 |
27 | export const headerPanel = css`
28 | height: 46px;
29 | cursor: pointer;
30 | transition: all 0.3s;
31 | display: flex;
32 | align-items: center;
33 | flex-grow: 1;
34 | `;
35 |
36 | export const headerText = css`
37 | font-size: 14px;
38 | `;
39 |
40 | export const headerIcon = css`
41 | transition: 0.2s all ease;
42 | > * {
43 | transition: 0.2s all ease;
44 | }
45 | `;
46 |
47 | export const panelContainer = (activePanel: boolean) => css`
48 | display: ${!activePanel ? 'none' : 'block'};
49 | padding: 6px;
50 | transition: 280ms cubic-bezier(0.4, 0, 0.2, 1);
51 | `;
52 |
--------------------------------------------------------------------------------
/src/components/Collapse/utils/parseValueToArray.ts:
--------------------------------------------------------------------------------
1 | export const parseValueToArray = (value: string | string[]) => {
2 | let currentValue = value;
3 | if (!Array.isArray(currentValue)) {
4 | currentValue = currentValue ? [currentValue] : [];
5 | }
6 | return currentValue.map(item => String(item));
7 | };
8 |
--------------------------------------------------------------------------------
/src/components/ColorPicker/ColorPickerLoading.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { View } from 'wiloke-react-core';
3 | import * as css from './styles';
4 |
5 | export interface ColorPickerLoadingProps {}
6 |
7 | const ColorPickerLoading: FC = () => {
8 | return ;
9 | };
10 |
11 | export default ColorPickerLoading;
12 |
--------------------------------------------------------------------------------
/src/components/ColorPicker/decimalToHex.ts:
--------------------------------------------------------------------------------
1 | export const decimalToHex = (alpha: number) => (alpha === 0 ? '00' : Math.round(255 * alpha).toString(16));
2 |
--------------------------------------------------------------------------------
/src/components/ColorPicker/hexToRgb.ts:
--------------------------------------------------------------------------------
1 | export interface RGB {
2 | b: number;
3 | g: number;
4 | r: number;
5 | }
6 |
7 | function checkHex(hex: string) {
8 | if (hex.length === 4) {
9 | const [r, g, b] = hex.replace('#', '').split('');
10 | return `#${r}${r}${g}${g}${b}${b}`;
11 | }
12 | return hex;
13 | }
14 |
15 | function hexToRgb(hex: string): RGB {
16 | const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(checkHex(hex)) as RegExpExecArray;
17 | return {
18 | r: parseInt(result[1], 16),
19 | g: parseInt(result[2], 16),
20 | b: parseInt(result[3], 16),
21 | };
22 | }
23 |
24 | export default hexToRgb;
25 |
--------------------------------------------------------------------------------
/src/components/ColorPicker/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './ColorPicker';
2 | // eslint-disable-next-line
3 | export type { ColorPickerProps, ColorPickerType, Placement, Strategy, PresetColor } from './ColorPicker';
4 |
--------------------------------------------------------------------------------
/src/components/ColorPicker/styles.ts:
--------------------------------------------------------------------------------
1 | import { css, Theme } from 'wiloke-react-core';
2 |
3 | type Placement =
4 | | 'top'
5 | | 'top-start'
6 | | 'top-end'
7 | | 'right'
8 | | 'right-start'
9 | | 'right-end'
10 | | 'bottom'
11 | | 'bottom-start'
12 | | 'bottom-end'
13 | | 'left'
14 | | 'left-start'
15 | | 'left-end';
16 |
17 | export const container = css`
18 | padding: 8px 8px 0;
19 | background-color: #ffffff;
20 | border: none !important;
21 | border-radius: 8px;
22 |
23 | z-index: 9999;
24 | `;
25 |
26 | export const targetPicker = css`
27 | position: relative;
28 | z-index: 1;
29 | cursor: pointer;
30 | width: 42px;
31 | height: 20px;
32 | `;
33 |
34 | export const targetBackground = css`
35 | width: 100%;
36 | height: 100%;
37 |
38 | &:after {
39 | content: '';
40 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==');
41 | position: absolute;
42 | background-repeat: repeat;
43 | background-size: auto;
44 | top: 0;
45 | left: 0;
46 | bottom: 0;
47 | right: 0;
48 | z-index: -1;
49 | }
50 | `;
51 |
52 | export const placement = (placement: Placement) => {
53 | switch (placement) {
54 | case 'bottom':
55 | case 'bottom-end':
56 | case 'bottom-start': {
57 | return css`
58 | margin-top: 8px;
59 | z-index: 9999;
60 |
61 | &:after {
62 | content: '';
63 | display: none;
64 | }
65 | `;
66 | }
67 | case 'top':
68 | case 'top-end':
69 | case 'top-start': {
70 | return css`
71 | margin-bottom: 8px;
72 | z-index: 9999;
73 |
74 | &:after {
75 | content: '';
76 | display: none;
77 | }
78 | `;
79 | }
80 | case 'left':
81 | case 'left-end':
82 | case 'left-start': {
83 | return css`
84 | margin-right: 8px;
85 | z-index: 9999;
86 |
87 | &:after {
88 | content: '';
89 | display: none;
90 | }
91 | `;
92 | }
93 | case 'right':
94 | case 'right-end':
95 | case 'right-start': {
96 | return css`
97 | margin-left: 8px;
98 | z-index: 9999;
99 | &:after {
100 | content: '';
101 | display: none;
102 | }
103 | `;
104 | }
105 | default:
106 | return css`
107 | z-index: 9999;
108 | &:after {
109 | content: '';
110 | display: none;
111 | }
112 | `;
113 | }
114 | };
115 |
116 | export const loadingContainer = ({ colors }: Theme) => css`
117 | width: 50px;
118 | height: 23px;
119 | border-radius: 6px;
120 | background-color: ${colors.gray5};
121 | `;
122 |
--------------------------------------------------------------------------------
/src/components/ColorPicker/utils/rgbToHex.ts:
--------------------------------------------------------------------------------
1 | function componentToHex(c: number) {
2 | const hex: string = c.toString(16);
3 | return hex.length === 1 ? `0${hex}` : hex;
4 | }
5 |
6 | function rgbToHex(r: number, g: number, b: number) {
7 | return `#${componentToHex(r)}${componentToHex(g)}${componentToHex(b)}`;
8 | }
9 |
10 | export default rgbToHex;
11 |
--------------------------------------------------------------------------------
/src/components/ColorPicker/utils/rgbaObjectToString.ts:
--------------------------------------------------------------------------------
1 | import { RGBColor } from 'react-color';
2 |
3 | const rgbaObjectToString = (color: RGBColor) => `rgba(${color.r},${color.g},${color.b},${color.a})`;
4 |
5 | export default rgbaObjectToString;
6 |
--------------------------------------------------------------------------------
/src/components/ColorPicker/utils/rgbaStringToObject.ts:
--------------------------------------------------------------------------------
1 | import { RGBColor } from 'react-color';
2 |
3 | const rgbaStringToObject = (color: string): RGBColor => {
4 | const [r, g, b, a] = color.replace(/rgba\(|\)|\s+/g, '').split(',');
5 | return {
6 | r: Number(r),
7 | g: Number(g),
8 | b: Number(b),
9 | a: Number(a),
10 | };
11 | };
12 |
13 | export default rgbaStringToObject;
14 |
--------------------------------------------------------------------------------
/src/components/ColorPickerBeauty/ColorPickerBeauty.tsx:
--------------------------------------------------------------------------------
1 | import ColorPicker, { ColorPickerProps } from 'components/ColorPicker';
2 | import Box from 'components/FieldBox';
3 | import React, { FC } from 'react';
4 | import { ColorNames, Radius, Text, View, ViewProps } from 'wiloke-react-core';
5 | import ColorPickerBeautyLoading from './ColorPickerBeautyLoading';
6 | import * as css from './styles';
7 |
8 | export interface ColorPickerBeautyProps
9 | extends Pick,
10 | Pick {
11 | /** Background color của box */
12 | backgroundInnerField?: ColorNames;
13 | /** Radius của field box */
14 | radiusBox?: Radius;
15 | /** Radius của color picker */
16 | radiusPicker?: Radius;
17 | }
18 |
19 | const ColorPickerBeauty: FC & {
20 | Loading: typeof ColorPickerBeautyLoading;
21 | } = ({
22 | placement = 'bottom-start',
23 | backgroundInnerField = 'light',
24 | strategy = 'absolute',
25 | color,
26 | borderStyle = 'solid',
27 | borderColor = 'gray5',
28 | radiusPicker = 5,
29 | radiusBox = 5,
30 | borderWidth = 1,
31 | pickerType,
32 | onChange,
33 | onChangeComplete,
34 | }) => {
35 | return (
36 |
44 |
45 | (
54 |
55 | {color}
56 |
57 | )}
58 | />
59 |
60 |
61 | );
62 | };
63 |
64 | ColorPickerBeauty.Loading = ColorPickerBeautyLoading;
65 |
66 | export default ColorPickerBeauty;
67 |
--------------------------------------------------------------------------------
/src/components/ColorPickerBeauty/ColorPickerBeautyLoading.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { View } from 'wiloke-react-core';
3 | import * as css from './styles';
4 |
5 | export interface ColorPickerBeautyLoadingProps {}
6 |
7 | const ColorPickerBeautyLoading: FC = () => {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | };
19 | export default ColorPickerBeautyLoading;
20 |
--------------------------------------------------------------------------------
/src/components/ColorPickerBeauty/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './ColorPickerBeauty';
2 | // eslint-disable-next-line
3 | export type { ColorPickerBeautyProps } from './ColorPickerBeauty';
--------------------------------------------------------------------------------
/src/components/ColorPickerBeauty/styles.ts:
--------------------------------------------------------------------------------
1 | import { css, Theme } from 'wiloke-react-core';
2 |
3 | export const container = css`
4 | position: relative;
5 | `;
6 |
7 | export const box = css`
8 | height: 46px;
9 | `;
10 |
11 | export const colorDetailsContainer = css`
12 | display: inherit;
13 | margin-left: 32px;
14 | `;
15 |
16 | export const colorDetails = ({ colors }: Theme) => css`
17 | font-size: 14px;
18 | color: ${colors.gray7};
19 | `;
20 |
21 | export const inner = css`
22 | display: flex;
23 | align-items: center;
24 | justify-content: space-between;
25 | width: 100%;
26 | height: 100%;
27 | border-radius: 6px;
28 | `;
29 |
30 | export const loadingContainer = css`
31 | position: relative;
32 | overflow: hidden;
33 | height: 46px;
34 | `;
35 |
36 | export const loadingInner = ({ colors }: Theme) => css`
37 | padding: 5px 10px;
38 | height: 100%;
39 | border-radius: 6px;
40 | display: flex;
41 | align-items: center;
42 | background-color: ${colors.gray5};
43 | `;
44 |
45 | export const loadingInnerLeft = ({ colors }: Theme) => css`
46 | width: 50px;
47 | height: 24px;
48 | background-color: ${colors.gray4};
49 | border-radius: 5px;
50 | `;
51 |
52 | export const loadingInnerRight = ({ colors }: Theme) => css`
53 | width: 100px;
54 | height: 4px;
55 | background-color: ${colors.gray4};
56 | border-radius: 5px;
57 | margin-left: 32px;
58 | `;
59 |
--------------------------------------------------------------------------------
/src/components/Field/Field.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, ReactNode } from 'react';
2 | import { Text, View, withStyles, ViewProps } from 'wiloke-react-core';
3 | import * as css from './styles';
4 |
5 | export interface FieldProps extends ViewProps {
6 | children: ReactNode;
7 | /** Label của field có thể có hoặc không */
8 | label?: ReactNode;
9 | /** Font-size của label */
10 | fontSize?: number;
11 | /** Note của Field */
12 | note?: string;
13 | }
14 |
15 | const FieldComponent: FC = ({ label, children, color = 'gray9', fontSize = 14, note, ...rest }) => {
16 | return (
17 |
18 | {!!label && (
19 |
20 | {label}
21 |
22 | )}
23 | {children}
24 |
25 | {note}
26 |
27 |
28 | );
29 | };
30 |
31 | const Field = withStyles(FieldComponent);
32 |
33 | export default Field;
34 |
--------------------------------------------------------------------------------
/src/components/Field/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Field';
2 | // eslint-disable-next-line
3 | export type { FieldProps } from './Field';
4 |
--------------------------------------------------------------------------------
/src/components/Field/styles.ts:
--------------------------------------------------------------------------------
1 | import { css, Theme } from 'wiloke-react-core';
2 |
3 | export const container = css`
4 | margin-bottom: 20px;
5 | position: relative;
6 | `;
7 |
8 | export const label = css`
9 | line-height: 1;
10 | margin-bottom: 5px;
11 | margin-top: 0px;
12 | `;
13 |
14 | export const note = ({ colors }: Theme) => css`
15 | font-size: 12px;
16 | margin: 0px;
17 | margin-top: 3px;
18 | color: ${colors.gray6};
19 | `;
20 |
--------------------------------------------------------------------------------
/src/components/FieldBox/FieldBox.tsx:
--------------------------------------------------------------------------------
1 | import React, { CSSProperties, FC } from 'react';
2 | import { useStyleSheet, View, ViewProps } from 'wiloke-react-core';
3 | import { classNames } from 'wiloke-react-core/utils';
4 | import * as css from './styles';
5 |
6 | export interface BoxProps extends ViewProps {
7 | style?: CSSProperties;
8 | className?: string;
9 | }
10 |
11 | const Box: FC = ({
12 | borderColor = 'gray5',
13 | borderStyle = 'solid',
14 | borderWidth = 1,
15 | backgroundColor = 'light',
16 | radius = 5,
17 | className,
18 | style,
19 | children,
20 | ...rest
21 | }) => {
22 | const { styles } = useStyleSheet();
23 | const combineProps = { style, className: classNames(className, styles(css.container)), ...rest };
24 | return (
25 |
33 | {children}
34 |
35 | );
36 | };
37 |
38 | export default Box;
39 |
--------------------------------------------------------------------------------
/src/components/FieldBox/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './FieldBox';
2 | // eslint-disable-next-line
3 | export type { BoxProps } from './FieldBox';
4 |
--------------------------------------------------------------------------------
/src/components/FieldBox/styles.ts:
--------------------------------------------------------------------------------
1 | import { css } from 'wiloke-react-core';
2 |
3 | export const container = css`
4 | padding: 0 12px;
5 | `;
6 |
--------------------------------------------------------------------------------
/src/components/HoverLazy/HoverLazy.tsx:
--------------------------------------------------------------------------------
1 | import React, { ComponentType, FC } from 'react';
2 | import { View } from 'wiloke-react-core';
3 |
4 | export interface HoverLazyProps {
5 | chunks: (() => Promise<{ default: ComponentType }>)[];
6 | }
7 |
8 | const HoverLazy: FC = ({ chunks, children }) => {
9 | const handleMouseEnter = () => {
10 | chunks.forEach(fn => {
11 | fn();
12 | });
13 | };
14 | return {children};
15 | };
16 |
17 | export default HoverLazy;
18 |
--------------------------------------------------------------------------------
/src/components/HoverLazy/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './HoverLazy';
2 | // eslint-disable-next-line
3 | export type { HoverLazyProps } from './HoverLazy';
4 |
--------------------------------------------------------------------------------
/src/components/IconText/IconText.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, memo } from 'react';
2 | import { LineAwesome, View, Text, LineAwesomeName } from 'wiloke-react-core';
3 | import * as css from './styles';
4 |
5 | export interface IconTextProps {
6 | iconColor?: string;
7 | iconName: LineAwesomeName;
8 | title: string;
9 | text: string;
10 | }
11 |
12 | const IconText: FC = ({ iconColor = '#FD9B9B', iconName, title, text }) => {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 | {title}
21 |
22 | {text}
23 |
24 | );
25 | };
26 |
27 | export default memo(IconText);
28 |
--------------------------------------------------------------------------------
/src/components/IconText/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './IconText';
2 | // eslint-disable-next-line
3 | export type { IconTextProps } from './IconText';
4 |
--------------------------------------------------------------------------------
/src/components/IconText/styles.ts:
--------------------------------------------------------------------------------
1 | import { css } from 'wiloke-react-core';
2 |
3 | export const container = css`
4 | padding: 30px;
5 | `;
6 |
7 | export const icon = css`
8 | display: flex;
9 | justify-content: center;
10 | align-items: center;
11 | overflow: hidden;
12 | position: relative;
13 | z-index: 1;
14 | width: 60px;
15 | height: 60px;
16 | margin-bottom: 12px;
17 | `;
18 |
19 | export const iconBackground = (iconColor: string) => css`
20 | position: absolute;
21 | top: 0;
22 | right: 0;
23 | bottom: 0;
24 | left: 0;
25 | z-index: -1;
26 | opacity: 0.2;
27 | background-color: ${iconColor};
28 | `;
29 |
--------------------------------------------------------------------------------
/src/components/LinkPrefetch/LinkPrefetch.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, useRef, useState } from 'react';
2 | import { View } from 'wiloke-react-core';
3 |
4 | export interface LinkPrefetchProps {
5 | link: string;
6 | }
7 |
8 | const createLinkElement = (link: string) => {
9 | const linkEl = document.createElement('link');
10 | linkEl.setAttribute('rel', 'prefetch');
11 | linkEl.setAttribute('as', 'html');
12 | linkEl.setAttribute('href', link);
13 | return linkEl;
14 | };
15 |
16 | const LinkPrefetch: FC = ({ link, children }) => {
17 | const linkElRef = useRef(null);
18 | const timeoutIdRef = useRef(0);
19 | const [load, setLoad] = useState(false);
20 |
21 | const handleMouseEnter = () => {
22 | linkElRef.current = createLinkElement(link);
23 | timeoutIdRef.current = window.setTimeout(() => {
24 | setLoad(true);
25 | clearTimeout(timeoutIdRef.current);
26 | }, 500);
27 | if (!!linkElRef.current && !document.querySelector(`link[href="${link}"]`)) {
28 | document.head.appendChild(linkElRef.current);
29 | }
30 | };
31 |
32 | const handleMouseLeave = () => {
33 | if (!load) {
34 | clearTimeout(timeoutIdRef.current);
35 | linkElRef.current?.remove();
36 | }
37 | };
38 |
39 | return (
40 |
41 | {children}
42 |
43 | );
44 | };
45 |
46 | export default LinkPrefetch;
47 |
--------------------------------------------------------------------------------
/src/components/LinkPrefetch/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './LinkPrefetch';
2 | // eslint-disable-next-line
3 | export type { LinkPrefetchProps } from './LinkPrefetch';
4 |
--------------------------------------------------------------------------------
/src/components/NumberInput/Actions.tsx:
--------------------------------------------------------------------------------
1 | import React, { DOMAttributes, FC } from 'react';
2 | import { MaterialIcon, Size, View } from 'wiloke-react-core';
3 | import * as css from './styles';
4 |
5 | export interface ActionProps {
6 | size?: Exclude;
7 | increment?: DOMAttributes['onClick'];
8 | decrement?: DOMAttributes['onClick'];
9 | }
10 |
11 | const Action: FC = ({ size = 'medium', increment, decrement }) => {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | };
23 |
24 | export default Action;
25 |
--------------------------------------------------------------------------------
/src/components/NumberInput/NumberInput.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, InputHTMLAttributes, useEffect } from 'react';
2 | import { Size, useStyleSheet, View, ViewProps } from 'wiloke-react-core';
3 | import { classNames } from 'wiloke-react-core/utils';
4 | import Action from './Actions';
5 | import NumberInputLoading from './NumberInputLoading';
6 | import * as css from './styles';
7 | import useCount from './useCount';
8 |
9 | type InputType = 'number' | 'phone';
10 | export interface NumberInputProps extends ViewProps {
11 | /** Size của input */
12 | sizeInput?: Exclude;
13 | /** Bật lên input sẽ rộng 100% */
14 | block?: boolean;
15 | /** Kiểu đầu vào của input */
16 | type?: InputType;
17 | /** Giá trị đầu vào của input */
18 | value?: number;
19 | /** Khi bật disabled thì nút mờ đi và không thể thực hiện event */
20 | disabled?: boolean;
21 | /** Giá trị nhỏ nhất của input */
22 | min?: number;
23 | /** Giá trị lớn nhất của input */
24 | max?: number;
25 | /** Bước nhảy cho mỗi lần tăng / giảm giá trị */
26 | step?: number;
27 | /** Sự kiện onChange của input */
28 | onChange?: InputHTMLAttributes['onChange'];
29 | /** Sự kiện onValueChange của input, trả về dữ liệu dạng string(chuỗi) */
30 | onValueChange?: (number: number) => void;
31 | }
32 |
33 | const NumberInput: FC & {
34 | Loading: typeof NumberInputLoading;
35 | } = ({
36 | sizeInput = 'medium',
37 | type = 'number',
38 | value = 0,
39 | min = 0,
40 | max = 10,
41 | step = 1,
42 | block = false,
43 | disabled = false,
44 | className,
45 | color = 'gray8',
46 | backgroundColor = 'light',
47 | borderColor = 'gray5',
48 | borderWidth = 1,
49 | borderStyle = 'solid',
50 | onValueChange,
51 | onChange,
52 | ...rest
53 | }) => {
54 | const { styles } = useStyleSheet();
55 |
56 | const { count, decrement, increment, setCount } = useCount({
57 | min: min,
58 | max: max,
59 | step: step,
60 | value: value,
61 | });
62 |
63 | useEffect(() => {
64 | onValueChange?.(count);
65 | // eslint-disable-next-line react-hooks/exhaustive-deps
66 | }, [count]);
67 |
68 | const _handleChange = (event: React.ChangeEvent) => {
69 | onChange?.(event);
70 | setCount(Number(event.target.value));
71 | };
72 |
73 | const _onIncrement = () => increment(step);
74 | const _onDecrement = () => decrement(step);
75 |
76 | return (
77 |
87 |
97 |
98 |
99 |
100 |
101 |
102 | );
103 | };
104 | NumberInput.Loading = NumberInputLoading;
105 |
106 | export default NumberInput;
107 |
--------------------------------------------------------------------------------
/src/components/NumberInput/NumberInputLoading.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { View } from 'wiloke-react-core';
3 | import * as css from './styles';
4 |
5 | export interface NumberInputLoadingProps {}
6 |
7 | const NumberInputLoading: FC = () => {
8 | return ;
9 | };
10 |
11 | export default NumberInputLoading;
12 |
--------------------------------------------------------------------------------
/src/components/NumberInput/index.ts:
--------------------------------------------------------------------------------
1 | import NumberInput, { NumberInputProps as Props } from './NumberInput';
2 |
3 | export type NumberInputProps = Props;
4 | export { NumberInput };
5 |
--------------------------------------------------------------------------------
/src/components/NumberInput/styles.ts:
--------------------------------------------------------------------------------
1 | import { css, Theme } from 'wiloke-react-core';
2 |
3 | export type Size = 'small' | 'medium' | 'large';
4 |
5 | const inputSizeMapping: Record = {
6 | small: 28,
7 | medium: 37,
8 | large: 46,
9 | };
10 |
11 | export const container = (size: Size, block: boolean, disabled: boolean) => css`
12 | margin: 0;
13 | font-size: 14px;
14 | display: ${!block ? 'inline-block' : 'block'};
15 | cursor: ${disabled ? 'not-allowed' : 'pointer'};
16 | height: ${inputSizeMapping[size]}px;
17 | opacity: ${disabled ? 0.4 : 1};
18 | position: relative;
19 | overflow: hidden;
20 | `;
21 |
22 | export const input = (size: Size) => css`
23 | display: block;
24 | background-color: transparent;
25 | border: none;
26 | box-shadow: none;
27 | width: 100%;
28 | height: 100%;
29 | padding-right: ${size === 'small' ? 20 : 30}px;
30 | padding-left: 12px;
31 | padding-top: ${size === 'small' ? 12 : 14}px;
32 | padding-bottom: ${size === 'small' ? 12 : 14}px;
33 |
34 | &:focus {
35 | outline: none;
36 | }
37 | &::-webkit-outer-spin-button,
38 | &::-webkit-inner-spin-button {
39 | appearance: none;
40 | opacity: 0;
41 | cursor: pointer;
42 | }
43 | `;
44 |
45 | export const actions = css`
46 | position: absolute;
47 | top: 0;
48 | right: 0;
49 | height: 100%;
50 | `;
51 |
52 | export const actionsContainer = css`
53 | height: 100%;
54 | display: flex;
55 | flex-direction: column;
56 | justify-content: center;
57 | overflow: hidden;
58 | `;
59 |
60 | export const actionIncre = ({ colors }: Theme) => css`
61 | cursor: pointer;
62 | display: flex;
63 | align-items: center;
64 | line-height: 1;
65 | flex: 0 0 50%;
66 | border-left: 1px solid ${colors.gray5};
67 | border-bottom: 1px solid ${colors.gray5};
68 | `;
69 |
70 | export const icon = (size: Size) => css`
71 | padding: ${size === 'small' ? 0 : size === 'medium' ? 3 : size === 'large' ? 5 : 0}px;
72 | font-size: 14px;
73 | `;
74 |
75 | export const actionDecre = ({ colors }: Theme) => css`
76 | cursor: pointer;
77 | display: flex;
78 | align-items: center;
79 | line-height: 1;
80 | flex: 0 0 50%;
81 | border-left: 1px solid ${colors.gray5};
82 | `;
83 |
84 | export const loadingContainer = ({ colors }: Theme) => css`
85 | width: 60px;
86 | height: 28px;
87 | background-color: ${colors.gray5};
88 | border-radius: 5px;
89 | `;
90 |
--------------------------------------------------------------------------------
/src/components/NumberInput/useCount.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | export interface UseCountOptions {
4 | min?: number;
5 | max?: number;
6 | step?: number;
7 | value?: number;
8 | }
9 |
10 | const useCount = ({ min = 0, max = Infinity, step = 1, value = 0 }: UseCountOptions) => {
11 | const [count, setCount] = useState(value);
12 |
13 | const _setCount = (value: number) => {
14 | setCount(value);
15 | };
16 |
17 | useEffect(() => {
18 | _setCount(value);
19 | }, [value]);
20 |
21 | const _increment = (_step = step) => {
22 | return _setCount(Math.min(count + _step, max));
23 | };
24 |
25 | const _decrement = (_step = step) => {
26 | return _setCount(Math.max(count - _step, min));
27 | };
28 |
29 | return {
30 | count,
31 | setCount: _setCount,
32 | increment: _increment,
33 | decrement: _decrement,
34 | };
35 | };
36 | export default useCount;
37 |
--------------------------------------------------------------------------------
/src/components/PostCard/PostCard.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { Image, LineAwesome, Text, View } from 'wiloke-react-core';
3 | import { memoization } from 'wiloke-react-core/utils';
4 | import PostCardLoading from './PostCardLoading';
5 | import * as css from './styles';
6 |
7 | export interface PostCardProps {
8 | title: string;
9 | previewSrc: string;
10 | imageSrc: string;
11 | category: string;
12 | }
13 |
14 | interface PostCardFC extends FC {
15 | Loading: typeof PostCardLoading;
16 | }
17 |
18 | const PostCard: PostCardFC = ({ title, previewSrc, imageSrc, category }) => {
19 | return (
20 |
21 |
22 |
23 | {category}
24 |
25 |
26 |
27 | {title}
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
35 | PostCard.Loading = PostCardLoading;
36 |
37 | export default memoization(PostCard);
38 |
--------------------------------------------------------------------------------
/src/components/PostCard/PostCardLoading.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, memo } from 'react';
2 | import { View } from 'wiloke-react-core';
3 | import * as css from './styles';
4 |
5 | export interface PostCardLoadingProps {}
6 |
7 | const PostCardLoading: FC = () => {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default memo(PostCardLoading);
21 |
--------------------------------------------------------------------------------
/src/components/PostCard/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './PostCard';
2 | // eslint-disable-next-line
3 | export type { PostCardProps } from './PostCard';
4 |
--------------------------------------------------------------------------------
/src/components/PostCard/styles.ts:
--------------------------------------------------------------------------------
1 | import { css } from 'wiloke-react-core';
2 |
3 | export const container = css`
4 | position: relative;
5 | overflow: hidden;
6 | `;
7 |
8 | export const cat = css`
9 | position: absolute;
10 | top: 15px;
11 | left: 15px;
12 | padding: 3px 10px;
13 | `;
14 |
15 | export const content = css`
16 | display: flex;
17 | justify-content: space-between;
18 | align-items: center;
19 | padding: 15px;
20 | `;
21 |
22 | export const text = css`
23 | width: 80%;
24 | `;
25 |
--------------------------------------------------------------------------------
/src/components/Radio/RadioButton.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { useRadioState } from './context';
3 | import Radio, { RadioProps } from './Radio';
4 |
5 | export interface RadioButtonProps extends RadioProps {}
6 |
7 | const RadioButton: FC = props => {
8 | const stateContext = useRadioState();
9 | const { ...rest } = props;
10 |
11 | if (stateContext) {
12 | rest.checked = String(props.value) === stateContext.value;
13 | rest.disabled = props.disabled || (stateContext.disabled as boolean);
14 | rest.block = props.block || (stateContext.block as boolean);
15 | }
16 | return ;
17 | };
18 |
19 | export default RadioButton;
20 |
--------------------------------------------------------------------------------
/src/components/Radio/context.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, InputHTMLAttributes, useContext } from 'react';
2 | import { ColorNames, Size } from 'wiloke-react-core';
3 | import { Value } from './Radio';
4 |
5 | export interface RadioGroupStateValue {
6 | /** Value cua radio */
7 | value?: Value;
8 | /** disabled tat ca radio */
9 | disabled?: boolean;
10 | /** Thuoc tinh name html cua radio */
11 | name?: string;
12 | /** Kich thuoc Radio */
13 | size?: Size;
14 | /** Block Radio Button */
15 | block?: boolean;
16 | /** Color khi radio active */
17 | activeColor?: ColorNames;
18 | /** Color text khi radio button active */
19 | textActiveColor?: ColorNames;
20 | /** Màu border được lấy màu từ ThemeProvider */
21 | borderColor?: ColorNames;
22 | }
23 |
24 | export type RadioGroupAction = InputHTMLAttributes['onChange'];
25 |
26 | const RadioGroupContext = createContext(null);
27 |
28 | const RadioGroupActionContext = createContext(null);
29 |
30 | export const RadioGroupStateProvider = RadioGroupContext.Provider;
31 |
32 | export const RadioGroupActionProvider = RadioGroupActionContext.Provider;
33 |
34 | export const useRadioState = () => {
35 | const state = useContext(RadioGroupContext);
36 | return state;
37 | };
38 |
39 | export const useRadioAction = () => {
40 | const action = useContext(RadioGroupActionContext);
41 | return action;
42 | };
43 |
--------------------------------------------------------------------------------
/src/components/Radio/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Radio';
2 | // eslint-disable-next-line
3 | export type { RadioProps } from './Radio';
--------------------------------------------------------------------------------
/src/components/Radio/useMergedState.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export default function useMergedState(
4 | defaultStateValue: T | (() => T),
5 | option?: {
6 | defaultValue?: T | (() => T);
7 | value?: T;
8 | onChange?: (value: T, prevValue: T) => void;
9 | postState?: (value: T) => T;
10 | },
11 | ): [R, (value: T) => void] {
12 | const { defaultValue, value, onChange, postState } = option || {};
13 | const [innerValue, setInnerValue] = React.useState(() => {
14 | if (value !== undefined) {
15 | return value;
16 | }
17 | if (defaultValue !== undefined) {
18 | return typeof defaultValue === 'function' ? (defaultValue as any)() : defaultValue;
19 | }
20 | return typeof defaultStateValue === 'function' ? (defaultStateValue as any)() : defaultStateValue;
21 | });
22 |
23 | let mergedValue = value !== undefined ? value : innerValue;
24 | if (postState) {
25 | mergedValue = postState(mergedValue);
26 | }
27 |
28 | function triggerChange(newValue: T) {
29 | setInnerValue(newValue);
30 | if (mergedValue !== newValue && onChange) {
31 | onChange(newValue, mergedValue);
32 | }
33 | }
34 |
35 | // Effect of reset value to `undefined`
36 | const firstRenderRef = React.useRef(true);
37 | React.useEffect(() => {
38 | if (firstRenderRef.current) {
39 | firstRenderRef.current = false;
40 | return;
41 | }
42 |
43 | if (value === undefined) {
44 | setInnerValue(value as T);
45 | }
46 | }, [value]);
47 |
48 | return [(mergedValue as unknown) as R, triggerChange];
49 | }
50 |
--------------------------------------------------------------------------------
/src/components/Section/Section.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, ReactNode } from 'react';
2 | import { View, ViewProps } from 'wiloke-react-core';
3 | import * as css from './styles';
4 |
5 | type PickViewProps = Pick;
6 | export interface SectionProps extends PickViewProps {
7 | children: ReactNode;
8 | backgroundColorNative?: string;
9 | backgroundType?: 'full' | 'left' | 'right';
10 | }
11 |
12 | const Section: FC = ({ children, backgroundColorNative, backgroundColor, backgroundType = 'full' }) => {
13 | return (
14 |
15 |
16 | {children}
17 |
18 | );
19 | };
20 |
21 | export default Section;
22 |
--------------------------------------------------------------------------------
/src/components/Section/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Section';
2 | // eslint-disable-next-line
3 | export type { SectionProps } from './Section';
4 |
--------------------------------------------------------------------------------
/src/components/Section/styles.ts:
--------------------------------------------------------------------------------
1 | import { ColorNames, css, Theme } from 'wiloke-react-core';
2 |
3 | export type BackgroundType = 'full' | 'left' | 'right';
4 |
5 | export const container = css`
6 | position: relative;
7 | background-size: cover;
8 | background-position: 50% 50%;
9 | padding-top: 40px;
10 | padding-bottom: 40px;
11 | z-index: 9;
12 | @media (min-width: 768px) {
13 | padding-top: 80px;
14 | padding-bottom: 80px;
15 | }
16 | `;
17 |
18 | export const background = (backgroundColorNative?: string, backgroundColor?: ColorNames) => ({ colors }: Theme) => css`
19 | position: absolute;
20 | top: 0;
21 | right: 0;
22 | bottom: 0;
23 | left: 0;
24 | z-index: -1;
25 | border-radius: 40px;
26 | background-color: ${!!backgroundColor ? colors[backgroundColor] : backgroundColorNative};
27 | @media (min-width: 992px) {
28 | margin: 0 15px;
29 | }
30 | `;
31 |
32 | const left = css`
33 | right: 60%;
34 | `;
35 |
36 | const right = css`
37 | left: 60%;
38 | `;
39 |
40 | export const backgroundType = (backgroundType: BackgroundType) => {
41 | switch (backgroundType) {
42 | case 'left':
43 | return left;
44 | case 'right':
45 | return right;
46 | case 'full':
47 | default:
48 | return {};
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/src/components/SectionTitle/SectionTitle.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { View, Text, ViewProps, useResponsive } from 'wiloke-react-core';
3 |
4 | export interface SectionTitleProps extends ViewProps {
5 | title: string;
6 | text?: string;
7 | }
8 |
9 | const SectionTitle: FC = ({ title, text, nightModeBlacklist, ...rest }) => {
10 | const { ref, size } = useResponsive({ maxWidth: 700 });
11 |
12 | return (
13 |
14 |
15 | {title}
16 |
17 | {!!text && (
18 |
19 | {text}
20 |
21 | )}
22 |
23 | );
24 | };
25 |
26 | export default SectionTitle;
27 |
--------------------------------------------------------------------------------
/src/components/SectionTitle/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './SectionTitle';
2 | // eslint-disable-next-line
3 | export type { SectionTitleProps } from './SectionTitle';
4 |
--------------------------------------------------------------------------------
/src/components/Slider/SliderLoading.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { View } from 'wiloke-react-core';
3 | import * as css from './styles';
4 |
5 | export interface SliderLoadingProps {}
6 |
7 | const SliderLoading: FC = () => {
8 | return (
9 |
10 |
11 |
12 | );
13 | };
14 |
15 | export default SliderLoading;
16 |
--------------------------------------------------------------------------------
/src/components/Slider/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Slider';
2 | // eslint-disable-next-line
3 | export type { SliderProps } from './Slider';
4 |
--------------------------------------------------------------------------------
/src/components/Switch/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Switch';
2 | // eslint-disable-next-line
3 | export type { SwitchProps } from './Switch';
4 |
--------------------------------------------------------------------------------
/src/components/Switch/styles.ts:
--------------------------------------------------------------------------------
1 | import { css } from 'wiloke-react-core';
2 |
3 | type Size = 'extra-small' | 'small' | 'medium' | 'large';
4 |
5 | interface SizeInfo {
6 | height: number;
7 | lineHeight: number;
8 | handleSize: number;
9 | fontSize: number;
10 | }
11 |
12 | const settings = {
13 | space: 2,
14 | heightLarge: 24,
15 | heightMedium: 22,
16 | heightSmall: 20,
17 | heightExtraSmall: 18,
18 | };
19 |
20 | const handleHeightSpace = settings.space * 2;
21 |
22 | const mappingSize: Record = {
23 | large: {
24 | height: settings.heightLarge,
25 | lineHeight: settings.heightLarge,
26 | handleSize: settings.heightLarge - handleHeightSpace,
27 | fontSize: 14,
28 | },
29 | medium: {
30 | height: settings.heightMedium,
31 | lineHeight: settings.heightMedium,
32 | handleSize: settings.heightMedium - handleHeightSpace,
33 | fontSize: 13,
34 | },
35 | small: {
36 | height: settings.heightSmall,
37 | lineHeight: settings.heightSmall,
38 | handleSize: settings.heightSmall - handleHeightSpace,
39 | fontSize: 12,
40 | },
41 | 'extra-small': {
42 | height: settings.heightExtraSmall,
43 | lineHeight: settings.heightExtraSmall,
44 | handleSize: settings.heightExtraSmall - handleHeightSpace,
45 | fontSize: 10,
46 | },
47 | };
48 |
49 | export const container = (size: Size) => css`
50 | font-size: 14px;
51 | user-select: none;
52 | transition: 0.2s all ease;
53 | position: relative;
54 | border-radius: 50px;
55 | display: inline-block;
56 | vertical-align: middle;
57 | cursor: pointer;
58 | outline: unset;
59 |
60 | min-width: calc(${mappingSize[size].height}px * 1.835);
61 | height: ${mappingSize[size].height}px;
62 | line-height: ${mappingSize[size].lineHeight}px;
63 | `;
64 |
65 | export const disabled = (disabled: boolean) => {
66 | if (!disabled) {
67 | return {};
68 | }
69 | return css`
70 | opacity: 0.4;
71 | cursor: no-drop;
72 | `;
73 | };
74 |
75 | export const handle = (size: Size, check: boolean) => css`
76 | border-radius: 50%;
77 | position: absolute;
78 | display: flex;
79 | justify-content: center;
80 | align-items: center;
81 | transition: 0.2s all ease-in-out;
82 |
83 | top: ${settings.space}px;
84 | transform: ${!check ? `translateX(${settings.space}px)` : `translateX(calc(100% + ${settings.space}px))`};
85 | width: ${mappingSize[size].handleSize}px;
86 | height: ${mappingSize[size].handleSize}px;
87 | `;
88 |
89 | export const inner = (size: Size) => css`
90 | font-size: ${mappingSize[size].fontSize}px;
91 | transition: 0.2s ease;
92 | display: flex;
93 | margin: 0px;
94 | text-align: right;
95 |
96 | position: absolute;
97 | top: 50%;
98 | left: ${settings.space}px;
99 | transform: translateY(-50%);
100 | `;
101 |
--------------------------------------------------------------------------------
/src/components/SwitchBeauty/SwitchBeauty.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import FieldBox, { BoxProps } from 'components/FieldBox';
3 | import Switch, { SwitchProps } from 'components/Switch/Switch';
4 | import TextStatus, { TextStatusProps } from 'components/SwitchBeauty/TextStatus';
5 | import { omit, pick } from 'ramda';
6 | import * as css from './styles';
7 |
8 | export interface SwitchBeautyProps
9 | extends SwitchProps,
10 | Omit,
11 | Pick {}
12 |
13 | const SwitchBeauty: FC = ({ ...rest }) => {
14 | const switchPropsKeys: (keyof SwitchProps)[] = [
15 | 'checked',
16 | 'CheckedChildren',
17 | 'UnCheckedChildren',
18 | 'defaultChecked',
19 | 'loading',
20 | 'size',
21 | 'disabled',
22 | 'onValueChange',
23 | 'onChange',
24 | ];
25 | const textStatusPropsKeys: (keyof TextStatusProps)[] = ['size', 'enable', 'enableText', 'disableText', 'enableColor', 'disableColor'];
26 | const omitFieldBoxPropsKeys = [...switchPropsKeys, ...textStatusPropsKeys];
27 | const switchProps = pick(switchPropsKeys, rest);
28 | const textStatusProps = pick(textStatusPropsKeys, rest);
29 | const fieldBoxProps = omit(omitFieldBoxPropsKeys, rest);
30 | const { size = 'large', disabled = false } = rest;
31 |
32 | return (
33 |
34 | } />
35 |
36 | );
37 | };
38 |
39 | export default SwitchBeauty;
40 |
--------------------------------------------------------------------------------
/src/components/SwitchBeauty/TextStatus/TextStatus.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { ColorNames, Size, Text, View } from 'wiloke-react-core';
3 | import * as css from './styles';
4 |
5 | export interface TextStatusProps {
6 | /** Size */
7 | size?: Size;
8 | /** Status cua component */
9 | enable?: boolean;
10 | /** Text sau khi enable */
11 | enableText?: string;
12 | /** Text truoc khi enable */
13 | disableText?: string;
14 | /** color khi enable */
15 | enableColor?: ColorNames;
16 | /** color status khi disable */
17 | disableColor?: ColorNames;
18 | }
19 |
20 | const TextStatus: FC = ({
21 | enable = false,
22 | enableText = 'Enable',
23 | disableText = 'Disabled',
24 | size = 'medium',
25 | enableColor = 'success',
26 | disableColor = 'gray7',
27 | }) => {
28 | return (
29 |
30 | {enable ? enableText : disableText}
31 |
32 | );
33 | };
34 |
35 | export default TextStatus;
36 |
--------------------------------------------------------------------------------
/src/components/SwitchBeauty/TextStatus/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './TextStatus';
2 | // eslint-disable-next-line
3 | export type { TextStatusProps } from './TextStatus';
--------------------------------------------------------------------------------
/src/components/SwitchBeauty/TextStatus/styles.ts:
--------------------------------------------------------------------------------
1 | import { css, Size } from 'wiloke-react-core';
2 |
3 | const textStatusSizeMapping: Record = {
4 | large: 14,
5 | medium: 14,
6 | small: 14,
7 | 'extra-small': 12,
8 | };
9 |
10 | export const container = (size: Size) => css`
11 | font-size: ${textStatusSizeMapping[size]}px;
12 | line-height: 1.2;
13 | `;
14 |
--------------------------------------------------------------------------------
/src/components/SwitchBeauty/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './SwitchBeauty';
2 | // eslint-disable-next-line
3 | export type { SwitchBeautyProps } from './SwitchBeauty';
--------------------------------------------------------------------------------
/src/components/SwitchBeauty/styles.ts:
--------------------------------------------------------------------------------
1 | import { css, Size, Theme } from 'wiloke-react-core';
2 |
3 | interface SwitchBeautySizeInfo {
4 | height: number;
5 | 'line-height': number;
6 | padding: string;
7 | }
8 |
9 | const switchBeautySizeMapping: Record = {
10 | 'extra-small': { height: 22, 'line-height': 20, padding: '0 7px' },
11 | small: { height: 30, 'line-height': 28, padding: '0 7px' },
12 | medium: { height: 38, 'line-height': 36, padding: '0 10px' },
13 | large: { height: 46, 'line-height': 44, padding: '0 12px' },
14 | };
15 |
16 | export const container = (size: Size) => {
17 | return css`
18 | display: flex;
19 | align-items: center;
20 | justify-content: space-between;
21 | position: relative;
22 | padding: ${switchBeautySizeMapping[size].padding};
23 | line-height: ${switchBeautySizeMapping[size]['line-height']}px;
24 | height: ${switchBeautySizeMapping[size].height}px;
25 | `;
26 | };
27 |
28 | export const disabled = (disabled: boolean) => ({ colors }: Theme) => {
29 | if (!disabled) {
30 | return {};
31 | }
32 | return css`
33 | background-color: ${colors.gray5} !important;
34 | opacity: 0.4;
35 | cursor: not-allowed;
36 | `;
37 | };
38 |
--------------------------------------------------------------------------------
/src/components/Tabs/TabPane/TabPaneBase.tsx:
--------------------------------------------------------------------------------
1 | import { TabPane, TabPaneProps } from 'rc-tabs';
2 | import React, { CSSProperties, FC, ReactNode } from 'react';
3 | import { ColorNames, View } from 'wiloke-react-core';
4 | import { classNames } from 'wiloke-react-core/utils';
5 | import * as css from '../styles';
6 |
7 | export interface TabPaneBaseProps extends TabPaneProps {
8 | className?: string;
9 | /** Tiêu đề của tab pane hiện tại. Có thể là string hoặc react element */
10 | tab?: ReactNode;
11 | /** Giá trị tương ứng vói active key của tabbar */
12 | key?: string | number;
13 | /** Disabled = true sẽ không active tab và không cho bấm vào tab đấy nữa */
14 | disabled?: boolean;
15 | /** Background color của Tab pane */
16 | backgroundColor?: ColorNames;
17 | /** Color text của Tab pane */
18 | color?: ColorNames;
19 | /** Forced render of content in tabs, not lazy render after clicking on tabs */
20 | forceRender?: boolean;
21 | /** style inline */
22 | style?: CSSProperties;
23 | }
24 |
25 | const TabPaneBase: FC = ({
26 | disabled,
27 | key,
28 | children,
29 | tab,
30 | tabKey,
31 | style,
32 | forceRender = false,
33 | backgroundColor = 'light',
34 | color = 'dark',
35 | className,
36 | ...rest
37 | }) => {
38 | const combineProps = { style, className: classNames(className, 'TabPane') };
39 | return (
40 |
41 |
42 | {children}
43 |
44 |
45 | );
46 | };
47 |
48 | export default TabPaneBase;
49 |
--------------------------------------------------------------------------------
/src/components/Tabs/TabPane/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './TabPaneBase';
2 | // eslint-disable-next-line
3 | export type { TabPaneBaseProps } from './TabPaneBase';
4 |
--------------------------------------------------------------------------------
/src/components/Tabs/Tabs.tsx:
--------------------------------------------------------------------------------
1 | import TabCore, { TabsProps } from 'rc-tabs';
2 | import 'rc-tabs/assets/index.css';
3 | import { RenderTabBar } from 'rc-tabs/lib/interface';
4 | import React, { FC, KeyboardEvent, MouseEvent, ReactNode } from 'react';
5 | import { useTheme, View } from 'wiloke-react-core';
6 | import { classNames } from 'wiloke-react-core/utils';
7 | import * as css from './styles';
8 | import TabPaneBase from './TabPane';
9 |
10 | export type TabPosition = 'left' | 'right' | 'top' | 'bottom';
11 | export type ScrollDirection = 'top' | 'bottom' | 'left' | 'right';
12 |
13 | type PickTabsProp = Pick<
14 | TabsProps,
15 | | 'activeKey'
16 | | 'className'
17 | | 'children'
18 | | 'onChange'
19 | | 'onTabClick'
20 | | 'onTabScroll'
21 | | 'renderTabBar'
22 | | 'moreIcon'
23 | | 'tabPosition'
24 | | 'defaultActiveKey'
25 | >;
26 |
27 | export interface TabProps extends PickTabsProp {
28 | /** key của tabPanel đang active hiện tại */
29 | activeKey?: string;
30 | /** Default active tab */
31 | defaultActiveKey?: string;
32 | /** Vị trí của tab */
33 | tabPosition?: TabPosition;
34 | /** Hiệu ứng khi chuyển tiêu đề tab */
35 | navBarAnimated?: boolean;
36 | /** Hiệu ứng khi chuyển nội dung tab */
37 | tabPaneAnimated?: boolean;
38 | /** Khoảng cách giữa mỗi tab title */
39 | tabTitleGutter?: number;
40 | /** More Icon */
41 | moreIcon?: ReactNode;
42 | /** className */
43 | className?: string;
44 | /** render navbar */
45 | renderTabBar?: RenderTabBar;
46 | /** Sự kiện onChange */
47 | onChange?: (activeKey: string) => void;
48 | /** Sự kiện scroll tab */
49 | onTabScroll?: (info: { direction: ScrollDirection }) => void;
50 | /** Sự kiện được gọi khi click vào tab */
51 | onTabClick?: (activeKey: string, e: KeyboardEvent | MouseEvent) => void;
52 | }
53 |
54 | interface TabsStatic {
55 | Pane: typeof TabPaneBase;
56 | }
57 |
58 | const Tabs: FC & TabsStatic = ({
59 | children,
60 | className,
61 | defaultActiveKey = '1',
62 | navBarAnimated = false,
63 | tabPaneAnimated = false,
64 | tabPosition = 'top',
65 | tabTitleGutter = 15,
66 | moreIcon = '...',
67 | activeKey,
68 | onTabScroll,
69 | onChange,
70 | onTabClick,
71 | renderTabBar,
72 | }) => {
73 | const { direction } = useTheme();
74 |
75 | return (
76 |
77 |
91 | {children}
92 |
93 |
94 | );
95 | };
96 |
97 | Tabs.Pane = TabPaneBase;
98 |
99 | export default Tabs;
100 |
--------------------------------------------------------------------------------
/src/components/Tabs/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './Tabs';
2 | // eslint-disable-next-line
3 | export type { TabProps, ScrollDirection, TabPosition } from './Tabs';
4 |
--------------------------------------------------------------------------------
/src/components/Tabs/styles.ts:
--------------------------------------------------------------------------------
1 | import { css, nightModeBlacklist, Theme } from 'wiloke-react-core';
2 |
3 | export const container = ({ colors }: Theme) => css`
4 | debug: Tab-container;
5 | position: relative;
6 |
7 | .rc-tabs {
8 | border: none !important;
9 | }
10 |
11 | .rc-tabs-nav-more {
12 | cursor: pointer !important;
13 | outline: none !important;
14 | }
15 |
16 | .rc-tabs-tab {
17 | padding: 4px 0;
18 | text-transform: capitalize;
19 | background-color: transparent !important;
20 | background: transparent;
21 | color: ${colors.dark};
22 | }
23 |
24 | .rc-tabs-nav {
25 | margin-bottom: 8px;
26 | align-items: center;
27 | }
28 |
29 | .rc-tabs-nav-wrap {
30 | &::before,
31 | &::after {
32 | content: '';
33 | display: none;
34 | }
35 | }
36 |
37 | .rc-tabs-nav-more {
38 | display: flex;
39 | justify-content: center;
40 | align-items: center;
41 |
42 | background-color: ${colors.light};
43 | color: ${colors.gray8};
44 | border: 1px solid ${colors.gray4};
45 | cursor: pointer;
46 |
47 | font-size: 14px;
48 | padding: 10px 16px;
49 | margin: 0 !important;
50 |
51 | outline: none !important;
52 | }
53 |
54 | .rc-tabs-tab .rc-tabs-tab-btn {
55 | font-weight: 400;
56 | font-size: 14px;
57 | border: none;
58 |
59 | &:active,
60 | &:hover,
61 | &:focus {
62 | border: none;
63 | outline: none;
64 | }
65 | }
66 |
67 | .rc-tabs-tab.rc-tabs-tab-active {
68 | color: ${colors.behance} !important;
69 | }
70 |
71 | .rc-tabs-ink-bar {
72 | background: ${colors.behance} !important;
73 | }
74 |
75 | .rc-tabs-tab-disabled {
76 | cursor: not-allowed;
77 | opacity: 0.4;
78 | }
79 |
80 | .rc-tabs-tabpane {
81 | font-size: 14px;
82 | color: ${colors.gray9};
83 |
84 | &:active,
85 | &:hover,
86 | &:focus {
87 | border: none;
88 | outline: none;
89 | }
90 | }
91 |
92 | :global {
93 | .rc-tabs-dropdown {
94 | transition: 0.3s ease-in-out;
95 | border: unset !important;
96 | outline: unset !important;
97 | box-shadow: 0 3px 6px -4px rgba(${colors.rgbDark}, 0.12), 0 6px 16px 0 rgba(${colors.rgbDark}, 0.08),
98 | 0 9px 28px 8px rgba(${colors.rgbDark}, 0.05);
99 |
100 | &:hover,
101 | &:focus,
102 | &:active {
103 | outline: unset !important;
104 | }
105 | }
106 | .rc-tabs-dropdown-menu {
107 | border: unset !important;
108 | &:hover,
109 | &:focus,
110 | &:active {
111 | outline: unset !important;
112 | }
113 | }
114 | .rc-tabs-dropdown-menu-item {
115 | padding: 4px 12px;
116 | transition: 0.2s cubic-bezier(0.075, 0.82, 0.165, 1);
117 | font-size: 14px;
118 | color: ${nightModeBlacklist(colors.gray8)};
119 | cursor: pointer !important;
120 | }
121 | }
122 | `;
123 |
124 | export const tabPaneChildren = css`
125 | padding: 8px;
126 | `;
127 |
--------------------------------------------------------------------------------
/src/components/TextInput/TextInput.tsx:
--------------------------------------------------------------------------------
1 | import React, { ChangeEvent, FC, InputHTMLAttributes, useCallback } from 'react';
2 | import { Size, useStyleSheet, View, ViewProps } from 'wiloke-react-core';
3 | import { classNames } from 'wiloke-react-core/utils';
4 | import * as css from './styles';
5 | import TextInputLoading from './TextInputLoading';
6 |
7 | type InputType = 'text' | 'password' | 'email';
8 | export interface InputProps extends ViewProps {
9 | /** Size của input */
10 | sizeInput?: Exclude;
11 | /** Placeholder của input */
12 | placeholder?: string;
13 | /** Bật lên input sẽ rộng 100% */
14 | block?: boolean;
15 | /** Kiểu đầu vào của input */
16 | type?: InputType;
17 | /** Giá trị đầu vào của input */
18 | value?: string;
19 | /** Khi bật disabled thì nút mờ đi và không thể thực hiện event */
20 | disabled?: boolean;
21 | /** Sự kiện onChange của input */
22 | onChange?: InputHTMLAttributes['onChange'];
23 | /** Sự kiện onValueChange của input, trả về dữ liệu dạng string(chuỗi) */
24 | onValueChange?: (text: string) => void;
25 | }
26 |
27 | const TextInput: FC & {
28 | Loading: typeof TextInputLoading;
29 | } = ({
30 | className,
31 | sizeInput = 'large',
32 | placeholder = '',
33 | block = false,
34 | type = 'text',
35 | value,
36 | disabled = false,
37 | borderColor = 'gray5',
38 | borderStyle = 'solid',
39 | borderWidth = 1,
40 | color = 'gray8',
41 | backgroundColor,
42 | radius = 5,
43 | onChange,
44 | onValueChange,
45 | ...rest
46 | }) => {
47 | const { styles } = useStyleSheet();
48 |
49 | const _handleChange = useCallback((event: ChangeEvent) => {
50 | if (!disabled) {
51 | onValueChange?.(event.target.value);
52 | onChange?.(event);
53 | }
54 | // eslint-disable-next-line react-hooks/exhaustive-deps
55 | }, []);
56 |
57 | return (
58 |
69 |
77 |
78 | );
79 | };
80 |
81 | TextInput.Loading = TextInputLoading;
82 |
83 | export default TextInput;
84 |
--------------------------------------------------------------------------------
/src/components/TextInput/TextInputLoading.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { View } from 'wiloke-react-core';
3 | import * as css from './styles';
4 |
5 | export interface TextInputLoadingProps {}
6 |
7 | const TextInputLoading: FC = () => {
8 | return (
9 |
10 |
11 |
12 | );
13 | };
14 |
15 | export default TextInputLoading;
16 |
--------------------------------------------------------------------------------
/src/components/TextInput/index.ts:
--------------------------------------------------------------------------------
1 | import TextInput, { InputProps as Props } from './TextInput';
2 |
3 | export type InputProps = Props;
4 |
5 | export { TextInput };
6 |
--------------------------------------------------------------------------------
/src/components/TextInput/styles.ts:
--------------------------------------------------------------------------------
1 | import { css, Theme } from 'wiloke-react-core';
2 |
3 | export type Size = 'small' | 'medium' | 'large';
4 |
5 | const inputSizeMapping: Record = {
6 | small: 28,
7 | medium: 37,
8 | large: 46,
9 | };
10 |
11 | export const container = (size: Size, block: boolean, disabled: boolean) => css`
12 | margin: 0;
13 | font-size: 14px;
14 | position: relative;
15 | overflow: hidden;
16 | display: ${!block ? 'inline-block' : 'block'};
17 | cursor: ${disabled ? 'not-allowed' : 'pointer'};
18 | opacity: ${disabled ? 0.4 : 1};
19 | height: ${inputSizeMapping[size]}px;
20 | `;
21 |
22 | export const input = (size: Size) => css`
23 | display: block;
24 | background-color: transparent;
25 | border: none;
26 | box-shadow: none;
27 | width: 100%;
28 | height: 100%;
29 | padding: 0px ${size === 'small' ? 10 : 12}px;
30 |
31 | &:focus {
32 | outline: none;
33 | }
34 | `;
35 |
36 | export const loadingContainer = ({ colors }: Theme) => css`
37 | width: 200px;
38 | height: 46px;
39 | border-radius: 6px;
40 | background-color: ${colors.gray5};
41 | position: relative;
42 | `;
43 |
44 | export const loadingInner = ({ colors }: Theme) => css`
45 | position: absolute;
46 | width: 40%;
47 | background-color: ${colors.gray4};
48 | height: 4px;
49 | border-radius: 4px;
50 | top: 50%;
51 | left: 10px;
52 | transform: translateY(-50%);
53 | `;
54 |
--------------------------------------------------------------------------------
/src/components/TextUnderline/TextUnderline.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { ColorNames, Text, WithStylesProps } from 'wiloke-react-core';
3 | import * as css from './styles';
4 |
5 | export interface TextUnderlineProps extends Pick {
6 | /** Set css font-size */
7 | textSize?: number | 'inherit';
8 | /** Color của component theo ThemeProvider */
9 | textColor?: ColorNames;
10 | /** React children */
11 | children: string | number;
12 | /** Màu của line */
13 | lineColor?: string;
14 | /** Kích thước line */
15 | lineSize?: number;
16 | /** Khoảng cách line tính từ bottom của chữ */
17 | lineBottomSpace?: number;
18 | }
19 |
20 | const TextUnderline: FC = ({
21 | children,
22 | textColor = 'gray8',
23 | textSize,
24 | lineSize = 5,
25 | lineColor = '#94fbd1',
26 | lineBottomSpace = 10,
27 | nightModeBlacklist,
28 | }) => {
29 | return (
30 |
37 |
38 | {children}
39 |
40 |
41 | );
42 | };
43 |
44 | export default TextUnderline;
45 |
--------------------------------------------------------------------------------
/src/components/TextUnderline/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './TextUnderline';
2 | // eslint-disable-next-line
3 | export type { TextUnderlineProps } from './TextUnderline';
4 |
--------------------------------------------------------------------------------
/src/components/TextUnderline/styles.ts:
--------------------------------------------------------------------------------
1 | import { css } from 'wiloke-react-core';
2 |
3 | export const container = (lineSize: number, color: string, lineBottomSpace: number) => css`
4 | position: relative;
5 | top: -${lineBottomSpace}px;
6 | display: inline;
7 | border-bottom: ${lineSize}px solid ${color};
8 | > * {
9 | display: inherit;
10 | position: relative;
11 | top: ${lineBottomSpace}px;
12 | }
13 | `;
14 |
--------------------------------------------------------------------------------
/src/configureApp.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrl": "https://5abe1d53d4c5900014949e9c.mockapi.io/api/",
3 | "timeout": 30000
4 | }
5 |
--------------------------------------------------------------------------------
/src/containers/AboutPage/AboutPage.tsx:
--------------------------------------------------------------------------------
1 | import Header from 'containers/Header/Header';
2 | import { View } from 'wiloke-react-core';
3 |
4 | const AboutPage = () => {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 | );
12 | };
13 |
14 | export default AboutPage;
15 |
--------------------------------------------------------------------------------
/src/containers/AboutPage/index.ts:
--------------------------------------------------------------------------------
1 | import AboutPage from './AboutPage';
2 |
3 | export { AboutPage };
4 |
--------------------------------------------------------------------------------
/src/containers/AppContent/AppContent.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { useSelector } from 'react-redux';
3 | import Routes from 'routes';
4 | import styleBase from 'styles/base';
5 | import { ThemeOverrides, ThemeProvider, useStyleSheet, View } from 'wiloke-react-core';
6 | import * as css from './styles';
7 |
8 | export const themeOverrides: ThemeOverrides = {
9 | fonts: {
10 | primary: 'Roboto, sans-serif',
11 | secondary: 'Poppins, sans-serif',
12 | tertiary: 'Merriweather, serif',
13 | quaternary: 'Vibes, serif',
14 | },
15 | colors: {
16 | primary: '#f06292',
17 | secondary: '#3ece7e',
18 | tertiary: '#f4b34d',
19 | quaternary: '#fc6363',
20 | light: '#ffffff',
21 | gray1: '#fbfbfc',
22 | gray2: '#f3f3f6',
23 | gray3: '#f1f1f3',
24 | gray4: '#e7e7ed',
25 | gray5: '#9ea6ba',
26 | gray6: '#787f95',
27 | gray7: '#70778b',
28 | gray8: '#485273',
29 | gray9: '#252c41',
30 | dark: '#12151f',
31 | },
32 | nightModeColors: {
33 | dark: '#ffffff',
34 | gray9: '#fbfbfc',
35 | gray8: '#f3f3f6',
36 | gray7: '#f1f1f3',
37 | gray6: '#e7e7ed',
38 | gray5: '#9ea6ba',
39 | gray4: '#787f95',
40 | gray3: '#70778b',
41 | gray2: '#485273',
42 | gray1: '#252c41',
43 | light: '#202638',
44 | },
45 | cssInJs: {
46 | pixelToRem: false,
47 | devMode: true,
48 | },
49 | grid: {
50 | container: {
51 | width: 1300,
52 | gap: 15,
53 | },
54 | columns: {
55 | max: 12,
56 | gap: 30,
57 | },
58 | breakpoints: {
59 | xs: 'default',
60 | sm: 768,
61 | md: 992,
62 | lg: 1300,
63 | },
64 | },
65 | };
66 |
67 | export const CSSGlobal: FC = ({ children }) => {
68 | const { renderer } = useStyleSheet();
69 | renderer.renderStatic(styleBase);
70 |
71 | return {children};
72 | };
73 |
74 | const AppContent: FC = () => {
75 | const direction = useSelector((state: AppState) => state.direction);
76 | return (
77 |
78 |
79 |
80 |
81 |
82 | );
83 | };
84 |
85 | export default AppContent;
86 |
--------------------------------------------------------------------------------
/src/containers/AppContent/index.ts:
--------------------------------------------------------------------------------
1 | import AppContent from './AppContent';
2 |
3 | export { AppContent };
4 |
--------------------------------------------------------------------------------
/src/containers/AppContent/styles.ts:
--------------------------------------------------------------------------------
1 | import { css, Theme } from 'wiloke-react-core';
2 |
3 | export const cssGlobalWithTheme = ({ fonts, colors }: Theme) => css`
4 | :global {
5 | body {
6 | font-family: ${fonts.primary};
7 | background-color: ${colors.light};
8 | color: ${colors.gray7};
9 | }
10 |
11 | h1,
12 | h2,
13 | h3,
14 | h4,
15 | h5,
16 | h6 {
17 | font-family: ${fonts.secondary};
18 | color: ${colors.gray9};
19 | }
20 | }
21 | `;
22 |
--------------------------------------------------------------------------------
/src/containers/Header/Header.tsx:
--------------------------------------------------------------------------------
1 | import Switch, { SwitchProps } from 'components/Switch/Switch';
2 | import { FC, memo } from 'react';
3 | import { useSelector } from 'react-redux';
4 | import { Link } from 'react-router-dom';
5 | import { Divider, Sticky, Text, useNightMode, View } from 'wiloke-react-core';
6 | import { useSetDirection } from './slice/sliceDirection';
7 | import * as css from './styles';
8 |
9 | export interface HeaderProps {}
10 |
11 | const Header: FC = () => {
12 | const direction = useSelector((state: AppState) => state.direction);
13 | const setDirection = useSetDirection();
14 | const { nightMode, setNightMode } = useNightMode();
15 |
16 | const _handleNightMode: SwitchProps['onValueChange'] = value => {
17 | setNightMode(value);
18 | };
19 |
20 | const _handleDirection: SwitchProps['onValueChange'] = value => {
21 | const direction = value ? 'rtl' : 'ltr';
22 | setDirection({ direction });
23 | };
24 |
25 | return (
26 |
27 |
28 |
29 |
30 |
31 |
32 | Home
33 |
34 |
35 |
36 |
37 |
43 |
44 | About
45 |
46 |
47 |
48 |
49 |
50 | NightMode
51 |
52 |
53 |
54 |
55 |
56 | RTL
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | );
65 | };
66 |
67 | export default memo(Header);
68 |
--------------------------------------------------------------------------------
/src/containers/Header/actions/actionNightMode.ts:
--------------------------------------------------------------------------------
1 | import { createAction, createDispatchAction } from 'wiloke-react-core/utils';
2 |
3 | export const actionSetNightMode = createAction('@Header/setNightMode', (nightMode: boolean) => ({
4 | nightMode,
5 | }));
6 |
7 | export const useSetNightMode = createDispatchAction(actionSetNightMode);
8 |
--------------------------------------------------------------------------------
/src/containers/Header/index.ts:
--------------------------------------------------------------------------------
1 | import Header from './Header';
2 | import reducerNightMode from './reducers/reducerNightMode';
3 | import { sliceDirection } from './slice/sliceDirection';
4 |
5 | const reducersHeader = {
6 | nightMode: reducerNightMode,
7 | direction: sliceDirection.reducer,
8 | };
9 |
10 | export { Header, reducersHeader };
11 |
--------------------------------------------------------------------------------
/src/containers/Header/reducers/reducerNightMode.ts:
--------------------------------------------------------------------------------
1 | import { ActionTypes, createReducer, handleAction } from 'wiloke-react-core/utils';
2 | import { actionSetNightMode } from '../actions/actionNightMode';
3 |
4 | type ActionNightMode = ActionTypes;
5 |
6 | type StateNightMode = boolean;
7 |
8 | const initialState: StateNightMode = false;
9 |
10 | const reducerNightMode = createReducer(initialState, [
11 | handleAction('@Header/setNightMode', ({ action }) => action.payload.nightMode),
12 | ]);
13 |
14 | export default reducerNightMode;
15 |
--------------------------------------------------------------------------------
/src/containers/Header/slice/sliceDirection.ts:
--------------------------------------------------------------------------------
1 | import { createDispatchAction, createSlice, handleAction } from 'wiloke-react-core/utils';
2 |
3 | // Thử dùng createSlice thay cho action + reducer
4 |
5 | export type DirectionState = 'ltr' | 'rtl';
6 |
7 | export interface DirectionAction {
8 | type: 'setDirection';
9 | payload: {
10 | direction: DirectionState;
11 | };
12 | }
13 |
14 | export const sliceDirection = createSlice({
15 | name: '@Header',
16 | initialState: 'ltr',
17 | reducers: [handleAction('setDirection', ({ action }) => action.payload.direction)],
18 | });
19 |
20 | export const useSetDirection = createDispatchAction(sliceDirection.actions.setDirection);
21 |
--------------------------------------------------------------------------------
/src/containers/Header/styles.ts:
--------------------------------------------------------------------------------
1 | import { css } from 'wiloke-react-core';
2 |
3 | export const inner = css`
4 | display: flex;
5 | flex-direction: row;
6 | justify-content: center;
7 | align-items: center;
8 | `;
9 |
10 | export const link = css`
11 | margin-left: 5px;
12 | margin-right: 5px;
13 | @media (min-width: 768px) {
14 | margin-left: 15px;
15 | margin-right: 15px;
16 | }
17 | a {
18 | text-decoration: none;
19 | }
20 | `;
21 |
--------------------------------------------------------------------------------
/src/containers/HomePage/actions/actionExample.ts:
--------------------------------------------------------------------------------
1 | import { createAction, createDispatchAction } from 'wiloke-react-core/utils';
2 |
3 | export const actionExample = createAction('@HomePage/actionExample');
4 |
5 | export const useActionExample = createDispatchAction(actionExample);
6 |
--------------------------------------------------------------------------------
/src/containers/HomePage/actions/actionTodolist.ts:
--------------------------------------------------------------------------------
1 | import { Todolist } from 'models/Todolist';
2 | import { createAsyncAction, createDispatchAsyncAction } from 'wiloke-react-core/utils';
3 | import { Endpoints } from 'types/Endpoints';
4 |
5 | export const getTodolist = createAsyncAction([
6 | '@HomePage/getTodolistRequest',
7 | '@HomePage/getTodolistSuccess',
8 | '@HomePage/getTodolistFailure',
9 | '@HomePage/getTodolistCancel',
10 | ])<{ endpoint: Endpoints.Todolist }, { data: Todolist }, { message: string }>();
11 |
12 | export const useGetTodolist = createDispatchAsyncAction(getTodolist);
13 |
--------------------------------------------------------------------------------
/src/containers/HomePage/index.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import HomePage from './HomePage';
3 | import { reducerExample } from './reducers/reducerExample';
4 | import reducerTodolist from './reducers/reducerTodolist';
5 | import { watchTodolistRequest, watchTodolistCancel } from './sagas/watchTodolist';
6 |
7 | const sagaHomePage = [watchTodolistRequest, watchTodolistCancel];
8 |
9 | const reducersHomePage = combineReducers({
10 | todolist: reducerTodolist,
11 | example: reducerExample,
12 | });
13 |
14 | export { HomePage, reducersHomePage, sagaHomePage };
15 |
--------------------------------------------------------------------------------
/src/containers/HomePage/reducers/reducerExample.ts:
--------------------------------------------------------------------------------
1 | import { ActionTypes, createReducer, handleAction } from 'wiloke-react-core/utils';
2 | import { actionExample } from '../actions/actionExample';
3 |
4 | type ExampleState = boolean;
5 | type ExampleAction = ActionTypes;
6 |
7 | export const reducerExample = createReducer(false, [
8 | handleAction('@HomePage/actionExample', ({ state }) => {
9 | return !state;
10 | }),
11 | ]);
12 |
--------------------------------------------------------------------------------
/src/containers/HomePage/reducers/reducerTodolist.ts:
--------------------------------------------------------------------------------
1 | import { ActionTypes, createReducer, handleAction } from 'wiloke-react-core/utils';
2 | import { Todolist } from 'models/Todolist';
3 | import { getTodolist } from '../actions/actionTodolist';
4 |
5 | type TodolistAction = ActionTypes;
6 |
7 | interface TodolistState {
8 | status: Status;
9 | errorMessage: string;
10 | data: Todolist;
11 | }
12 |
13 | const initialState: TodolistState = {
14 | status: 'idle',
15 | errorMessage: '',
16 | data: [],
17 | };
18 |
19 | const reducerTodolist = createReducer(initialState, [
20 | handleAction('@HomePage/getTodolistRequest', ({ state }) => ({
21 | ...state,
22 | status: 'loading',
23 | })),
24 | handleAction('@HomePage/getTodolistSuccess', ({ state, action }) => ({
25 | ...state,
26 | status: 'success',
27 | data: action.payload.data,
28 | })),
29 | handleAction('@HomePage/getTodolistFailure', ({ state, action }) => ({
30 | ...state,
31 | status: 'failure',
32 | errorMessage: action.payload.message,
33 | })),
34 | handleAction('@HomePage/getTodolistCancel', ({ state }) => {
35 | // mutable test
36 | state.status = 'idle';
37 | return state;
38 | }),
39 | ]);
40 |
41 | export default reducerTodolist;
42 |
--------------------------------------------------------------------------------
/src/containers/HomePage/sagas/watchTodolist.ts:
--------------------------------------------------------------------------------
1 | import { put, call, take, fork, cancel } from 'redux-saga/effects';
2 | import { AxiosResponse } from 'axios';
3 | import { Todolist } from 'models/Todolist';
4 | import fetchAPI from 'utils/functions/fetchAPI';
5 | import { getTodolist } from 'containers/HomePage/actions/actionTodolist';
6 | import { Task } from 'redux-saga';
7 | import { getActionType } from 'wiloke-react-core/utils';
8 |
9 | type GetTodolistRequest = ReturnType;
10 | type GetTodolistCancel = ReturnType;
11 |
12 | function* handleTodolist({ payload }: GetTodolistRequest) {
13 | try {
14 | const res: AxiosResponse = yield call(fetchAPI.request, {
15 | url: payload.endpoint,
16 | });
17 | yield put(getTodolist.success({ data: res.data }));
18 | } catch (err) {
19 | yield put(getTodolist.failure({ message: 'Error' }));
20 | }
21 | }
22 |
23 | let todoListTask: Task | undefined;
24 |
25 | export function* watchTodolistRequest() {
26 | while (true) {
27 | const actionTodolistRequest: GetTodolistRequest = yield take(getActionType(getTodolist.request));
28 | if (!!todoListTask) {
29 | yield cancel(todoListTask);
30 | }
31 | todoListTask = yield fork(handleTodolist, actionTodolistRequest);
32 | }
33 | }
34 |
35 | export function* watchTodolistCancel() {
36 | while (true) {
37 | const actionTodolistCancel: GetTodolistCancel = yield take(getActionType(getTodolist.cancel));
38 | if (actionTodolistCancel.type === '@HomePage/getTodolistCancel' && !!todoListTask) {
39 | yield cancel(todoListTask);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/containers/NotFoundPage/NotFoundPage.tsx:
--------------------------------------------------------------------------------
1 | import Section from 'components/Section';
2 | import { Header } from 'containers/Header';
3 | import React from 'react';
4 | import { Text, View } from 'wiloke-react-core';
5 |
6 | const NotFoundPage = () => {
7 | return (
8 |
9 |
10 |
11 |
14 |
15 |
16 | );
17 | };
18 |
19 | export default NotFoundPage;
20 |
--------------------------------------------------------------------------------
/src/containers/NotFoundPage/index.ts:
--------------------------------------------------------------------------------
1 | import NotFoundPage from './NotFoundPage';
2 |
3 | export { NotFoundPage };
4 |
--------------------------------------------------------------------------------
/src/hooks/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/long-hp/react-typescript-boilerplate/545789ef7ce543a9fa09a891bc6856100c0f3c0a/src/hooks/.gitkeep
--------------------------------------------------------------------------------
/src/hooks/useMergedState.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export default function useMergedState(
4 | defaultStateValue: T | (() => T),
5 | option?: {
6 | defaultValue?: T | (() => T);
7 | value?: T;
8 | onChange?: (value: T, prevValue: T) => void;
9 | postState?: (value: T) => T;
10 | },
11 | ): [R, (value: T) => void] {
12 | const { defaultValue, value, onChange, postState } = option || {};
13 | const [innerValue, setInnerValue] = React.useState(() => {
14 | if (value !== undefined) {
15 | return value;
16 | }
17 | if (defaultValue !== undefined) {
18 | return typeof defaultValue === 'function' ? (defaultValue as any)() : defaultValue;
19 | }
20 | return typeof defaultStateValue === 'function' ? (defaultStateValue as any)() : defaultStateValue;
21 | });
22 |
23 | let mergedValue = value !== undefined ? value : innerValue;
24 | if (postState) {
25 | mergedValue = postState(mergedValue);
26 | }
27 |
28 | function triggerChange(newValue: T) {
29 | setInnerValue(newValue);
30 | if (mergedValue !== newValue && onChange) {
31 | onChange(newValue, mergedValue);
32 | }
33 | }
34 |
35 | // Effect of reset value to `undefined`
36 | const firstRenderRef = React.useRef(true);
37 | React.useEffect(() => {
38 | if (firstRenderRef.current) {
39 | firstRenderRef.current = false;
40 | return;
41 | }
42 |
43 | if (value === undefined) {
44 | setInnerValue(value as T);
45 | }
46 | }, [value]);
47 |
48 | return [(mergedValue as unknown) as R, triggerChange];
49 | }
50 |
--------------------------------------------------------------------------------
/src/hooks/useTranslation.ts:
--------------------------------------------------------------------------------
1 | import { defaultTranslation } from 'utils/constants/defaultTranslation';
2 | import createTranslation from 'utils/functions/createTranslation';
3 |
4 | const fetchFake = () => {
5 | return new Promise(resolve => {
6 | const timeoutId = setTimeout(() => {
7 | resolve(defaultTranslation);
8 | clearTimeout(timeoutId);
9 | }, 1000);
10 | });
11 | };
12 |
13 | const useTranslation = createTranslation({
14 | initialValue: defaultTranslation,
15 | locale: 'en',
16 | cache: true,
17 | request: async () => {
18 | const data = (await fetchFake()) as typeof defaultTranslation;
19 | return data;
20 | },
21 | });
22 |
23 | export default useTranslation;
24 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { hydrate, render } from 'react-dom';
2 | import App from 'App';
3 | import { useDispatch } from 'react-redux';
4 | import { getUseDispatchRedux } from 'wiloke-react-core/utils';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | getUseDispatchRedux(useDispatch);
8 |
9 | const rootElement = document.getElementById('root') as HTMLElement;
10 |
11 | if (rootElement.hasChildNodes()) {
12 | hydrate(, rootElement);
13 | } else {
14 | render(, rootElement);
15 | }
16 |
17 | // If you want to start measuring performance in your app, pass a function
18 | // to log results (for example: reportWebVitals(console.log))
19 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
20 | reportWebVitals();
21 |
--------------------------------------------------------------------------------
/src/models/Todolist.ts:
--------------------------------------------------------------------------------
1 | export interface TodolistItem {
2 | id: string;
3 | text: string;
4 | }
5 |
6 | export type Todolist = TodolistItem[];
7 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/src/routes/LocationSearch.ts:
--------------------------------------------------------------------------------
1 | export interface LocationSearch {
2 | '/'?: undefined;
3 | '/about': {
4 | abcd?: string;
5 | };
6 | }
7 |
--------------------------------------------------------------------------------
/src/routes/LocationStates.ts:
--------------------------------------------------------------------------------
1 | export interface LocationStates {
2 | '/'?: undefined;
3 | '/about': {
4 | abc: string;
5 | };
6 | }
7 |
--------------------------------------------------------------------------------
/src/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { BrowserRouter, Switch, Route } from 'react-router-dom';
3 | import { HomePage } from 'containers/HomePage';
4 | import { AboutPage } from 'containers/AboutPage';
5 | import { NotFoundPage } from 'containers/NotFoundPage';
6 | import { Page } from './types';
7 |
8 | export const pages: Page[] = [
9 | {
10 | path: '/',
11 | exact: true,
12 | component: HomePage,
13 | },
14 | {
15 | path: '/about',
16 | exact: true,
17 | component: AboutPage,
18 | },
19 | ];
20 |
21 | const Routes = () => {
22 | return (
23 |
24 |
25 | {pages.map(({ component, path, exact }) => {
26 | return ;
27 | })}
28 |
29 |
30 |
31 | );
32 | };
33 |
34 | export default Routes;
35 |
--------------------------------------------------------------------------------
/src/routes/types.ts:
--------------------------------------------------------------------------------
1 | import { ComponentType } from 'react';
2 | import { LocationStates } from './LocationStates';
3 |
4 | export type PathName = keyof LocationStates;
5 |
6 | export interface Page {
7 | path: PathName;
8 | exact?: boolean;
9 | component: ComponentType;
10 | }
11 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/store/configureStore.ts:
--------------------------------------------------------------------------------
1 | import { Middleware, createStore, applyMiddleware, combineReducers, compose } from 'redux';
2 | import logger from 'redux-logger';
3 | import createSagaMiddleware from 'redux-saga';
4 | import storage from 'redux-persist/lib/storage';
5 | import { persistStore, persistReducer } from 'redux-persist';
6 | import rootSaga from 'store/rootSagas';
7 | import rootReducers from 'store/rootReducers';
8 |
9 | const isDev = process.env.NODE_ENV === 'development';
10 | const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
11 |
12 | const persistConfig = {
13 | key: 'root',
14 | storage,
15 | whitelist: ['nightMode'],
16 | };
17 |
18 | const _combineReducers = combineReducers({
19 | ...rootReducers,
20 | });
21 |
22 | const sagaMiddleware = createSagaMiddleware();
23 | const reducers = persistReducer(persistConfig, _combineReducers);
24 | const middlewares: Middleware[] = [sagaMiddleware];
25 | if (isDev) {
26 | middlewares.push(logger);
27 | }
28 |
29 | const store = createStore(reducers, undefined, composeEnhancers(applyMiddleware(...middlewares)));
30 | sagaMiddleware.run(rootSaga);
31 | const persistor = persistStore(store as any);
32 |
33 | export type Reducers = ReturnType;
34 |
35 | export { store, persistor };
36 |
--------------------------------------------------------------------------------
/src/store/rootReducers.ts:
--------------------------------------------------------------------------------
1 | import { reducersHeader } from 'containers/Header';
2 | import { reducersHomePage } from 'containers/HomePage';
3 |
4 | const reducers = {
5 | homePage: reducersHomePage,
6 | ...reducersHeader,
7 | };
8 |
9 | export default reducers;
10 |
--------------------------------------------------------------------------------
/src/store/rootSagas.ts:
--------------------------------------------------------------------------------
1 | import { all, call, spawn, delay } from 'redux-saga/effects';
2 | import { sagaHomePage } from 'containers/HomePage';
3 |
4 | const sagas = [...sagaHomePage];
5 |
6 | // https://github.com/redux-saga/redux-saga/issues/760#issuecomment-273737022
7 | const makeRestartable = (saga: any) => {
8 | return function*() {
9 | yield spawn(function*() {
10 | while (true) {
11 | try {
12 | yield call(saga);
13 | console.error('unexpected root saga termination. The root sagas are supposed to be sagas that live during the whole app lifetime!', saga);
14 | } catch (e) {
15 | console.error('Saga error, the saga will be restarted', e);
16 | }
17 | yield delay(1000); // Avoid infinite failures blocking app TODO use backoff retry policy...
18 | }
19 | });
20 | };
21 | };
22 |
23 | const rootSagas = sagas.map(makeRestartable);
24 |
25 | export default function* root() {
26 | yield all(rootSagas.map(call));
27 | }
28 |
--------------------------------------------------------------------------------
/src/store/selectors.ts:
--------------------------------------------------------------------------------
1 | export const todolistSelector = (state: AppState) => state.homePage.todolist;
2 |
--------------------------------------------------------------------------------
/src/stories/1-Start/App.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { pages } from 'routes';
3 | import { Route } from 'react-router-dom';
4 |
5 | export default {
6 | title: 'Start/App',
7 | };
8 |
9 | export const App = () => {
10 | return pages.map(({ component, path, exact }) => {
11 | return ;
12 | });
13 | };
14 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/ActivityIndicator.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { number, optionsKnob, select } from '@storybook/addon-knobs';
3 | import getOptions from 'stories/utils/getOptions';
4 | import { defaultColors, ActivityIndicatorProps } from 'wiloke-react-core';
5 | import ActivityIndicator from './base/ActivityIndicator';
6 |
7 | export default {
8 | title: 'UI Base/ActivityIndicator',
9 | component: ActivityIndicator,
10 | };
11 |
12 | export const WithProps = () => {
13 | const sizeType = optionsKnob(
14 | 'Size Type',
15 | getOptions<('css style' | 'number')[]>(['css style', 'number']),
16 | 'css style',
17 | {
18 | display: 'inline-radio',
19 | },
20 | );
21 | const size =
22 | sizeType === 'css style'
23 | ? select(
24 | 'Size',
25 | getOptions(['large', 'medium', 'small']),
26 | 'large',
27 | )
28 | : number('Size', 10, { range: true, min: 0, max: 100 });
29 | const color = select('Color', getOptions(defaultColors), 'primary');
30 |
31 | return ;
32 | };
33 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/CountTo.stories.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { action } from '@storybook/addon-actions';
3 | import { number, optionsKnob, text } from '@storybook/addon-knobs';
4 | import { View } from 'wiloke-react-core';
5 | import getOptions from 'stories/utils/getOptions';
6 | import CountTo from './base/CountTo';
7 |
8 | export default {
9 | title: 'UI Base/CountTo',
10 | component: CountTo,
11 | };
12 |
13 | export const Default = () => {
14 | const [start, setStart] = useState(true);
15 |
16 | const countEnd = () => {
17 | setStart(false);
18 | action('onEndCounting')();
19 | };
20 | const countStart = () => setStart(true);
21 |
22 | const targetNumber = number('Target Number', 56789);
23 | const digitSpace = text('Digit space', ',');
24 | const quanityPlaceholder = number('Quanity Placeholder', 50);
25 | const animationName = optionsKnob('Src Type', getOptions(['countUp', 'countDown']), 'countDown', {
26 | display: 'inline-radio',
27 | });
28 | const animationDuration = number('animationDuration', 2000, { range: true, min: 0, max: 10000 });
29 | return (
30 |
31 |
32 |
42 |
43 | );
44 | };
45 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/Divider.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { select, number } from '@storybook/addon-knobs';
3 | import { View, defaultColors } from 'wiloke-react-core';
4 | import getOptions from 'stories/utils/getOptions';
5 | import Divider from './base/Divider';
6 |
7 | export default {
8 | title: 'UI Base/Divider',
9 | component: Divider,
10 | };
11 |
12 | export const Default = () => {
13 | const size = number('Size', 30, { range: true, min: 0, max: 1000 });
14 | const color = select('Color', getOptions(defaultColors), 'gray2');
15 | return (
16 |
17 | Box 1
18 |
19 | Box 2
20 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/GridSmart.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { number, select } from '@storybook/addon-knobs';
3 | import { range } from 'ramda';
4 | import getOptions from 'stories/utils/getOptions';
5 | import { BorderStyle, View, defaultColors } from 'wiloke-react-core';
6 | import GridSmart from './base/GridSmart';
7 |
8 | export default {
9 | title: 'UI Base/GridSmart',
10 | component: GridSmart,
11 | };
12 |
13 | export const Default = () => {
14 | const quanityItems = number('Quanity Items', 4, { range: true, min: 1, max: 20 });
15 | const columnWidth = number('Column Width', 200, { range: true, min: 0, max: window.innerWidth });
16 | const columnGap = number('Column Gap', 15, { range: true, min: 0, max: window.innerWidth });
17 | const columnCount = number('Column Count', 0, { range: true, min: 0, max: 10 });
18 | const columnRuleColor = select('Column Rule Color', getOptions(defaultColors), 'primary');
19 | const columnRuleStyle = select('Column Rule Style', getOptions(['solid', 'dotted', 'dashed'] as BorderStyle[]), 'solid');
20 | const columnRuleWidth = number('Column Rule Width', 0, { range: true, min: 0, max: 10 });
21 | return (
22 |
30 | {range(0, quanityItems).map((_, index) => (
31 |
32 | Box {index + 1}
33 |
34 | ))}
35 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/LineAwesome.stories.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import lineAwesome from 'stories/utils/lineAwesome';
3 | import { select, number } from '@storybook/addon-knobs';
4 | import getOptions from 'stories/utils/getOptions';
5 | import { View, defaultColors, LineAwesomeName, GridSmart, Text } from 'wiloke-react-core';
6 | import { range } from 'ramda';
7 | import Button from 'components/Button/Button';
8 | import LineAwesome from './base/LineAwesome';
9 |
10 | export default {
11 | title: 'UI Base/LineAwesome',
12 | component: LineAwesome,
13 | };
14 |
15 | export const Default = () => {
16 | const [searchValue, setSearchValue] = useState('');
17 | const [icons, setIcons] = useState(range(0, 80));
18 | const size = number('Size', 30, { range: true, min: 5, max: 50 });
19 | const color = select('Color', getOptions(defaultColors), 'gray8');
20 |
21 | const _renderIconText = (iconName: LineAwesomeName, index: number) => {
22 | if (!icons.includes(index)) {
23 | return null;
24 | }
25 | return (
26 |
27 |
28 | {iconName}
29 |
30 | );
31 | };
32 |
33 | return (
34 |
35 |
36 | Search Icon:
37 | {
40 | if (icons.length <= 80) {
41 | setIcons(range(0, lineAwesome.length));
42 | }
43 | }}
44 | onChange={e => setSearchValue(e.target.value)}
45 | />
46 |
47 |
48 | {!!searchValue ? lineAwesome.filter(name => name.includes(searchValue.toLowerCase())).map(_renderIconText) : lineAwesome.map(_renderIconText)}
49 |
50 | {icons.length <= 80 && (
51 |
52 |
55 |
56 | )}
57 |
58 | );
59 | };
60 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/MaterialIcon.stories.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import materialIcon from 'stories/utils/materialIcon';
3 | import { select, number, optionsKnob } from '@storybook/addon-knobs';
4 | import getOptions from 'stories/utils/getOptions';
5 | import { View, defaultColors, MaterialIconName, GridSmart, Text } from 'wiloke-react-core';
6 | import { range } from 'ramda';
7 | import Button from 'components/Button/Button';
8 | import MaterialIcon from './base/MaterialIcon';
9 |
10 | export default {
11 | title: 'UI Base/MaterialIcon',
12 | component: MaterialIcon,
13 | };
14 |
15 | export const Default = () => {
16 | const [searchValue, setSearchValue] = useState('');
17 | const [icons, setIcons] = useState(range(0, 80));
18 | const size = number('Size', 30, { range: true, min: 5, max: 50 });
19 | const color = select('Color', getOptions(defaultColors), 'gray8');
20 | const type = optionsKnob('Type', getOptions(['filled', 'outlined']), 'filled', { display: 'inline-radio' });
21 |
22 | const _renderIconText = (iconName: MaterialIconName, index: number) => {
23 | if (!icons.includes(index)) {
24 | return null;
25 | }
26 | return (
27 |
28 |
29 | {iconName}
30 |
31 | );
32 | };
33 |
34 | return (
35 |
36 |
37 | Search Icon:
38 | {
41 | if (icons.length <= 80) {
42 | setIcons(range(0, materialIcon.length));
43 | }
44 | }}
45 | onChange={e => setSearchValue(e.target.value)}
46 | />
47 |
48 |
49 | {!!searchValue
50 | ? materialIcon.filter(name => name.includes(searchValue.toLowerCase())).map(_renderIconText)
51 | : materialIcon.map(_renderIconText)}
52 |
53 | {icons.length <= 80 && (
54 |
55 |
58 |
59 | )}
60 |
61 | );
62 | };
63 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/OuterTrigger.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Text } from 'wiloke-react-core';
3 | import { action } from '@storybook/addon-actions';
4 | import OuterTrigger, { useOuterTrigger } from './base/OuterTrigger';
5 |
6 | export default {
7 | title: 'UI Base/OuterTrigger',
8 | component: OuterTrigger,
9 | };
10 |
11 | export const WithComponent = () => {
12 | return (
13 | action('onClick')('Outer: true')}>
14 |
15 | Click outer box
16 |
17 |
18 | );
19 | };
20 |
21 | export const WithHooks = () => {
22 | const targetRef = useOuterTrigger(() => action('onClick')('Outer: true'));
23 | return (
24 |
25 | Click outer box
26 |
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/ProgressLoader.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { boolean } from '@storybook/addon-knobs';
3 | import ProgressLoader from './base/ProgressLoader';
4 |
5 | export default {
6 | title: 'UI Base/ProgressLoader',
7 | component: ProgressLoader,
8 | };
9 |
10 | export const WithProps = () => {
11 | const run = boolean('Run', false);
12 | const done = boolean('Done', false);
13 |
14 | if (!run) {
15 | return null;
16 | }
17 |
18 | return ;
19 | };
20 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/Responsive.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Text, View } from 'wiloke-react-core';
3 | import Responsive, { SBUseResponsive } from './base/Responsive';
4 |
5 | export default {
6 | title: 'UI Base/Responsive',
7 | component: Responsive,
8 | };
9 |
10 | export const Default = () => {
11 | return (
12 |
13 | (
17 |
18 |
19 | Text Responsive
20 |
21 |
22 | )}
23 | />
24 |
25 | (
29 |
30 |
31 | Text Responsive
32 |
33 |
34 | )}
35 | />
36 |
37 | );
38 | };
39 | export const UseResponsive = () => {
40 | const element1 = SBUseResponsive({ maxWidth: 800, minWidth: 400 });
41 | const element2 = SBUseResponsive({ maxWidth: 500, minWidth: 300 });
42 |
43 | return (
44 |
45 |
46 |
47 | Text Responsive 1
48 |
49 |
50 |
51 |
52 | Text Responsive 2
53 |
54 |
55 |
56 | );
57 | };
58 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/Space.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View } from 'wiloke-react-core';
3 | import { number, optionsKnob } from '@storybook/addon-knobs';
4 | import getOptions from 'stories/utils/getOptions';
5 | import Space from './base/Space';
6 |
7 | export default {
8 | title: 'UI Base/Space',
9 | component: Space,
10 | };
11 |
12 | export const Default = () => {
13 | const size = number('Size', 30, { range: true, min: 0, max: 1000 });
14 | const type = optionsKnob(
15 | 'Type',
16 | getOptions<('horizontal' | 'vertical')[]>(['horizontal', 'vertical']),
17 | 'horizontal',
18 | {
19 | display: 'inline-radio',
20 | },
21 | );
22 | return (
23 |
24 |
25 | Box 1
26 |
27 |
28 |
29 | Box 2
30 |
31 |
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/Text.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import getWithStylesProps from 'stories/utils/getWithStylesProps';
3 | import { number } from '@storybook/addon-knobs';
4 | import { View } from 'wiloke-react-core';
5 | import Text from './base/Text';
6 |
7 | export default {
8 | title: 'UI Base/Text',
9 | component: Text,
10 | };
11 |
12 | export const WithStyles = () => {
13 | return (
14 |
15 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cum soluta fugit suscipit odio accusantium quas nostrum expedita tempora debitis
16 | blanditiis minus consequatur, nam eius sunt numquam nesciunt, quidem illum. Earum alias ea a explicabo. Explicabo corporis voluptatum
17 | repellendus itaque vero? Nam iste praesentium exercitationem recusandae dolorem. Quidem illum quod dolorem accusantium, modi iure non nisi
18 | dolores aliquid asperiores corporis incidunt excepturi soluta quasi. Unde perferendis, magni eaque quisquam ut facere, quod suscipit iure hic
19 | earum voluptatem molestiae itaque animi libero architecto, repellendus cupiditate reiciendis obcaecati. Rerum, assumenda optio error, temporibus
20 | facere ratione ea laborum iusto id provident libero blanditiis eum.
21 |
22 | );
23 | };
24 |
25 | export const WithTagName = () => {
26 | const numberOfLines = number('Number of lines', 5, { range: true, min: 1, max: 10 });
27 | const size = number('Size (Demo tag p)', 15, { range: true, min: 8, max: 100 });
28 |
29 | return (
30 |
31 | Heading 1
32 | Heading 2
33 | Heading 3
34 | Heading 4
35 | Heading 5
36 | Heading 6
37 |
38 |
39 | p: Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cum soluta fugit suscipit odio accusantium quas nostrum expedita tempora debitis
40 | blanditiis minus consequatur, nam eius sunt numquam nesciunt, quidem illum. Earum alias ea a explicabo. Explicabo corporis voluptatum
41 | repellendus itaque vero? Nam iste praesentium exercitationem recusandae dolorem. Quidem illum quod dolorem accusantium, modi iure non nisi
42 | dolores aliquid asperiores corporis incidunt excepturi soluta quasi. Unde perferendis, magni eaque quisquam ut facere, quod suscipit iure hic
43 | earum voluptatem molestiae itaque animi libero architecto, repellendus cupiditate reiciendis obcaecati. Rerum, assumenda optio error,
44 | temporibus facere ratione ea laborum iusto id provident libero blanditiis eum.
45 |
46 |
47 | i: Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet, explicabo!
48 |
49 |
50 | span: Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet, explicabo!
51 |
52 | );
53 | };
54 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/View.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import getWithStylesProps from 'stories/utils/getWithStylesProps';
3 | import { number, array } from '@storybook/addon-knobs';
4 | import { Text } from 'wiloke-react-core';
5 | import View from './base/View';
6 |
7 | export default {
8 | title: 'UI Base/View',
9 | component: View,
10 | };
11 |
12 | export const WithStyles = () => {
13 | return (
14 |
15 |
16 | ReactNode
17 |
18 |
19 | );
20 | };
21 |
22 | export const WithGrid = () => {
23 | const items = number('Items', 4, { range: true, min: 1, max: 20 });
24 | const columns = array('Columns ( max 12 )', ['12', '6', '4', '3'], '|').map(Number);
25 | return (
26 |
27 |
28 | {Array(items)
29 | .fill(null)
30 | .map((_, index) => (
31 |
32 |
33 | Item {index + 1}
34 |
35 |
36 | ))}
37 |
38 |
39 | );
40 | };
41 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/ViewportTracking.stories.tsx:
--------------------------------------------------------------------------------
1 | import { action } from '@storybook/addon-actions';
2 | import { number } from '@storybook/addon-knobs';
3 | import React from 'react';
4 | import { Text, View } from 'wiloke-react-core';
5 | import ViewportTracking from './base/ViewportTracking';
6 |
7 | export default {
8 | title: 'UI Base/ViewportTracking',
9 | component: ViewportTracking,
10 | };
11 |
12 | export const WithProps = () => {
13 | const offsetTop = number('Offset Top', 0);
14 | const offsetBottom = number('Offset Bottom', 0);
15 | const numberOfRuns = number('Number Of Runs', Infinity);
16 | return (
17 |
18 |
19 | Scroll xuống dưới
20 |
21 | action('onClick')('Trong màn hình')}
26 | onLeaveViewport={() => action('onClick')('Ngoài màn hình')}
27 | >
28 |
29 | Viewport
30 |
31 |
32 |
33 |
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/base/ActivityIndicator.tsx:
--------------------------------------------------------------------------------
1 | import { ActivityIndicator } from 'wiloke-react-core';
2 |
3 | export const Component = ActivityIndicator;
4 |
5 | export default Component;
6 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/base/CountTo.tsx:
--------------------------------------------------------------------------------
1 | import { CountTo } from 'wiloke-react-core';
2 |
3 | export const Component = CountTo;
4 |
5 | export default Component;
6 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/base/Divider.tsx:
--------------------------------------------------------------------------------
1 | import { Divider } from 'wiloke-react-core';
2 |
3 | export const Component = Divider;
4 |
5 | export default Component;
6 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/base/GridSmart.tsx:
--------------------------------------------------------------------------------
1 | import { GridSmart } from 'wiloke-react-core';
2 |
3 | export const Component = GridSmart;
4 |
5 | export default Component;
6 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/base/Image.tsx:
--------------------------------------------------------------------------------
1 | import { Image } from 'wiloke-react-core';
2 |
3 | export const Component = Image;
4 |
5 | export default Component;
6 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/base/LineAwesome.tsx:
--------------------------------------------------------------------------------
1 | import { LineAwesome } from 'wiloke-react-core';
2 |
3 | export const Component = LineAwesome;
4 |
5 | export default Component;
6 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/base/MaterialIcon.tsx:
--------------------------------------------------------------------------------
1 | import { MaterialIcon } from 'wiloke-react-core';
2 |
3 | export const Component = MaterialIcon;
4 |
5 | export default Component;
6 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/base/OuterTrigger.tsx:
--------------------------------------------------------------------------------
1 | import { OuterTrigger, useOuterTrigger as u } from 'wiloke-react-core';
2 |
3 | export const Component = OuterTrigger;
4 |
5 | export const useOuterTrigger = u;
6 |
7 | export default Component;
8 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/base/ProgressLoader.tsx:
--------------------------------------------------------------------------------
1 | import { ProgressLoader } from 'wiloke-react-core';
2 |
3 | export const Component = ProgressLoader;
4 |
5 | export default Component;
6 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/base/Responsive.tsx:
--------------------------------------------------------------------------------
1 | import { Responsive, useResponsive } from 'wiloke-react-core';
2 |
3 | export const Component = Responsive;
4 | export const SBUseResponsive = useResponsive;
5 |
6 | export default Component;
7 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/base/Space.tsx:
--------------------------------------------------------------------------------
1 | import { Space } from 'wiloke-react-core';
2 |
3 | export const Component = Space;
4 |
5 | export default Component;
6 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/base/Sticky.tsx:
--------------------------------------------------------------------------------
1 | import { Sticky } from 'wiloke-react-core';
2 |
3 | export const Component = Sticky;
4 |
5 | export default Component;
6 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/base/Text.tsx:
--------------------------------------------------------------------------------
1 | import { Text } from 'wiloke-react-core';
2 |
3 | export const Component = Text;
4 |
5 | export default Component;
6 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/base/View.tsx:
--------------------------------------------------------------------------------
1 | import { View } from 'wiloke-react-core';
2 |
3 | export const Component = View;
4 |
5 | export default Component;
6 |
--------------------------------------------------------------------------------
/src/stories/2-UIBase/base/ViewportTracking.tsx:
--------------------------------------------------------------------------------
1 | import { ViewportTracking } from 'wiloke-react-core';
2 |
3 | export const Component = ViewportTracking;
4 |
5 | export default Component;
6 |
--------------------------------------------------------------------------------
/src/stories/3-Components/Alert.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { number, optionsKnob, select, text, boolean } from '@storybook/addon-knobs';
3 | import getOptions from 'stories/utils/getOptions';
4 | import { action } from '@storybook/addon-actions';
5 | import { Radius } from 'wiloke-react-core';
6 | import Alert, { AlertProps } from 'components/Alert';
7 |
8 | export default {
9 | title: 'General/Alert',
10 | component: Alert,
11 | };
12 |
13 | export const WithProps = () => {
14 | const type = select(
15 | 'Type',
16 | getOptions(['danger', 'info', 'success', 'warning']),
17 | 'info',
18 | );
19 | const message = text('Message', `Lorem ipsum, dolor sit amet consectetur`);
20 | const description = text(
21 | 'Description',
22 | `Minima, tempore magni, incidunt tenetur iusto impedit, corporis officiis suscipit quaerat ut aut ullam necessitatibus porro voluptas repellendus architecto delectus modi! Tenetur placeat inventore reprehenderit soluta, quod veritatis, totam sapiente, quis ab molestiae magnam iusto quaerat!`,
23 | );
24 | const closable = boolean('Closable', true);
25 | const showIcon = boolean('ShowIcon', true);
26 | const radiusType = optionsKnob('Radius Type', getOptions(['css style', 'number']), 'css style', {
27 | display: 'inline-radio',
28 | });
29 | const radius =
30 | radiusType === 'css style'
31 | ? select(
32 | 'Radius',
33 | getOptions(['pill', 'square']),
34 | 'square',
35 | )
36 | : number('Radius', 0, { range: true, min: 0, max: 100 });
37 | const size = select(
38 | 'Size',
39 | getOptions(['small', 'medium', 'large']),
40 | 'medium',
41 | );
42 |
43 | return (
44 |
54 | );
55 | };
56 |
--------------------------------------------------------------------------------
/src/stories/3-Components/Avatar.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { number, text } from '@storybook/addon-knobs';
3 | import Avatar from 'components/Avatar';
4 | import { View, Text } from 'wiloke-react-core';
5 |
6 | export default {
7 | title: 'General/Avatar',
8 | component: (Avatar as any).type,
9 | };
10 |
11 | export const Default = () => {
12 | const uri = text('Image Uri', '');
13 | const size = number('Size', 30, { range: true, min: 10, max: 100 });
14 | const name = text('Name', 'Wiloke');
15 |
16 | return (
17 |
18 | Avatar
19 |
20 | Loading
21 |
22 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/src/stories/3-Components/Button.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { action } from '@storybook/addon-actions';
3 | import { text, select, boolean, number } from '@storybook/addon-knobs';
4 | import getOptions from 'stories/utils/getOptions';
5 | import getWithStylesProps, { generalGroup } from 'stories/utils/getWithStylesProps';
6 | import { LineAwesome, Text, View, Size } from 'wiloke-react-core';
7 | import Button, { ButtonProps } from 'components/Button/Button';
8 |
9 | export default {
10 | title: 'General/Button',
11 | component: Button,
12 | };
13 |
14 | const getButtonProps = (): Omit => ({
15 | size: select(
16 | 'Size',
17 | getOptions(['extra-small', 'small', 'medium', 'large']),
18 | 'medium',
19 | ),
20 | loading: boolean('Loading', false),
21 | fontSize: number('Font Size', undefined as any),
22 | disabled: boolean('Disabled', false),
23 | onClick: action('onClick'),
24 | });
25 |
26 | export const WithStyles = () => {
27 | return (
28 |
31 | );
32 | };
33 |
34 | export const WithProps = () => {
35 | return (
36 |
39 | );
40 | };
41 |
42 | export const WithChildrenNode = () => {
43 | return (
44 |
50 | );
51 | };
52 |
--------------------------------------------------------------------------------
/src/stories/3-Components/Checkbox.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Checkbox from 'components/Checkbox';
3 | import { boolean, number, optionsKnob, select, text } from '@storybook/addon-knobs';
4 | import { BorderStyle, ColorNames, defaultColors, Radius, Size } from 'wiloke-react-core';
5 | import getOptions from 'stories/utils/getOptions';
6 | import { action } from '@storybook/addon-actions';
7 |
8 | export default {
9 | title: 'Fields/Checkbox',
10 | component: (Checkbox as any).type,
11 | };
12 |
13 | export const WithProps = () => {
14 | const isLoading = boolean('Loading', false);
15 | let size: Size = 'medium';
16 | let checked;
17 | let disabled;
18 | let iconActiveColor: ColorNames = 'light';
19 | let borderWidth = 2;
20 | let borderColor: ColorNames = 'gray5';
21 | let borderStyle: BorderStyle = 'solid';
22 | let activeColor: ColorNames = 'primary';
23 | const radiusType = optionsKnob<'css style' | 'number'>('Radius Type', getOptions(['css style', 'number']), 'number', {
24 | display: 'inline-radio',
25 | });
26 | const radius =
27 | radiusType === 'css style'
28 | ? select(
29 | 'Radius',
30 | getOptions(['pill', 'square']),
31 | 'square',
32 | )
33 | : number('Radius', 5, { range: true, min: 0, max: 100 });
34 |
35 | if (!isLoading) {
36 | checked = boolean('Checked', false);
37 | disabled = boolean('Disabled', false);
38 | size = select(
39 | 'Size',
40 | getOptions(['extra-small', 'small', 'medium', 'large']),
41 | 'small',
42 | ) as any;
43 | iconActiveColor = select('Color Icon Active', getOptions(defaultColors), 'light');
44 | activeColor = select('Color Checkbox Active', getOptions(defaultColors), 'primary');
45 | borderWidth = number('Boder Width', 2);
46 | borderColor = select('Border Color', getOptions(defaultColors), 'gray5');
47 | borderStyle = select(
48 | 'Border Style',
49 | getOptions(['solid', 'dotted', 'dashed']),
50 | 'solid',
51 | );
52 | }
53 |
54 | const _handleValueChange = (value: boolean) => {
55 | action('onValueChange')(value);
56 | };
57 |
58 | return isLoading ? (
59 |
60 | ) : (
61 |
73 | {text('Children', 'Lorem ipsum')}
74 |
75 | );
76 | };
77 |
--------------------------------------------------------------------------------
/src/stories/3-Components/CodeHighlight.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CodeHighlight, { CodeHighlightProps } from 'components/CodeHighlight';
3 | import { select, text } from '@storybook/addon-knobs';
4 | import getOptions from 'stories/utils/getOptions';
5 |
6 | export default {
7 | title: 'General/CodeHighlight',
8 | component: (CodeHighlight as any).type,
9 | };
10 | console.log(CodeHighlight);
11 |
12 | const defaultChildren = `function HelloMessage({ name }) {
13 | return Hello {name}
;
14 | }
15 | ReactDOM.render(
16 | ,
17 | document.getElementById('container')
18 | );`;
19 |
20 | function decodeHtml(html: string) {
21 | const txt = document.createElement('textarea');
22 | txt.innerHTML = html;
23 | return txt.value;
24 | }
25 |
26 | export const Default = () => {
27 | const language = select(
28 | 'language',
29 | getOptions(['c', 'cpp', 'css', 'go', 'java', 'js', 'jsx', 'php', 'python', 'scss', 'tsx', 'typescript']),
30 | 'jsx',
31 | );
32 | const children = text('children', defaultChildren);
33 |
34 | return {decodeHtml(children)};
35 | };
36 |
--------------------------------------------------------------------------------
/src/stories/3-Components/Collapse.stories.tsx:
--------------------------------------------------------------------------------
1 | import { action } from '@storybook/addon-actions';
2 | import { boolean, number, select } from '@storybook/addon-knobs';
3 | import Collapse from 'components/Collapse';
4 | import ColorPickerBeauty from 'components/ColorPickerBeauty';
5 | import Field from 'components/Field';
6 | import SwitchBeauty from 'components/SwitchBeauty';
7 | import { TextInput } from 'components/TextInput';
8 | import React from 'react';
9 | import getOptions from 'stories/utils/getOptions';
10 | import { BorderStyle, defaultColors } from 'wiloke-react-core';
11 |
12 | export default {
13 | title: 'General/Collapse',
14 | component: Collapse,
15 | subcomponents: { 'Collapse.Panel': (Collapse.Panel as any).type },
16 | };
17 |
18 | export const Default = () => {
19 | return (
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | };
32 |
33 | export const WithProps = () => {
34 | const radius = number('Radius', 5, { range: true, min: 0, max: 30 });
35 | const backgroundColor = select('Background color', getOptions(defaultColors), 'gray2');
36 | const borderColor = select('Border color', getOptions(defaultColors), 'gray5');
37 | const borderStyle = select(
38 | 'Border Style',
39 | getOptions(['dashed', 'dotted', 'solid']),
40 | 'solid',
41 | );
42 | const borderWidth = number('Border Width', 1);
43 | const showArrow = boolean('Show Arrow', true);
44 | const disabled = boolean('Disabled', false);
45 |
46 | return (
47 |
48 |
59 |
60 |
61 |
62 |
63 |
64 |
75 |
76 |
77 |
78 |
79 |
80 | );
81 | };
82 |
--------------------------------------------------------------------------------
/src/stories/3-Components/ColorPicker.stories.tsx:
--------------------------------------------------------------------------------
1 | import { action } from '@storybook/addon-actions';
2 | import { boolean, number, optionsKnob, select } from '@storybook/addon-knobs';
3 | import ColorPicker, { ColorPickerProps } from 'components/ColorPicker';
4 | import React from 'react';
5 | import getOptions from 'stories/utils/getOptions';
6 | import { Radius, View } from 'wiloke-react-core';
7 |
8 | export default {
9 | title: 'Fields/ColorPicker',
10 | component: ColorPicker,
11 | };
12 |
13 | export const WithProps = () => {
14 | const isLoading = boolean('Loading', false);
15 | const onlyShowColorBoard = boolean('Only show color picker board', false);
16 |
17 | const radiusType = optionsKnob<'css style' | 'number'>('Radius Picker Type', getOptions(['css style', 'number']), 'number', {
18 | display: 'inline-radio',
19 | });
20 |
21 | const radius =
22 | radiusType === 'css style'
23 | ? select(
24 | 'Radius Picker',
25 | getOptions(['pill', 'square']),
26 | 'square',
27 | )
28 | : number('Radius Picker', 5, { range: true, min: 0, max: 100 });
29 |
30 | const selectType = select(
31 | 'Color Picker Platform',
32 | getOptions(['chrome', 'photoshop', 'sketch']),
33 | 'sketch',
34 | );
35 |
36 | const selectPlacement = select(
37 | 'Placement',
38 | getOptions([
39 | 'bottom',
40 | 'bottom-end',
41 | 'bottom-start',
42 | 'top',
43 | 'top-end',
44 | 'top-start',
45 | 'left',
46 | 'left-end',
47 | 'left-start',
48 | 'right',
49 | 'right-end',
50 | 'right-start',
51 | ]),
52 | 'bottom-start',
53 | );
54 |
55 | return (
56 |
57 | {isLoading ? (
58 |
59 | ) : (
60 |
67 | )}
68 |
69 | );
70 | };
71 |
--------------------------------------------------------------------------------
/src/stories/3-Components/Field.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Field from 'components/Field';
3 | import { TextInput } from 'components/TextInput';
4 | import { View } from 'wiloke-react-core';
5 | import ColorPickerBeauty from 'components/ColorPickerBeauty';
6 |
7 | export default {
8 | title: 'Fields/Field',
9 | component: Field,
10 | };
11 |
12 | export const WithProps = () => {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/src/stories/3-Components/IconText.stories.tsx:
--------------------------------------------------------------------------------
1 | import { color, text } from '@storybook/addon-knobs';
2 | import IconText from 'components/IconText/IconText';
3 | import React from 'react';
4 | import { GridSmart, View } from 'wiloke-react-core';
5 |
6 | export default {
7 | title: 'General/IconText',
8 | component: (IconText as any).type,
9 | };
10 |
11 | export const WithProps = () => {
12 | const title = text('Title', 'Adipisicing elit');
13 | const desc = text(
14 | 'Text',
15 | `Lorem ipsum dolor sit amet, consectetur adipisicing elit. Enim, veniam possimus. Qui beatae enim aliquam culpa quia, ea dolorum aperiam temporibus?`,
16 | );
17 | const iconColor = color('Icon Color', '#FD9B9B');
18 |
19 | return (
20 |
21 |
22 |
23 |
29 |
35 |
41 |
47 |
53 |
54 |
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/src/stories/3-Components/NumberInput.stories.tsx:
--------------------------------------------------------------------------------
1 | import { action } from '@storybook/addon-actions';
2 | import { boolean, number, optionsKnob, select } from '@storybook/addon-knobs';
3 | import { NumberInput } from 'components/NumberInput';
4 | import React, { useState } from 'react';
5 | import getOptions from 'stories/utils/getOptions';
6 | import getWithStylesProps from 'stories/utils/getWithStylesProps';
7 | import { Radius, Size } from 'wiloke-react-core';
8 |
9 | export default {
10 | title: 'Fields/NumberInput',
11 | component: NumberInput,
12 | };
13 |
14 | export const WithProps = () => {
15 | const isLoading = boolean('Loading Input', false);
16 | const block = boolean('Block', false);
17 | const initNumber = 0;
18 | let disabled: boolean,
19 | min: number,
20 | max: number,
21 | step: number,
22 | size: Exclude | undefined = 'small';
23 |
24 | const radiusType = optionsKnob<'css style' | 'number'>('Radius Type', getOptions(['css style', 'number']), 'number', {
25 | display: 'inline-radio',
26 | });
27 |
28 | const radius =
29 | radiusType === 'css style'
30 | ? select(
31 | 'Radius',
32 | getOptions(['pill', 'square']),
33 | 'square',
34 | )
35 | : number('Radius', 5, { range: true, min: 0, max: 100 });
36 |
37 | if (isLoading === false) {
38 | disabled = boolean('Disabled', false);
39 | min = number('Min', initNumber);
40 | max = number('Max', 10);
41 | step = number('Step', 1);
42 |
43 | size = select(
44 | 'Size',
45 | getOptions[]>(['large', 'medium', 'small']),
46 | 'small',
47 | );
48 | }
49 |
50 | const [value, setValue] = useState(initNumber);
51 |
52 | const _handleOnChange = (value: number) => {
53 | setValue(value);
54 | action('onChange')(value);
55 | };
56 |
57 | const _renderNumberInput = () => {
58 | return isLoading ? (
59 |
60 | ) : (
61 |
74 | );
75 | };
76 |
77 | return _renderNumberInput();
78 | };
79 |
80 | export const WithStyles = () => {
81 | return ;
82 | };
83 |
--------------------------------------------------------------------------------
/src/stories/3-Components/Slider.stories.tsx:
--------------------------------------------------------------------------------
1 | import { action } from '@storybook/addon-actions';
2 | import { boolean, number, select } from '@storybook/addon-knobs';
3 | import Slider from 'components/Slider';
4 | import React, { useState } from 'react';
5 | import getOptions from 'stories/utils/getOptions';
6 | import { ColorNames, defaultColors, View } from 'wiloke-react-core';
7 |
8 | export default {
9 | title: 'Fields/Slider',
10 | component: Slider,
11 | };
12 |
13 | export const Default = () => {
14 | const [value, setValue] = useState(0);
15 | const MIN = 0;
16 | const MAX = 100;
17 | const STEP = 1;
18 |
19 | const _handleOnChange = (value: number) => {
20 | setValue(value);
21 | action('onChange')(value);
22 | };
23 |
24 | return (
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | export const WithProps = () => {
32 | const isLoading = boolean('Loading', false);
33 | let trackColor: ColorNames = 'primary',
34 | handleColor: ColorNames = 'light',
35 | handleBorderColor: ColorNames = 'gray4',
36 | railColor: ColorNames = 'gray5',
37 | min,
38 | max,
39 | step;
40 |
41 | if (isLoading === false) {
42 | min = number('Min', 0);
43 | max = number('Max', 100);
44 | step = number('Step', 1);
45 |
46 | trackColor = select('Track Color', getOptions(defaultColors), 'primary');
47 | handleColor = select('Hanlder Color', getOptions(defaultColors), 'light');
48 | handleBorderColor = select('Hanlder Border Color', getOptions(defaultColors), 'gray4');
49 | railColor = select('Rail Color', getOptions(defaultColors), 'gray5');
50 | }
51 |
52 | const [value, setValue] = useState(0);
53 | const _handleOnChange = (value: number) => {
54 | setValue(value);
55 | action('onChange')(value);
56 | };
57 |
58 | return (
59 |
60 | {isLoading ? (
61 |
62 | ) : (
63 |
74 | )}
75 |
76 | );
77 | };
78 |
--------------------------------------------------------------------------------
/src/stories/3-Components/Switch.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Size } from 'wiloke-react-core';
3 | import { boolean, select } from '@storybook/addon-knobs';
4 | import getOptions from 'stories/utils/getOptions';
5 | import { action } from '@storybook/addon-actions';
6 | import Switch from 'components/Switch';
7 |
8 | export default {
9 | title: 'Fields/Switch',
10 | component: (Switch as any).type,
11 | };
12 |
13 | export const Default = () => {
14 | const size = select(
15 | 'Size',
16 | getOptions(['extra-small', 'small', 'medium', 'large']),
17 | 'medium',
18 | );
19 | const checked = boolean('Checked', false);
20 | const loading = boolean('Loading', false);
21 | const disabled = boolean('Disabled', false);
22 |
23 | const _handleValueChange = (value: boolean) => {
24 | action('onValueChange')(value);
25 | };
26 |
27 | return ;
28 | };
29 |
--------------------------------------------------------------------------------
/src/stories/3-Components/SwitchBeauty.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SwitchBeauty from 'components/SwitchBeauty';
3 | import getWithStylesProps from 'stories/utils/getWithStylesProps';
4 | import getOptions from 'stories/utils/getOptions';
5 | import { ColorNames, defaultColors, Size } from 'wiloke-react-core';
6 | import { boolean, select, text } from '@storybook/addon-knobs';
7 |
8 | export default {
9 | title: 'Fields/SwitchBeauty',
10 | component: SwitchBeauty,
11 | };
12 |
13 | const getRadioDefaultProps = () => ({
14 | size: select(
15 | 'Size',
16 | getOptions(['extra-small', 'small', 'medium', 'large']),
17 | 'large',
18 | ),
19 | checked: boolean('Checked', false),
20 | disabled: boolean('Disabled', false),
21 | disableText: text('Disable Text', 'Disabled'),
22 | enableText: text('Enable Text', 'Enable'),
23 | loading: boolean('Loading', false),
24 | enableColor: select('Enable Color', getOptions(defaultColors), 'success'),
25 | disableColor: select('Disable Color', getOptions(defaultColors), 'gray7'),
26 | });
27 |
28 | export const WithProps = () => {
29 | return ;
30 | };
31 |
32 | export const WithStyles = () => {
33 | return ;
34 | };
35 |
--------------------------------------------------------------------------------
/src/stories/3-Components/TextInput.stories.tsx:
--------------------------------------------------------------------------------
1 | import React, { ChangeEvent, useState } from 'react';
2 | import { InputProps, TextInput } from 'components/TextInput';
3 | import { boolean, number, optionsKnob, select, text } from '@storybook/addon-knobs';
4 | import getOptions from 'stories/utils/getOptions';
5 | import { action } from '@storybook/addon-actions';
6 | import getWithStylesProps from 'stories/utils/getWithStylesProps';
7 | import { Radius, Size } from 'wiloke-react-core';
8 |
9 | export default {
10 | title: 'Fields/TextInput',
11 | component: TextInput,
12 | };
13 |
14 | export const WithProps = () => {
15 | const isLoading = boolean('Loading Input', false);
16 |
17 | let size: Size = 'large',
18 | disabled,
19 | block,
20 | placeholder;
21 | const radiusType = optionsKnob<'css style' | 'number'>('Radius Type', getOptions(['css style', 'number']), 'number', {
22 | display: 'inline-radio',
23 | });
24 |
25 | const radius =
26 | radiusType === 'css style'
27 | ? select(
28 | 'Radius',
29 | getOptions(['pill', 'square']),
30 | 'square',
31 | )
32 | : number('Radius', 5, { range: true, min: 0, max: 100 });
33 | if (isLoading === false) {
34 | size = select(
35 | 'Size',
36 | getOptions(['small', 'medium', 'large']),
37 | 'large',
38 | );
39 | disabled = boolean('Disabled', false);
40 | block = boolean('Block', false);
41 | placeholder = text('Placeholder', 'Do something...');
42 | }
43 |
44 | const [value, setValue] = useState('');
45 |
46 | const _handleOnChange = (event: ChangeEvent) => {
47 | setValue(event.target.value);
48 | action('onChange')(event.target.value);
49 | };
50 |
51 | return isLoading ? (
52 |
53 | ) : (
54 |
66 | );
67 | };
68 |
69 | export const WithStyles = () => {
70 | const _renderNumberInput = () => {
71 | return ;
72 | };
73 |
74 | return _renderNumberInput();
75 | };
76 |
--------------------------------------------------------------------------------
/src/stories/3-Components/TextUnderline.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { text, number, color, select } from '@storybook/addon-knobs';
3 | import getOptions from 'stories/utils/getOptions';
4 | import { View, defaultColors } from 'wiloke-react-core';
5 | import TextUnderline from 'components/TextUnderline';
6 |
7 | export default {
8 | title: 'General/TextUnderline',
9 | component: TextUnderline,
10 | };
11 |
12 | export const Default = () => {
13 | const size = number('Size', 60, { range: true, min: 20, max: 100 });
14 | const lineSize = number('Line Size', 20, { range: true, min: 20, max: 100 });
15 | const lineBottomSpace = number('Line Bottom Space', 40, { range: true, min: 20, max: 100 });
16 | const lineColor = color('Line Color', '#94fbd1');
17 | const colorProps = select('Color', getOptions(defaultColors), 'gray8');
18 | const children = text('Children', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit');
19 | return (
20 |
21 |
22 | {children}
23 |
24 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/src/stories/utils/getOptions.ts:
--------------------------------------------------------------------------------
1 | export default function getOptions(source: T) {
2 | if (Array.isArray(source)) {
3 | return source.reduce(
4 | (obj, item) => ({
5 | ...obj,
6 | [item]: item,
7 | }),
8 | {},
9 | );
10 | }
11 | return Object.keys(source).reduce((obj, key) => {
12 | return {
13 | ...obj,
14 | [key]: key,
15 | };
16 | }, {});
17 | }
18 |
--------------------------------------------------------------------------------
/src/stories/utils/getWithStylesProps.ts:
--------------------------------------------------------------------------------
1 | import { select, optionsKnob, number, boolean } from '@storybook/addon-knobs';
2 | import { BorderStyle, defaultColors, WithStylesProps } from 'wiloke-react-core';
3 | import getOptions from './getOptions';
4 |
5 | export const borderStyleGroup = 'Border Style';
6 | export const generalGroup = 'General';
7 |
8 | const getWithStylesProps = (): WithStylesProps & { radiusType: string; borderEnalbed: boolean } => {
9 | const backgroundColor = select('Background Color', getOptions(defaultColors), 'primary', generalGroup);
10 | const backgroundColorHover = select('Background Color Hover', getOptions(defaultColors), 'primary', generalGroup);
11 | const color = select('Color', getOptions(defaultColors), 'light', generalGroup);
12 | const colorHover = select('Color Hover', getOptions(defaultColors), 'light', generalGroup);
13 | const radiusType = optionsKnob(
14 | 'Radius Type',
15 | getOptions<('css style' | 'number')[]>(['css style', 'number']),
16 | 'css style',
17 | {
18 | display: 'inline-radio',
19 | },
20 | generalGroup,
21 | );
22 | const radius =
23 | radiusType === 'css style'
24 | ? select(
25 | 'Radius',
26 | getOptions(['pill', 'square']),
27 | 'square',
28 | generalGroup,
29 | )
30 | : number('Radius', 10, { range: true, min: 0, max: 100 }, generalGroup);
31 |
32 | const borderEnalbed = boolean('Border Enabled', false, borderStyleGroup);
33 | const borderColor = borderEnalbed
34 | ? select('Border Color', getOptions(defaultColors), 'primary', borderStyleGroup)
35 | : select('Border Color ', getOptions(defaultColors), undefined, borderStyleGroup);
36 | const borderColorHover = borderEnalbed
37 | ? select('Border Color Hover', getOptions(defaultColors), 'secondary', borderStyleGroup)
38 | : select('Border Color Hover ', getOptions(defaultColors), undefined, borderStyleGroup);
39 | const borderStyle = borderEnalbed
40 | ? select(
41 | 'Border Style',
42 | getOptions(['solid', 'dotted', 'dashed']),
43 | 'solid',
44 | borderStyleGroup,
45 | )
46 | : select(
47 | 'Border Style ',
48 | getOptions(['solid', 'dotted', 'dashed']),
49 | undefined,
50 | borderStyleGroup,
51 | );
52 | const borderWidth = borderEnalbed ? number('Border Width', 2) : undefined;
53 | return {
54 | backgroundColor,
55 | backgroundColorHover,
56 | color,
57 | colorHover,
58 | radiusType,
59 | radius,
60 | borderEnalbed,
61 | borderColor,
62 | borderColorHover,
63 | borderStyle,
64 | borderWidth,
65 | };
66 | };
67 |
68 | export default getWithStylesProps;
69 |
--------------------------------------------------------------------------------
/src/styles/base.ts:
--------------------------------------------------------------------------------
1 | const styleBase = `
2 | *,
3 | :after,
4 | :before {
5 | -webkit-box-sizing: border-box;
6 | box-sizing: border-box;
7 | }
8 |
9 | body {
10 | font-size: 14px;
11 | line-height: 1.78;
12 | font-weight: 400;
13 | }
14 |
15 | h1 {
16 | font-size: 40px;
17 | }
18 | h2 {
19 | font-size: 34px;
20 | }
21 | h3 {
22 | font-size: 28px;
23 | }
24 | h4 {
25 | font-size: 24px;
26 | }
27 | h5 {
28 | font-size: 18px;
29 | }
30 | h6 {
31 | font-size: 16px;
32 | }
33 |
34 | h1, h2, h3, h4, h5, h6 {
35 | font-weight: 600;
36 | line-height: 1.3;
37 | margin: 0;
38 | }
39 |
40 | `;
41 |
42 | export default styleBase;
43 |
--------------------------------------------------------------------------------
/src/types.d.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Reducers } from './store/configureStore';
3 |
4 | declare global {
5 | declare type AppState = Reducers;
6 | declare type RootState = Reducers;
7 | declare type GetState = () => AppState;
8 | declare type Connect = ReturnType & TTypeOfMapDispatchToProps;
9 |
10 | declare type ValueOf = T[keyof T];
11 |
12 | declare type Status = 'idle' | 'loading' | 'success' | 'failure';
13 | }
14 |
--------------------------------------------------------------------------------
/src/types/Endpoints.ts:
--------------------------------------------------------------------------------
1 | export enum Endpoints {
2 | Todolist = 'todolist',
3 | }
4 |
--------------------------------------------------------------------------------
/src/utils/functions/createTranslation/createTranslation.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import * as R from 'ramda';
3 | import { TransitionDefault, I18n } from './types';
4 |
5 | interface CreateTranslationInput {
6 | initialValue: T;
7 | locale: string | (() => string);
8 | cache?: boolean;
9 | request?: () => Promise;
10 | }
11 |
12 | const createTranslation = ({ initialValue, locale, cache = false, request }: CreateTranslationInput) => {
13 | const { origin } = window.location;
14 | const STORAGE_KEY = `___translation_${origin}___`;
15 | const translationCache = localStorage.getItem(STORAGE_KEY);
16 | let requested = false;
17 | let defaultI18n = { ...initialValue };
18 | if (cache) {
19 | if (!!translationCache && translationCache.includes('{')) {
20 | defaultI18n = JSON.parse(translationCache);
21 | }
22 | } else {
23 | localStorage.removeItem(STORAGE_KEY);
24 | }
25 |
26 | const useTranslation = () => {
27 | const [i18nData, setI18n] = useState(defaultI18n);
28 | const _locale = typeof locale === 'function' ? locale() : locale;
29 |
30 | useEffect(() => {
31 | if (!requested) {
32 | const getTranslation = async () => {
33 | if (!!request) {
34 | try {
35 | const data = await request();
36 | if (cache) {
37 | localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
38 | }
39 | setI18n(data);
40 | } catch {
41 | setI18n(defaultI18n);
42 | }
43 | }
44 | };
45 | getTranslation();
46 | requested = true;
47 | }
48 | }, []);
49 |
50 | const translation: I18n['translation'] = (key, options) => {
51 | const _lang = i18nData[_locale];
52 | const _options = options as Record;
53 | if (!_lang) {
54 | return '';
55 | }
56 | const value = key.includes('.') ? R.path(key.split('.'), _lang) : _lang[key as string];
57 | if (!_options) {
58 | return value;
59 | }
60 | return Object.entries(_options).reduce((acc, [prop, value]) => {
61 | const regex = new RegExp(`\\{\\{\\s*${prop}\\s*\\}\\}`, 'g');
62 | return acc.replace(regex, value);
63 | }, value);
64 | };
65 |
66 | return translation;
67 | };
68 |
69 | return useTranslation;
70 | };
71 |
72 | export default createTranslation;
73 |
--------------------------------------------------------------------------------
/src/utils/functions/createTranslation/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from './createTranslation';
2 |
--------------------------------------------------------------------------------
/src/utils/functions/createTranslation/types.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line
2 | type TrimLeft = T extends '' ? T : (T extends ` ${infer U}` ? TrimLeft : T);
3 |
4 | type Trim = T extends '' ? T : (TrimLeft extends `${infer U} ` ? Trim : TrimLeft);
5 |
6 | type OptionKeys =
7 | T extends `${any}{{${infer U}}}${infer C}`
8 | ? Trim | OptionKeys
9 | : never;
10 |
11 | type PathImpl =
12 | K extends string
13 | ? T[K] extends Record
14 | ? T[K] extends ArrayLike
15 | ? `${K}.${PathImpl>}`
16 | : `${K}.${PathImpl}`
17 | : K
18 | : never;
19 |
20 | type Path = PathImpl;
21 |
22 | type PathValue> =
23 | P extends `${infer K}.${infer Rest}`
24 | ? K extends keyof T
25 | ? Rest extends Path
26 | ? PathValue
27 | : never
28 | : never
29 | : P extends keyof T
30 | ? T[P]
31 | : never;
32 |
33 | export interface I18n> {
34 | translation>(key: K, options?: OptionKeys> extends never ? undefined : { [P in OptionKeys>]: string }): string;
35 | }
36 |
37 | export type TransitionDefault = Record>;
38 |
--------------------------------------------------------------------------------
/src/utils/functions/fetchAPI/index.ts:
--------------------------------------------------------------------------------
1 | import configureApp from 'configureApp.json';
2 | import qs from 'qs';
3 | import { CANCEL } from 'redux-saga';
4 | import ConfigureAxios from './ConfigureAxios';
5 |
6 | interface RefreshTokenResponseData {
7 | data: {
8 | accessToken: string;
9 | };
10 | }
11 |
12 | interface AxiosData {
13 | refreshToken: string;
14 | accessToken: string;
15 | }
16 |
17 | const axiosConfig = new ConfigureAxios({
18 | configure: {
19 | method: 'GET',
20 | baseURL: configureApp.baseUrl,
21 | timeout: configureApp.timeout,
22 | paramsSerializer: qs.stringify,
23 | },
24 | setAccessToken() {
25 | return '';
26 | },
27 | setRefreshToken() {
28 | return '';
29 | },
30 | });
31 |
32 | const fetchAPI = axiosConfig.create(CANCEL);
33 |
34 | axiosConfig.accessToken({
35 | setCondition(config) {
36 | const isAppURL = config?.url?.search(/^http/g) === -1;
37 | return isAppURL;
38 | },
39 | });
40 |
41 | axiosConfig.refreshToken({
42 | url: 'Endpoint Renew Token',
43 | setRefreshCondition(error) {
44 | return error.response?.status === 401 && !error.config.url?.includes('Endpoint Renew Token');
45 | },
46 | axiosData(refreshToken, accessToken) {
47 | return {
48 | refreshToken,
49 | accessToken,
50 | };
51 | },
52 | success(res, originalRequest) {
53 | // store.dispatch(
54 | // updateToken({
55 | // accessToken: res.data.data.accessToken,
56 | // }),
57 | // );
58 | originalRequest.headers.Authorization = `Bearer ${res.data.data.accessToken}`;
59 | },
60 | failure(error) {
61 | console.log(error.response);
62 | // store.dispatch(logout());
63 | },
64 | });
65 |
66 | export default fetchAPI;
67 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.paths.json",
3 | "compilerOptions": {
4 | "target": "es5",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "allowJs": true,
11 | "skipLibCheck": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "module": "esnext",
15 | "moduleResolution": "node",
16 | "resolveJsonModule": true,
17 | "noEmit": true,
18 | "jsx": "react-jsx",
19 | "sourceMap": false,
20 | "esModuleInterop": true,
21 | "forceConsistentCasingInFileNames": true,
22 | "isolatedModules": true,
23 | "noFallthroughCasesInSwitch": true,
24 | "baseUrl": "./src"
25 | },
26 | "exclude": [
27 | "node_modules",
28 | "build",
29 | "**/*.js"
30 | ],
31 | "include": [
32 | "src"
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/tsconfig.paths.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "plugins": [
4 | { "name": "typescript-plugin-css-modules" },
5 | {
6 | "name": "typescript-styled-plugin",
7 | "lint": {
8 | "validProperties": ["debug"]
9 | }
10 | }
11 | ],
12 | "allowSyntheticDefaultImports": true,
13 | "experimentalDecorators": true,
14 | "isolatedModules": false,
15 | "jsx": "react-jsx",
16 | "baseUrl": "./src",
17 | "paths": {
18 | "*": ["./@types/*", "*"]
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------