├── .editorconfig
├── .eslintrc.json
├── .gitignore
├── .npmignore
├── .storybook
├── main.js
├── preview-head.html
└── webpack.config.js
├── .travis.yml
├── LICENSE
├── README.md
├── babel.config.js
├── jest.config.js
├── lib
├── index.d.ts
├── index.d.ts.map
├── index.es.d.ts
├── index.es.d.ts.map
├── index.es.js
├── index.es.js.map
├── index.js
└── index.js.map
├── logo
└── logo-primary.svg
├── package.json
├── rollup.config.js
├── src
├── bases
│ ├── config
│ │ ├── border.ts
│ │ ├── cursor.ts
│ │ ├── flex.ts
│ │ ├── grid.ts
│ │ ├── gridItem.ts
│ │ ├── layout.ts
│ │ ├── position.ts
│ │ ├── shadow.ts
│ │ ├── space.ts
│ │ ├── typography.ts
│ │ └── visuals.ts
│ ├── configs.ts
│ ├── index.ts
│ └── types.ts
├── components
│ ├── AbsoluteBox
│ │ ├── InsetBox.tsx
│ │ ├── OutsetBox.tsx
│ │ └── data.ts
│ ├── ActiveBreakpoint.tsx
│ ├── AspectRatio.tsx
│ ├── Box.tsx
│ ├── Button
│ │ ├── index.tsx
│ │ └── theme.ts
│ ├── Card
│ │ ├── index.tsx
│ │ └── theme.ts
│ ├── CloseControl
│ │ ├── index.tsx
│ │ └── theme.ts
│ ├── Collapse.tsx
│ ├── Complement.tsx
│ ├── Container
│ │ ├── index.tsx
│ │ └── theme.ts
│ ├── Flex.ts
│ ├── Grid
│ │ ├── Cell.tsx
│ │ ├── data.tsx
│ │ └── index.tsx
│ ├── Icon.tsx
│ ├── Img.tsx
│ ├── Link
│ │ ├── index.tsx
│ │ └── theme.ts
│ ├── List
│ │ ├── ListItem.tsx
│ │ ├── index.tsx
│ │ └── theme.ts
│ ├── LoremIpsum.tsx
│ ├── Modal
│ │ ├── ModalProvider.tsx
│ │ ├── data.ts
│ │ ├── index.tsx
│ │ └── theme.ts
│ ├── SimpleGrid.tsx
│ ├── Spinner.tsx
│ ├── Stack.tsx
│ ├── Tag
│ │ ├── index.tsx
│ │ └── theme.ts
│ ├── Text
│ │ ├── aliases.tsx
│ │ ├── index.tsx
│ │ └── theme.ts
│ ├── Tooltip
│ │ ├── index.tsx
│ │ └── theme.ts
│ ├── Typography
│ │ ├── aliases.tsx
│ │ ├── index.tsx
│ │ └── theme.ts
│ ├── XcoreGlobal
│ │ ├── index.tsx
│ │ └── theme.ts
│ └── XcoreProvider
│ │ └── index.tsx
├── hooks
│ └── useDisclosure.ts
├── icons
│ └── close.tsx
├── index.ts
├── scales
│ ├── breakpoints.ts
│ ├── colors.ts
│ ├── fonts.ts
│ ├── index.ts
│ └── utils.ts
├── theme.ts
├── useTheme.ts
└── utils
│ ├── baseStyle.ts
│ ├── gridTemplate.ts
│ ├── isEmptyValue.ts
│ ├── isIE.ts
│ ├── mediaQuery.ts
│ ├── merge.ts
│ ├── mergeThemes.ts
│ ├── renderComponent.ts
│ ├── transform.ts
│ ├── useMerge.ts
│ ├── variant.ts
│ └── withConfig.ts
├── stories
├── AspectRatio.stories.tsx
├── Box.stories.tsx
├── Button
│ └── index.stories.tsx
├── Card
│ └── index.stories.tsx
├── CloseControl
│ └── index.stories.tsx
├── Collapse.stories.tsx
├── Container.stories.tsx
├── DarkTheme
│ └── index.stories.tsx
├── Flex.stories.tsx
├── Global
│ └── index.stories.tsx
├── Grid
│ └── index.stories.tsx
├── Img.stories.tsx
├── InsetBox
│ └── index.stories.tsx
├── Link.stories.tsx
├── List.stories.tsx
├── Modal
│ └── index.stories.tsx
├── OutsetBox
│ └── index.stories.tsx
├── SimpleGrid
│ └── index.stories.tsx
├── Stack
│ └── index.stories.tsx
├── Tag.stories.tsx
├── Text
│ └── Text.stories.tsx
├── Theme
│ ├── Breakpoints.stories.tsx
│ └── index.stories.tsx
├── Tooltip
│ └── index.stories.tsx
└── Typography
│ └── index.stories.tsx
├── tests
├── components
│ ├── __snapshots__
│ │ ├── aspectRatio.test.tsx.snap
│ │ ├── box.test.tsx.snap
│ │ ├── button.test.tsx.snap
│ │ ├── card.test.tsx.snap
│ │ ├── closeControl.test.tsx.snap
│ │ ├── container.test.tsx.snap
│ │ ├── grid.test.tsx.snap
│ │ ├── link.test.tsx.snap
│ │ ├── list.test.tsx.snap
│ │ ├── scales.test.tsx.snap
│ │ ├── simpleGrid.test.tsx.snap
│ │ ├── spinner.test.tsx.snap
│ │ ├── stack.test.tsx.snap
│ │ ├── tag.test.tsx.snap
│ │ ├── text.test.tsx.snap
│ │ ├── typography.test.tsx.snap
│ │ └── xcoreProvider.test.tsx.snap
│ ├── aspectRatio.test.tsx
│ ├── box.test.tsx
│ ├── button.test.tsx
│ ├── card.test.tsx
│ ├── closeControl.test.tsx
│ ├── container.test.tsx
│ ├── grid.test.tsx
│ ├── link.test.tsx
│ ├── list.test.tsx
│ ├── scales.test.tsx
│ ├── simpleGrid.test.tsx
│ ├── spinner.test.tsx
│ ├── stack.test.tsx
│ ├── tag.test.tsx
│ ├── text.test.tsx
│ ├── typography.test.tsx
│ └── xcoreProvider.test.tsx
└── utils
│ ├── defaultsTheme.test.ts
│ ├── merge.test.ts
│ └── variant.test.ts
├── tsconfig.build.json
├── tsconfig.eslint.json
├── tsconfig.json
├── types
├── lib.es5.d.ts
├── styled-system__core.d.ts
└── styled.d.ts
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_size = 2
5 | indent_style = space
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
3 | coverage
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .storybook
2 | coverage
3 | # lib
4 | logo
5 | # node_modules
6 | src
7 | stories
8 | tests
9 |
10 | .babelrc
11 | .editorconfig
12 | .eslintrc.json
13 | .gitignore
14 | .travis.yml
15 | jest.config.json
16 | # LICENSE
17 | # package.json
18 | # README.md
19 | rollup.config.json
20 | tsconfig.build.json
21 | tsconfig.eslint.json
22 | tsconfig.json
23 | yarn.lock
24 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | stories: ['../stories/**/*.stories.tsx'],
3 | addons: [
4 | '@storybook/react',
5 | ],
6 | };
7 |
--------------------------------------------------------------------------------
/.storybook/preview-head.html:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = ({ config }) => {
4 | config.resolve.modules.unshift(path.resolve(__dirname, '../src'));
5 | config.resolve.alias = {
6 | ...config.resolve.alias,
7 | "@xcorejs/ui": path.resolve(__dirname, '../src')
8 | };
9 |
10 | config.module.rules.push({
11 | test: /\.(ts|tsx)$/,
12 | use: [
13 | {
14 | loader: require.resolve('ts-loader')
15 | }
16 | ]
17 | });
18 | config.resolve.extensions.push('.ts', '.tsx');
19 | return config;
20 | };
21 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - stable
4 | cache: yarn
5 | install:
6 | - yarn
7 | jobs:
8 | include:
9 | - stage: Build and test
10 | script:
11 | - yarn ci
12 | - stage: Release package
13 | if: branch = master
14 | script: yarn build
15 | deploy:
16 | edge: true
17 | provider: npm
18 | email: alfonz@homolik.cz
19 | api_key:
20 | secure: CRkReRFQKZo0tn+fGMhFc+Ltm7d/irRPLW64jNi7Z/1oJ1Qtmpya/ItjYlVZ0xssdJUyMiARYpX66NuRGEJ+Qh94j470JUqxzmDTdsZqWqdaGs5S/Mt0CcZQAmGC2zQ5cxm9TOK/n1z2F+U0y1HNpqmCUiQUGzEPEEk+8CwOAJ+8Z9j1AHRQFlH3dkvbh4eqJqj2fpnsWrOlP/Pgr7pBJB/Mu2bckG1Y3Scb+8LPEwdzbtQfsGWZVQapZNXY4zzqb1Gv+MCDprWbySd47VXmR2ZW9v6gZ2rqzvPQyxq70CiTcRhdotLzc0J1icW1HgI9lRL4WncTu2pCq5wQdDVysBg09/TfDlUSoOFFGjIvZUs9UsEDt0wSr7unN/Gni2ud17Rq8cQYzOa3p2qf7pmix1yS3EJGtHFrz3BLeRKg/D6N36pmRWLwqdn6rlK4+wvnbW7Jl76LwBiXg9d45Ftx3HkAMYB2HK1j5G+iEovEFCl4ZARA+2y4Xg6cAOmrrEv5uFK5j5b4w5vUl4DTEEZRjua4TAcwqubja+aNzOykKAf4+hlpZSPGjABoGB18sFpW+b/m3NdRNtUVR2Ka4bMAYeIU90Zi/WSYOqNJCBkfJl1WHZmKna079kr/iFIQQH783OeYmnS4uIGIylguA5qc/90oju/QAgtLRcT/oANJpcg=
21 | skip_cleanup: true
22 |
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Xcore
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Xcore UI
4 | =======
5 |
6 | Flexible, themeable React component library for applications with advanced UI design requirements which is suitable for creating tailored design systems.
7 |
8 | 🌪 Unlimited customization and theming
9 | 📱 Fully responsive props and breakpoint aliases support
10 | 🔌️ Superpowered by [Styled system](https://styled-system.com/) and its paradigm
11 | 💅️ Based on [styled components](https://styled-components.com)
12 | ⚡️ Inspired by [Chakra UI](https://chakra-ui.com) + [Theme UI](https://theme-ui.com/)
13 |
14 | ## Future development
15 | Xcore UI development is currently closed and will continue in a bug-fix mode only.
16 | Next generation component library which will take place of Xcore UI is Anolis UI, currently in development.
17 |
18 | See [Anolis UI on GitHub](https://github.com/anolis-ui/anolis-ui)
19 |
20 | ## Documentation
21 | Not yet.
22 | We published simple layout components and are working very hard to release prototypes of more complex components.
23 | See our [roadmap](https://github.com/xcorejs/ui/wiki/Roadmap) and stay tuned.
24 |
25 | ## Install
26 | Use `yarn` or `npm` and install `@xcorejs/ui` and `styled-components` to your React project:
27 |
28 | `
29 | $ yarn add @xcorejs/ui styled-components
30 | `
31 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@babel/preset-env',
4 | '@babel/preset-react',
5 | '@babel/preset-typescript'
6 | ],
7 | plugins: [
8 | '@babel/plugin-syntax-nullish-coalescing-operator'
9 | ]
10 | };
11 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testEnvironment: 'jsdom',
3 | moduleNameMapper: {
4 | '@xcorejs/ui': '/src'
5 | },
6 | moduleDirectories: ['node_modules', 'src']
7 | };
8 |
--------------------------------------------------------------------------------
/lib/index.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts","../src/utils/baseStyle.ts","../src/scales/utils.ts","../src/scales/colors.ts","../src/bases/config/border.ts","../src/bases/config/cursor.ts","../src/bases/config/flex.ts","../src/bases/config/gridItem.ts","../src/bases/config/layout.ts","../src/bases/config/position.ts","../src/bases/config/shadow.ts","../src/bases/config/space.ts","../src/bases/config/typography.ts","../src/bases/config/visuals.ts","../src/bases/configs.ts","../src/bases/types.ts","../src/bases/index.ts","../src/utils/renderComponent.ts","../src/bases/config/grid.ts","../src/utils/withConfig.ts","../src/components/Icon.tsx","../src/components/Complement.tsx","../src/components/Spinner.tsx","../src/useTheme.ts","../src/utils/merge.ts","../src/utils/useMerge.ts","../src/utils/variant.ts","../src/components/Button/index.tsx","../src/utils/mergeThemes.ts","../src/components/Button/theme.ts","../src/components/Box.tsx","../src/components/Flex.ts","../src/components/Tag/theme.ts","../src/components/Tag/index.tsx","../src/components/Text/theme.ts","../src/components/Text/index.tsx","../src/components/Card/index.tsx","../src/components/Card/theme.ts","../src/components/Container/theme.ts","../src/components/Link/index.tsx","../src/components/Link/theme.ts","../src/components/List/index.tsx","../src/components/List/theme.ts","../src/components/Typography/theme.ts","../src/components/XcoreGlobal/theme.ts","../src/scales/breakpoints.ts","../src/scales/fonts.ts","../src/scales/index.ts","../src/icons/close.tsx","../src/components/CloseControl/index.tsx","../src/components/CloseControl/theme.ts","../src/utils/transform.ts","../src/components/AbsoluteBox/data.ts","../src/components/AbsoluteBox/InsetBox.tsx","../src/components/Typography/index.tsx","../src/components/Modal/data.ts","../src/components/Modal/index.tsx","../src/components/Modal/theme.ts","../src/theme.ts","../src/components/ActiveBreakpoint.tsx","../src/components/AspectRatio.tsx","../src/components/Collapse.tsx","../src/components/Img.tsx","../src/components/LoremIpsum.tsx","../src/utils/gridTemplate.ts","../src/utils/isIE.ts","../src/utils/mediaQuery.ts","../src/components/Grid/index.tsx","../src/components/SimpleGrid.tsx","../src/components/Stack.tsx","../src/components/Grid/data.tsx","../src/components/Grid/Cell.tsx","../src/components/AbsoluteBox/OutsetBox.tsx","../src/components/Container/index.tsx","../src/components/List/ListItem.tsx","../src/components/Modal/ModalProvider.tsx","../src/components/Text/aliases.tsx","../src/components/Typography/aliases.tsx","../src/components/XcoreGlobal/index.tsx","../src/components/XcoreProvider/index.tsx","../src/hooks/useDisclosure.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mDACqC,MAAO;aACvC,IAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AADV,OAAO,4mDAA+B,CAAsC"}
--------------------------------------------------------------------------------
/lib/index.es.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.es.d.ts","sourceRoot":"","sources":["../src/index.ts","../src/utils/baseStyle.ts","../src/scales/utils.ts","../src/scales/colors.ts","../src/bases/config/border.ts","../src/bases/config/cursor.ts","../src/bases/config/flex.ts","../src/bases/config/gridItem.ts","../src/bases/config/layout.ts","../src/bases/config/position.ts","../src/bases/config/shadow.ts","../src/bases/config/space.ts","../src/bases/config/typography.ts","../src/bases/config/visuals.ts","../src/bases/configs.ts","../src/bases/types.ts","../src/bases/index.ts","../src/utils/renderComponent.ts","../src/bases/config/grid.ts","../src/utils/withConfig.ts","../src/components/Icon.tsx","../src/components/Complement.tsx","../src/components/Spinner.tsx","../src/useTheme.ts","../src/utils/merge.ts","../src/utils/useMerge.ts","../src/utils/variant.ts","../src/components/Button/index.tsx","../src/utils/mergeThemes.ts","../src/components/Button/theme.ts","../src/components/Box.tsx","../src/components/Flex.ts","../src/components/Tag/theme.ts","../src/components/Tag/index.tsx","../src/components/Text/theme.ts","../src/components/Text/index.tsx","../src/components/Card/index.tsx","../src/components/Card/theme.ts","../src/components/Container/theme.ts","../src/components/Link/index.tsx","../src/components/Link/theme.ts","../src/components/List/index.tsx","../src/components/List/theme.ts","../src/components/Typography/theme.ts","../src/components/XcoreGlobal/theme.ts","../src/scales/breakpoints.ts","../src/scales/fonts.ts","../src/scales/index.ts","../src/icons/close.tsx","../src/components/CloseControl/index.tsx","../src/components/CloseControl/theme.ts","../src/utils/transform.ts","../src/components/AbsoluteBox/data.ts","../src/components/AbsoluteBox/InsetBox.tsx","../src/components/Typography/index.tsx","../src/components/Modal/data.ts","../src/components/Modal/index.tsx","../src/components/Modal/theme.ts","../src/theme.ts","../src/components/ActiveBreakpoint.tsx","../src/components/AspectRatio.tsx","../src/components/Collapse.tsx","../src/components/Img.tsx","../src/components/LoremIpsum.tsx","../src/utils/gridTemplate.ts","../src/utils/isIE.ts","../src/utils/mediaQuery.ts","../src/components/Grid/index.tsx","../src/components/SimpleGrid.tsx","../src/components/Stack.tsx","../src/components/Grid/data.tsx","../src/components/Grid/Cell.tsx","../src/components/AbsoluteBox/OutsetBox.tsx","../src/components/Container/index.tsx","../src/components/List/ListItem.tsx","../src/components/Modal/ModalProvider.tsx","../src/components/Text/aliases.tsx","../src/components/Typography/aliases.tsx","../src/components/XcoreGlobal/index.tsx","../src/components/XcoreProvider/index.tsx","../src/hooks/useDisclosure.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mDACqC,MAAO;aACvC,IAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AADV,OAAO,4mDAA+B,CAAsC"}
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@xcorejs/ui",
3 | "version": "0.8.0",
4 | "description": "Flexible, themeable React component library for applications with advanced UI design requirements which is suitable for creating tailored design systems.",
5 | "main": "lib/index.js",
6 | "module": "lib/index.es.js",
7 | "jsnext:main": "lib/index.es.js",
8 | "types": "lib/index.d.ts",
9 | "repository": "https://github.com/xcorejs/ui",
10 | "license": "MIT",
11 | "contributors": [
12 | {
13 | "name": "Denis Homolik",
14 | "email": "denis@homolik.cz",
15 | "url": "http://homolik.cz"
16 | },
17 | {
18 | "name": "Pavel Vondrasek",
19 | "email": "pavel@appio.cz"
20 | },
21 | {
22 | "name": "Tomas Nyvlt",
23 | "email": "tomas@nyvlt.xyz"
24 | },
25 | {
26 | "name": "Martin Skara",
27 | "email": "jsem@skaramart.in"
28 | }
29 | ],
30 | "scripts": {
31 | "storybook": "start-storybook -p 3001",
32 | "storybook-ci": "start-storybook --ci --smoke-test",
33 | "build": "rollup -c",
34 | "ts-check": "yarn tsc --noEmit",
35 | "lint": "eslint \"{src,stories}/**/*.{ts,tsx}\"",
36 | "ci": "yarn lint && yarn build && yarn test && yarn storybook-ci",
37 | "test": "jest"
38 | },
39 | "dependencies": {
40 | "@reach/portal": "^0.8.5",
41 | "@styled-system/core": "^5.1.2",
42 | "lorem-ipsum": "^2.0.3",
43 | "polished": "^3.4.4",
44 | "react-animate-height": "^2.0.20",
45 | "use-debounce": "^3.3.0"
46 | },
47 | "peerDependencies": {
48 | "react": ">= 16.8.0",
49 | "styled-components": ">=4"
50 | },
51 | "devDependencies": {
52 | "@babel/core": "^7.9.0",
53 | "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
54 | "@babel/preset-env": "^7.9.0",
55 | "@babel/preset-react": "^7.9.4",
56 | "@babel/preset-typescript": "^7.9.0",
57 | "@rollup/plugin-typescript": "^4.0.0",
58 | "@storybook/addon-storyshots": "^5.3.18",
59 | "@storybook/react": "^5.2.8",
60 | "@types/jest": "^24.9.0",
61 | "@types/react": "^16.9.19",
62 | "@types/react-test-renderer": "^16.9.2",
63 | "@types/styled-components": "^5.0.1",
64 | "@types/styled-system": "^5.1.9",
65 | "@types/uuid": "^7.0.0",
66 | "@typescript-eslint/eslint-plugin": "^2.19.0",
67 | "@typescript-eslint/parser": "^2.19.0",
68 | "@wessberg/rollup-plugin-ts": "^1.2.24",
69 | "babel-jest": "^25.3.0",
70 | "babel-loader": "^8.1.0",
71 | "csstype": "^2.6.8",
72 | "eslint": "^6.8.0",
73 | "eslint-config-alfonz": "0.1.0",
74 | "eslint-config-standard": "^14.1.0",
75 | "eslint-plugin-import": "^2.20.1",
76 | "eslint-plugin-jsx-a11y": "^6.2.3",
77 | "eslint-plugin-node": "^11.0.0",
78 | "eslint-plugin-promise": "^4.2.1",
79 | "eslint-plugin-react": "^7.18.3",
80 | "eslint-plugin-standard": "^4.0.1",
81 | "jest": "^24.9.0",
82 | "jest-styled-components": "^7.0.2",
83 | "react": "^16.12.0",
84 | "react-dom": "^16.13.0",
85 | "react-is": "^16.13.1",
86 | "react-test-renderer": "^16.13.1",
87 | "rollup": "^1.20.0",
88 | "styled-components": "^5.0.1",
89 | "ts-jest": "^24.3.0",
90 | "ts-loader": "^6.2.1",
91 | "ts-node": "^8.6.2",
92 | "tslib": "^1.11.1",
93 | "typescript": "^3.7.2",
94 | "webpack": "^4.42.1"
95 | },
96 | "keywords": [
97 | "react",
98 | "typescript",
99 | "design-system",
100 | "css-in-js",
101 | "styled-components"
102 | ]
103 | }
104 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import ts from '@wessberg/rollup-plugin-ts';
2 |
3 | import pkg, { dependencies, peerDependencies } from './package.json';
4 |
5 | // Get all packages we are using
6 | const deps = Object.keys({
7 | ...dependencies,
8 | ...peerDependencies
9 | });
10 |
11 | export default {
12 | input: 'src/index.ts',
13 | external: id => deps.some(d => d === id || d.startsWith(d + '/')),
14 | plugins: [
15 | ts({
16 | transpiler: 'babel',
17 | tsconfig: 'tsconfig.build.json'
18 | })
19 | ],
20 | output: [
21 | {
22 | file: pkg.module,
23 | format: 'esm',
24 | sourcemap: true
25 | },
26 | {
27 | file: pkg.main,
28 | format: 'cjs',
29 | sourcemap: true
30 | }
31 | ]
32 | };
33 |
--------------------------------------------------------------------------------
/src/bases/config/border.ts:
--------------------------------------------------------------------------------
1 | import { Config } from '@styled-system/core';
2 |
3 | export const borderConfig: Config = {
4 | // Border
5 | border: {
6 | property: 'border',
7 | scale: 'borders'
8 | },
9 | borderWidth: {
10 | property: 'borderWidth',
11 | scale: 'borderWidths'
12 | },
13 | borderStyle: {
14 | property: 'borderStyle',
15 | scale: 'borderStyles'
16 | },
17 | borderColor: {
18 | property: 'borderColor',
19 | scale: 'colors'
20 | },
21 | borderRadius: {
22 | property: 'borderRadius',
23 | scale: 'radii'
24 | },
25 | // Border rop
26 | borderTop: {
27 | property: 'borderTop',
28 | scale: 'borders'
29 | },
30 | borderTopWidth: {
31 | property: 'borderTopWidth',
32 | scale: 'borderWidths'
33 | },
34 | borderTopColor: {
35 | property: 'borderTopColor',
36 | scale: 'colors'
37 | },
38 | borderTopStyle: {
39 | property: 'borderTopStyle',
40 | scale: 'borderStyles'
41 | },
42 | borderTopLeftRadius: {
43 | property: 'borderTopLeftRadius',
44 | scale: 'radii'
45 | },
46 | borderTopRightRadius: {
47 | property: 'borderTopRightRadius',
48 | scale: 'radii'
49 | },
50 | // Border bottom
51 | borderBottom: {
52 | property: 'borderBottom',
53 | scale: 'borders'
54 | },
55 | borderBottomLeftRadius: {
56 | property: 'borderBottomLeftRadius',
57 | scale: 'radii'
58 | },
59 | borderBottomRightRadius: {
60 | property: 'borderBottomRightRadius',
61 | scale: 'radii'
62 | },
63 | borderBottomWidth: {
64 | property: 'borderBottomWidth',
65 | scale: 'borderWidths'
66 | },
67 | borderBottomColor: {
68 | property: 'borderBottomColor',
69 | scale: 'colors'
70 | },
71 | borderBottomStyle: {
72 | property: 'borderBottomStyle',
73 | scale: 'borderStyles'
74 | },
75 | // Border right
76 | borderRight: {
77 | property: 'borderRight',
78 | scale: 'borders'
79 | },
80 | borderRightWidth: {
81 | property: 'borderRightWidth',
82 | scale: 'borderWidths'
83 | },
84 | borderRightColor: {
85 | property: 'borderRightColor',
86 | scale: 'colors'
87 | },
88 | borderRightStyle: {
89 | property: 'borderRightStyle',
90 | scale: 'borderStyles'
91 | },
92 | // Border left
93 | borderLeft: {
94 | property: 'borderLeft',
95 | scale: 'borders'
96 | },
97 | borderLeftWidth: {
98 | property: 'borderLeftWidth',
99 | scale: 'borderWidths'
100 | },
101 | borderLeftColor: {
102 | property: 'borderLeftColor',
103 | scale: 'colors'
104 | },
105 | borderLeftStyle: {
106 | property: 'borderLeftStyle',
107 | scale: 'borderStyles'
108 | },
109 | // Border compined
110 | borderX: {
111 | properties: ['borderLeft', 'borderRight'],
112 | scale: 'borders'
113 | },
114 | borderY: {
115 | properties: ['borderTop', 'borderBottom'],
116 | scale: 'borders'
117 | }
118 | };
119 |
--------------------------------------------------------------------------------
/src/bases/config/cursor.ts:
--------------------------------------------------------------------------------
1 | import { Config } from '@styled-system/core';
2 |
3 | export const cursorConfig: Config = {
4 | cursor: true,
5 | userSelect: true,
6 | pointerEvents: true
7 | };
8 |
--------------------------------------------------------------------------------
/src/bases/config/flex.ts:
--------------------------------------------------------------------------------
1 | import { Config } from '@styled-system/core';
2 |
3 | export const flexConfig: Config = {
4 | alignItems: true,
5 | alignContent: true,
6 | justifyItems: true,
7 | justifyContent: true,
8 | flexWrap: true,
9 | flexDirection: true,
10 | // item
11 | flex: true,
12 | flexGrow: true,
13 | flexShrink: true,
14 | flexBasis: true,
15 | justifySelf: true,
16 | alignSelf: true,
17 | order: true
18 | };
19 |
--------------------------------------------------------------------------------
/src/bases/config/grid.ts:
--------------------------------------------------------------------------------
1 | import { Config } from '@styled-system/core';
2 |
3 | const defaultScale = [0, 4, 8, 16, 32, 64, 128, 256, 512];
4 |
5 | export const gridConfig: Config = {
6 | gridGap: {
7 | property: 'gridGap',
8 | scale: 'space',
9 | defaultScale
10 | },
11 | gridColumnGap: {
12 | property: 'gridColumnGap',
13 | scale: 'space',
14 | defaultScale
15 | },
16 | gridRowGap: {
17 | property: 'gridRowGap',
18 | scale: 'space',
19 | defaultScale
20 | },
21 | gridColumn: true,
22 | gridRow: true,
23 | gridAutoFlow: true,
24 | gridAutoColumns: true,
25 | gridAutoRows: true,
26 | gridTemplateColumns: true,
27 | gridTemplateRows: true,
28 | gridTemplateAreas: true,
29 | gridArea: true,
30 |
31 | justifyItems: true,
32 | justifyContent: true,
33 | alignContent: true,
34 | alignItems: true,
35 | columns: {
36 | property: 'gridTemplateColumns'
37 | },
38 | rows: {
39 | property: 'gridTemplateRows'
40 | },
41 | gap: {
42 | property: 'gridGap'
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/src/bases/config/gridItem.ts:
--------------------------------------------------------------------------------
1 | import { Config } from '@styled-system/core';
2 |
3 | export const gridItemConfig: Config = {
4 | gridColumn: true,
5 | gridColumnStart: true,
6 | gridColumnEnd: true,
7 | column: {
8 | property: 'gridColumn'
9 | },
10 | gridRow: true,
11 | gridRowStart: true,
12 | gridRowEnd: true,
13 | row: {
14 | property: 'gridRow'
15 | },
16 | gridArea: true,
17 | area: {
18 | property: 'gridArea'
19 | },
20 |
21 | justifySelf: true,
22 | alignSelf: true,
23 | placeSelf: true
24 | };
25 |
--------------------------------------------------------------------------------
/src/bases/config/layout.ts:
--------------------------------------------------------------------------------
1 | import { get, Config, Scale } from '@styled-system/core';
2 |
3 | const isNumber = (n: any) => typeof n === 'number' && !isNaN(n);
4 | const getWidth = (n: any, scale?: Scale) =>
5 | get(scale, n, !isNumber(n) || n > 1 ? n : n * 100 + '%');
6 |
7 | export const layoutConfig: Config = {
8 | width: {
9 | property: 'width',
10 | scale: 'sizes',
11 | transform: getWidth
12 | },
13 | height: {
14 | property: 'height',
15 | scale: 'sizes'
16 | },
17 | minWidth: {
18 | property: 'minWidth',
19 | scale: 'sizes'
20 | },
21 | minHeight: {
22 | property: 'minHeight',
23 | scale: 'sizes'
24 | },
25 | maxWidth: {
26 | property: 'maxWidth',
27 | scale: 'sizes'
28 | },
29 | maxHeight: {
30 | property: 'maxHeight',
31 | scale: 'sizes'
32 | },
33 | overflow: true,
34 | overflowX: true,
35 | overflowY: true,
36 | display: true,
37 | verticalAlign: true
38 | };
39 |
--------------------------------------------------------------------------------
/src/bases/config/position.ts:
--------------------------------------------------------------------------------
1 | import { Config } from '@styled-system/core';
2 |
3 | const defaultScale = [0, 4, 8, 16, 32, 64, 128, 256, 512];
4 |
5 | export const positionConfig: Config = {
6 | position: true,
7 | zIndex: {
8 | property: 'zIndex',
9 | scale: 'zIndices'
10 | },
11 | top: {
12 | property: 'top',
13 | scale: 'space',
14 | defaultScale
15 | },
16 | right: {
17 | property: 'right',
18 | scale: 'space',
19 | defaultScale
20 | },
21 | bottom: {
22 | property: 'bottom',
23 | scale: 'space',
24 | defaultScale
25 | },
26 | left: {
27 | property: 'left',
28 | scale: 'space',
29 | defaultScale
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/src/bases/config/shadow.ts:
--------------------------------------------------------------------------------
1 | import { Config } from '@styled-system/core';
2 |
3 | export const shadowConfig: Config = {
4 | boxShadow: {
5 | property: 'boxShadow',
6 | scale: 'shadows'
7 | },
8 | textShadow: {
9 | property: 'textShadow',
10 | scale: 'shadows'
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/src/bases/config/space.ts:
--------------------------------------------------------------------------------
1 | import { get, Config, ConfigStyle, Scale } from '@styled-system/core';
2 | import CSS from 'csstype';
3 |
4 | const defaultScale = [0, 4, 8, 16, 32, 64, 128, 256, 512];
5 |
6 | const isNumber = (n: any): n is number => typeof n === 'number' && !isNaN(n);
7 |
8 | const getMargin = (n: any, scale?: Scale) => {
9 | if (!isNumber(n)) {
10 | return get(scale, n, n);
11 | }
12 |
13 | const isNegative = n < 0;
14 | const absolute = Math.abs(n);
15 | const value = get(scale, absolute, absolute);
16 | if (!isNumber(value)) {
17 | return isNegative ? '-' + value : value;
18 | }
19 | return value * (isNegative ? -1 : 1);
20 | };
21 |
22 | const getMarginConfig = (property: keyof CSS.Properties): ConfigStyle => ({
23 | property,
24 | scale: 'space',
25 | transform: getMargin,
26 | defaultScale
27 | });
28 |
29 | const getPaddingConfig = (property: keyof CSS.Properties): ConfigStyle => ({
30 | property,
31 | scale: 'space',
32 | defaultScale
33 | });
34 |
35 | export const spaceConfig: Config = {
36 | margin: getMarginConfig('margin'),
37 | marginTop: getMarginConfig('marginTop'),
38 | marginRight: getMarginConfig('marginRight'),
39 | marginBottom: getMarginConfig('marginBottom'),
40 | marginLeft: getMarginConfig('marginLeft'),
41 | marginX: {
42 | properties: ['marginLeft', 'marginRight'],
43 | scale: 'space',
44 | transform: getMargin,
45 | defaultScale
46 | },
47 | marginY: {
48 | properties: ['marginTop', 'marginBottom'],
49 | scale: 'space',
50 | transform: getMargin,
51 | defaultScale
52 | },
53 | // Padd
54 | padding: getPaddingConfig('padding'),
55 | paddingTop: getPaddingConfig('paddingTop'),
56 | paddingRight: getPaddingConfig('paddingRight'),
57 | paddingBottom: getPaddingConfig('paddingBottom'),
58 | paddingLeft: getPaddingConfig('paddingLeft'),
59 | paddingX: {
60 | properties: ['paddingLeft', 'paddingRight'],
61 | scale: 'space',
62 | defaultScale
63 | },
64 | paddingY: {
65 | properties: ['paddingTop', 'paddingBottom'],
66 | scale: 'space',
67 | defaultScale
68 | }
69 | };
70 |
71 | spaceConfig.m = spaceConfig.margin;
72 | spaceConfig.mt = spaceConfig.marginTop;
73 | spaceConfig.mr = spaceConfig.marginRight;
74 | spaceConfig.mb = spaceConfig.marginBottom;
75 | spaceConfig.ml = spaceConfig.marginLeft;
76 | spaceConfig.mx = spaceConfig.marginX;
77 | spaceConfig.my = spaceConfig.marginY;
78 |
79 | spaceConfig.p = spaceConfig.padding;
80 | spaceConfig.pt = spaceConfig.paddingTop;
81 | spaceConfig.pr = spaceConfig.paddingRight;
82 | spaceConfig.pb = spaceConfig.paddingBottom;
83 | spaceConfig.pl = spaceConfig.paddingLeft;
84 | spaceConfig.px = spaceConfig.paddingX;
85 | spaceConfig.py = spaceConfig.paddingY;
86 |
--------------------------------------------------------------------------------
/src/bases/config/typography.ts:
--------------------------------------------------------------------------------
1 | import { Config } from '@styled-system/core';
2 |
3 | const defaultScale = [12, 14, 16, 20, 24, 32, 48, 64, 72];
4 |
5 | export const typographyConfig: Config = {
6 | fontFamily: {
7 | property: 'fontFamily',
8 | scale: 'fonts'
9 | },
10 | fontSize: {
11 | property: 'fontSize',
12 | scale: 'fontSizes',
13 | defaultScale
14 | },
15 | fontWeight: {
16 | property: 'fontWeight',
17 | scale: 'fontWeights'
18 | },
19 | lineHeight: {
20 | property: 'lineHeight',
21 | scale: 'lineHeights'
22 | },
23 | letterSpacing: {
24 | property: 'letterSpacing',
25 | scale: 'letterSpacings'
26 | },
27 | textAlign: true,
28 | fontStyle: true
29 | };
30 |
--------------------------------------------------------------------------------
/src/bases/config/visuals.ts:
--------------------------------------------------------------------------------
1 | import { Config } from '@styled-system/core';
2 | import { colorTransform } from 'scales/colors';
3 |
4 | export const visualsConfig: Config = {
5 | background: {
6 | property: 'background',
7 | scale: 'colors',
8 | transform: colorTransform
9 | },
10 | backgroundImage: true,
11 | backgroundSize: true,
12 | backgroundPosition: true,
13 | backgroundRepeat: true,
14 | color: {
15 | property: 'color',
16 | scale: 'colors',
17 | transform: colorTransform
18 | },
19 | backgroundColor: {
20 | property: 'backgroundColor',
21 | scale: 'colors',
22 | transform: colorTransform
23 | },
24 | opacity: true
25 | };
26 |
27 | visualsConfig.bgImage = visualsConfig.backgroundImage;
28 | visualsConfig.bgSize = visualsConfig.backgroundSize;
29 | visualsConfig.bgPosition = visualsConfig.backgroundPosition;
30 | visualsConfig.bgRepeat = visualsConfig.backgroundRepeat;
31 | visualsConfig.bg = visualsConfig.background;
32 |
--------------------------------------------------------------------------------
/src/bases/configs.ts:
--------------------------------------------------------------------------------
1 | import { system } from '@styled-system/core';
2 | import { colorTransform } from 'scales/colors';
3 |
4 | import { borderConfig } from './config/border';
5 | import { cursorConfig } from './config/cursor';
6 | import { flexConfig } from './config/flex';
7 | import { gridItemConfig } from './config/gridItem';
8 | import { layoutConfig } from './config/layout';
9 | import { positionConfig } from './config/position';
10 | import { shadowConfig } from './config/shadow';
11 | import { spaceConfig } from './config/space';
12 | import { typographyConfig } from './config/typography';
13 | import { visualsConfig } from './config/visuals';
14 |
15 | export const selectionSystem = system({
16 | color: {
17 | property: 'color',
18 | scale: 'colors',
19 | transform: colorTransform
20 | },
21 | backgroundColor: {
22 | property: 'backgroundColor',
23 | scale: 'colors',
24 | transform: colorTransform
25 | },
26 | cursor: true,
27 | caretColor: {
28 | property: 'caretColor',
29 | scale: 'colors',
30 | transform: colorTransform
31 | },
32 | outline: true,
33 | outlineOffset: true,
34 | textDecoration: true,
35 | textEmphasisColor: {
36 | property: 'textEmphasisColor',
37 | scale: 'colors',
38 | transform: colorTransform
39 | },
40 | textShadow: true
41 | });
42 |
43 | export const boxSystem = system({
44 | ...layoutConfig,
45 | ...spaceConfig,
46 | ...typographyConfig,
47 | ...visualsConfig,
48 | ...borderConfig,
49 | ...flexConfig,
50 | ...gridItemConfig,
51 | ...positionConfig,
52 | ...cursorConfig,
53 | ...shadowConfig,
54 |
55 | animation: true,
56 | transition: true,
57 | transform: true,
58 | filter: true,
59 | outline: true,
60 | outlineOffset: true,
61 | WebkitTapHighlightColor: true
62 | });
63 |
64 | export const pseudoBoxSystem = system({
65 | content: true
66 | });
67 |
68 | export const globalSystem = system({
69 | boxSizing: {
70 | property: 'boxSizing'
71 | }
72 | });
73 |
74 | export const flexSystem = system({ display: true });
75 |
76 | export const textSystem = system({
77 | textTransform: true,
78 | WebkitLineClamp: true,
79 | WebkitBoxOrient: true,
80 | textOverflow: true,
81 | whiteSpace: true,
82 | textDecoration: true,
83 | wordBreak: true
84 | });
85 |
86 | export const pathSystem = system({
87 | fill: {
88 | property: 'fill',
89 | scale: 'colors'
90 | }
91 | });
92 |
93 | export const pathHoverSystem = system({
94 | fillHover: {
95 | property: 'fill',
96 | scale: 'colors'
97 | }
98 | });
99 |
--------------------------------------------------------------------------------
/src/bases/index.ts:
--------------------------------------------------------------------------------
1 | import { css, FlattenInterpolation, ThemeProps } from 'styled-components';
2 | import { XcoreTheme } from 'theme';
3 | import { base, compose } from 'utils/baseStyle';
4 |
5 | import {
6 | boxSystem,
7 | flexSystem,
8 | globalSystem,
9 | pseudoBoxSystem,
10 | selectionSystem,
11 | textSystem,
12 | pathSystem,
13 | pathHoverSystem
14 | } from './configs';
15 | import {
16 | BoxBaseProps,
17 | FlexBaseProps,
18 | GlobalBaseProps,
19 | IconBaseProps,
20 | PseudoBoxBaseProps,
21 | SelectionBaseProps,
22 | TextBaseProps
23 | } from './types';
24 |
25 | export * from './types';
26 |
27 | type WithTheme = { theme: XcoreTheme };
28 |
29 | export const selectionBase = (p: SelectionBaseProps & WithTheme) => css`
30 | ${selectionSystem(p)}
31 | `;
32 |
33 | export const boxBase = (p: BoxBaseProps & WithTheme): FlattenInterpolation> => css`
34 | ${boxSystem(p)}
35 |
36 | ${p._icon && css`
37 | & path {
38 | ${pseudoBoxBaseComposed({ ...p._icon, theme: p.theme })}
39 | ${pathSystem({ ...p._icon, theme: p.theme })}
40 | }
41 | `}
42 |
43 | ${p._selection && css`
44 | & *::selection {
45 | ${selectionBase({ ...p._selection, theme: p.theme })}
46 | }
47 | `}
48 |
49 | ${Object.keys(pseudoSelectors).map(k => p[k] && css`
50 | ${pseudoSelectors[k]} {
51 | ${pseudoBoxBaseComposed({ ...p[k], theme: p.theme })}
52 | }
53 | `)}
54 |
55 | ${p._group && Object.keys(groupPseudoSelectors).map(k => p._group![k] && css`
56 | ${groupPseudoSelectors[k]} {
57 | ${pseudoBoxBaseComposed({ ...p._group![k]!, theme: p.theme })}
58 | }
59 | `)}
60 | `;
61 | export const composedBoxBase = compose(boxBase);
62 |
63 | export const pseudoBoxBase = base([boxBase], (p: PseudoBoxBaseProps & WithTheme) => css`
64 | ${pseudoBoxSystem(p)}
65 | `);
66 | const pseudoBoxBaseComposed = compose(pseudoBoxBase);
67 |
68 | export const globalBase = base([boxBase], ({ webkitFontSmoothing, ...p }: GlobalBaseProps & WithTheme) => css`
69 | ${globalSystem(p)}
70 | `);
71 |
72 | export const flexBase = base([boxBase], (p: FlexBaseProps & WithTheme) => css`
73 | ${flexSystem({ display: p.display ?? 'flex' })}
74 | `);
75 |
76 | export const composedFlexBase = compose(flexBase);
77 |
78 | export const iconBase = base([flexBase], (p: IconBaseProps & WithTheme) => css`
79 | flex-shrink: 0;
80 | backface-visibility: hidden;
81 |
82 | svg {
83 | display: block;
84 | max-width: 100%;
85 | max-height: 100%;
86 |
87 | path {
88 | transition: fill 300ms;
89 | ${pathSystem(p)}
90 | }
91 | }
92 |
93 | &:not(:root) {
94 | overflow: hidden;
95 | }
96 |
97 | &:hover {
98 | svg path {
99 | ${pathHoverSystem(p)}
100 | }
101 | }
102 | `);
103 |
104 | export const composedIconBase = compose(iconBase);
105 |
106 | export const textBase = base([boxBase], (p: TextBaseProps & WithTheme) => css`
107 | ${textSystem(p)}
108 | `);
109 |
110 | export const composedTextBase = compose(textBase);
111 |
112 | type GroupPseudoSelector =
113 | | '_hover'
114 | | '_active'
115 | | '_focus';
116 |
117 | type PseudoSelector =
118 | | GroupPseudoSelector
119 | | '_before'
120 | | '_after'
121 | | '_disabled'
122 | | '_groupHover'
123 | | '_placeholder'
124 | | '_focusWithin'
125 | | '_first'
126 | | '_firstOfType'
127 | | '_last';
128 |
129 | const pseudoSelectors: Record = {
130 | _hover: '&:hover',
131 | _active: '&:active',
132 | _focus: '&:focus',
133 | _before: '&::before',
134 | _after: '&::after',
135 | // eslint-disable-next-line max-len
136 | _disabled: '&:disabled, &:disabled:focus, &:disabled:hover, &[aria-disabled=true], &[aria-disabled=true]:focus, &[aria-disabled=true]:hover',
137 | _groupHover: '[role=group]:hover &',
138 | _placeholder: '&::placeholder',
139 | _focusWithin: '&:focus-within',
140 | _first: '&:first-child',
141 | _firstOfType: '&:first-of-type',
142 | _last: '&:last-child'
143 | };
144 |
145 | const groupPseudoSelectors: Record = {
146 | _hover: '[role=group]:hover &',
147 | _active: '[role=group]:active &',
148 | _focus: '[role=group]:focus &'
149 | };
150 |
--------------------------------------------------------------------------------
/src/bases/types.ts:
--------------------------------------------------------------------------------
1 | import CSS from 'csstype';
2 | import { CSSProperties, HTMLAttributes } from 'react';
3 | import { ResponsiveValue } from '@styled-system/core';
4 | import system from 'styled-system';
5 | import { XcoreTheme } from 'theme';
6 |
7 | type TLen = string | 0 | number;
8 |
9 | export type SelectionBaseProps =
10 | {
11 | color?: ResponsiveValue;
12 | cursor?: ResponsiveValue;
13 | caretColor?: ResponsiveValue;
14 | outline?: ResponsiveValue>;
15 | outlineOffset?: ResponsiveValue>;
16 | textDecoration?: ResponsiveValue>;
17 | textEmphasisColor?: ResponsiveValue;
18 |
19 | theme?: XcoreTheme;
20 | }
21 | & system.BackgroundColorProps
22 | & system.TextShadowProps;
23 |
24 | export type BoxBaseProps =
25 | {
26 | _hover?: BoxBaseProps;
27 | _active?: BoxBaseProps;
28 | _focus?: BoxBaseProps;
29 | _before?: PseudoBoxBaseProps;
30 | _after?: PseudoBoxBaseProps;
31 | _disabled?: BoxBaseProps;
32 | _groupHover?: BoxBaseProps;
33 | _groupHoverIcon?: IconBaseProps;
34 | _placeholder?: BoxBaseProps;
35 | _selection?: SelectionBaseProps;
36 | _focusWithin?: BoxBaseProps;
37 | _first?: BoxBaseProps;
38 | _firstOfType?: BoxBaseProps;
39 | _last?: BoxBaseProps;
40 |
41 | _group?: {
42 | _hover?: BoxBaseProps;
43 | _focus?: BoxBaseProps;
44 | _active?: BoxBaseProps;
45 | };
46 |
47 | _icon?: IconBaseProps;
48 |
49 | color?: string;
50 | cursor?: ResponsiveValue;
51 | animation?: ResponsiveValue;
52 | transition?: ResponsiveValue;
53 | outline?: ResponsiveValue>;
54 | outlineOffset?: ResponsiveValue>;
55 | transform?: ResponsiveValue;
56 | filter?: ResponsiveValue;
57 | placeSelf?: ResponsiveValue;
58 | userSelect?: system.ResponsiveValue;
59 | pointerEvents?: system.ResponsiveValue;
60 |
61 | WebkitTapHighlightColor?: system.ResponsiveValue;
62 |
63 | // Aliases
64 | column?: ResponsiveValue;
65 | row?: ResponsiveValue;
66 |
67 | theme?: XcoreTheme;
68 | }
69 | & system.TypographyProps
70 | & system.FlexboxProps
71 | & system.BorderProps
72 | & system.BoxShadowProps
73 | & Omit
74 | & Omit
75 | & system.PositionProps
76 | & system.SpaceProps
77 | & system.BackgroundProps
78 | & system.GridColumnProps
79 | & system.GridRowProps
80 | & system.ZIndexProps
81 | & system.JustifySelfProps
82 | & system.AlignSelfProps
83 | & HTMLAttributes;
84 |
85 | export type PseudoBoxBaseProps = {
86 | content?: ResponsiveValue;
87 | } & BoxBaseProps;
88 |
89 | export type GlobalBaseProps = {
90 | webkitFontSmoothing?: ResponsiveValue;
91 | boxSizing?: ResponsiveValue;
92 | } & BoxBaseProps;
93 |
94 | export type FlexBaseProps =
95 | & system.FlexboxProps
96 | & BoxBaseProps;
97 |
98 | export type IconBaseProps = {
99 | fill?: string;
100 | fillHover?: string;
101 | } & FlexBaseProps;
102 |
103 | export type TextBaseProps =
104 | {
105 | whiteSpace?: ResponsiveValue;
106 | wordBreak?: ResponsiveValue;
107 |
108 | textDecoration?: ResponsiveValue>;
109 | textOverflow?: ResponsiveValue;
110 | textTransform?: ResponsiveValue;
111 |
112 | WebkitLineClamp?: ResponsiveValue;
113 | WebkitBoxOrient?: ResponsiveValue;
114 | }
115 | & Omit
116 | & system.TypographyProps
117 | & system.TextShadowProps
118 | & BoxBaseProps;
119 |
--------------------------------------------------------------------------------
/src/components/AbsoluteBox/InsetBox.tsx:
--------------------------------------------------------------------------------
1 | import Portal from '@reach/portal';
2 | import { ResponsiveValue } from '@styled-system/core';
3 | import Box, { BoxProps } from 'components/Box';
4 | import React, { FC, MutableRefObject, useLayoutEffect, useState } from 'react';
5 | import useTheme from 'useTheme';
6 | import { transform } from 'utils/transform';
7 |
8 | import { HorizontalPosition, VerticalPosition } from './data';
9 |
10 | export type InsetBoxProps = {
11 | horizontalPosition?: ResponsiveValue;
12 | h?: ResponsiveValue;
13 |
14 | verticalPosition?: VerticalPosition;
15 | v?: VerticalPosition;
16 |
17 | target?: MutableRefObject;
18 | } & BoxProps;
19 |
20 | const InsetBox: FC = ({ target, ...p }) => {
21 | const [rect, setRect] = useState({
22 | x: null! as number,
23 | y: null! as number,
24 | width: null! as number,
25 | height: null! as number
26 | });
27 |
28 | useLayoutEffect(() => {
29 | if (target?.current) {
30 | const { x, y, width, height } = target.current.getBoundingClientRect();
31 | if (x !== rect.x || y !== rect.y || width !== rect.width || height !== rect.height) {
32 | setRect({ x, y, width, height });
33 | }
34 | }
35 | });
36 |
37 | return (
38 |
39 | {target
40 | ? (
41 |
42 |
43 |
44 |
45 |
46 | )
47 | : }
48 |
49 |
50 | );
51 | };
52 |
53 | export default InsetBox;
54 |
55 | const InnerInsetBox: FC = ({ h, horizontalPosition, v, verticalPosition, ...props }) => {
56 | const { breakpoints } = useTheme();
57 | const t = transform(breakpoints);
58 |
59 | const hor = t(h ?? horizontalPosition);
60 | const ver = t(v ?? verticalPosition);
61 |
62 | return (
63 |
66 | y === 'top' || y === 'stretch'
67 | ? 0
68 | : y === 'center'
69 | ? '50%'
70 | : null
71 | )}
72 | bottom={ver.map(y =>
73 | y === 'bottom' || y === 'stretch'
74 | ? 0
75 | : null
76 | )}
77 | left={hor.map(x =>
78 | x === 'left' || x === 'stretch'
79 | ? 0
80 | : x === 'center'
81 | ? '50%'
82 | : null
83 | )}
84 | right={hor.map(x =>
85 | x === 'right' || x === 'stretch'
86 | ? 0
87 | : null
88 | )}
89 | transform={ver.map((y, i) =>
90 | `${y === 'center' ? 'translateY(-50%)' : ''} ${hor.get(i) === 'center' ? 'translateX(-50%)' : ''}`
91 | )}
92 | {...props}
93 | />
94 | );
95 | };
96 |
--------------------------------------------------------------------------------
/src/components/AbsoluteBox/OutsetBox.tsx:
--------------------------------------------------------------------------------
1 | import Portal from '@reach/portal';
2 | import { ResponsiveValue } from '@styled-system/core';
3 | import Box, { BoxProps } from 'components/Box';
4 | import React, { FC, MutableRefObject, useEffect, useLayoutEffect, useMemo, useState } from 'react';
5 | import { useDebouncedCallback } from 'use-debounce';
6 | import useTheme from 'useTheme';
7 | import { transform } from 'utils/transform';
8 |
9 | import { HorizontalPosition, VerticalPosition } from './data';
10 |
11 | export type OutsetBoxTarget = MutableRefObject | Element;
12 |
13 | export type OutsetBoxProps = {
14 | horizontalPosition?: ResponsiveValue;
15 | h?: ResponsiveValue;
16 |
17 | verticalPosition?: VerticalPosition;
18 | v?: VerticalPosition;
19 |
20 | target: OutsetBoxTarget;
21 | } & BoxProps;
22 |
23 | const OutsetBox: FC = ({ h, horizontalPosition, v, verticalPosition, target, ...props }) => {
24 | const { breakpoints } = useTheme();
25 | const tr = transform(breakpoints);
26 |
27 | const hor = tr(h ?? horizontalPosition);
28 | const ver = tr(v ?? verticalPosition);
29 |
30 | const [{ top, left, right, bottom }, setRect] = useState({ top: 0, left: 0, right: 0, bottom: 0 });
31 |
32 | const t = target instanceof Element ? target : target.current;
33 |
34 | const up = () => {
35 | const rect = t.getBoundingClientRect();
36 | if (top !== rect.top || left !== rect.left || right !== rect.right || bottom !== rect.bottom) {
37 | setRect({ top: rect.top, left: rect.left, right: rect.right, bottom: rect.bottom });
38 | }
39 | };
40 |
41 | const [update] = useDebouncedCallback(up, 500);
42 |
43 | useLayoutEffect(() => {
44 | t && up();
45 | });
46 |
47 | useEffect(() => {
48 | const handler = () => update();
49 |
50 | window.addEventListener('resize', handler);
51 | return () => {
52 | window.removeEventListener('resize', handler);
53 | };
54 | }, []);
55 |
56 | return (
57 |
58 |
61 | y === 'right'
62 | ? right
63 | : y === 'center'
64 | ? left
65 | : null)}
66 | right={hor.map(y =>
67 | y === 'left'
68 | ? `calc(100vw - ${left}px)`
69 | : y === 'center'
70 | ? `calc(100vw - ${right}px)`
71 | : null)}
72 | bottom={ver.map(x => x === 'top'
73 | ? `calc(100vh - ${top}px)`
74 | : x === 'center'
75 | ? `calc(100vh - ${bottom}px)`
76 | : null)}
77 | top={ver.map(x => x === 'bottom'
78 | ? bottom
79 | : x === 'center'
80 | ? top
81 | : null)}
82 | >
83 |
84 |
85 |
86 | );
87 | };
88 |
89 | export default OutsetBox;
90 |
--------------------------------------------------------------------------------
/src/components/AbsoluteBox/data.ts:
--------------------------------------------------------------------------------
1 | export type VerticalPosition = 'top' | 'center' | 'bottom' | 'stretch';
2 | export type HorizontalPosition = 'left' | 'center' | 'right' | 'stretch';
3 |
--------------------------------------------------------------------------------
/src/components/ActiveBreakpoint.tsx:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from 'components/Box';
2 | import Text from 'components/Text';
3 | import React, { FC } from 'react';
4 | import useTheme from 'useTheme';
5 |
6 | const ActiveBreakpoint: FC = props => {
7 | const { breakpoints } = useTheme();
8 | const style = breakpoints.aliases.map(() => 'none');
9 |
10 | return (
11 |
12 |
13 |
14 | [0]: "_" (0 - {breakpoints[0]})
15 |
16 | {breakpoints.aliases.map((a, i) => (
17 |
18 | [{i + 1}]: "{a}" ({breakpoints[i]} - {i === breakpoints.length - 1 ? '∞' : breakpoints[i + 1]})
19 |
20 | ))}
21 |
22 |
23 | );
24 | };
25 |
26 | export default ActiveBreakpoint;
27 |
--------------------------------------------------------------------------------
/src/components/AspectRatio.tsx:
--------------------------------------------------------------------------------
1 | import { BoxBaseProps, composedBoxBase } from 'bases';
2 | import Flex from 'components/Flex';
3 | import React, { FC } from 'react';
4 | import styled from 'styled-components';
5 | import { shouldForwardProp } from 'utils/withConfig';
6 |
7 | export type AspectRatioProps = {
8 | ratio: number;
9 | } & BoxBaseProps;
10 |
11 | const AspectRatioStyle = styled.div.withConfig({ shouldForwardProp })`
12 | ${composedBoxBase}
13 |
14 | & > div {
15 | width: 100%;
16 | height: 100%;
17 | overflow: hidden;
18 | }
19 |
20 | img {
21 | object-fit: cover;
22 | }
23 | `;
24 |
25 | const AspectRatio: FC = ({ ratio, children, ...props }) => {
26 | return (
27 |
34 |
41 | {children}
42 |
43 |
44 | );
45 | };
46 |
47 | export default AspectRatio;
48 |
--------------------------------------------------------------------------------
/src/components/Box.tsx:
--------------------------------------------------------------------------------
1 | import { boxBase, BoxBaseProps } from 'bases';
2 | import styled from 'styled-components';
3 | import { compose } from 'utils/baseStyle';
4 | import { shouldForwardProp } from 'utils/withConfig';
5 |
6 | export type TLen = string | 0 | number;
7 |
8 | export type BoxProps = BoxBaseProps;
9 |
10 | const Box = styled.div.withConfig({ shouldForwardProp })`
11 | ${compose(boxBase)}
12 | `;
13 |
14 | export default Box;
15 |
--------------------------------------------------------------------------------
/src/components/Button/index.tsx:
--------------------------------------------------------------------------------
1 | import { flexBase, FlexBaseProps, textBase, TextBaseProps } from 'bases';
2 | import Complement, { comp, ComplementProps } from 'components/Complement';
3 | import Spinner, { SpinnerProps } from 'components/Spinner';
4 | import * as CSS from 'csstype';
5 | import React, { AnchorHTMLAttributes, forwardRef, ReactNode } from 'react';
6 | import styled from 'styled-components';
7 | import { ButtonSize, ButtonVariant } from 'theme';
8 | import useTheme from 'useTheme';
9 | import { compose } from 'utils/baseStyle';
10 | import useMerge from 'utils/useMerge';
11 | import { sizeVariant, typeVariant } from 'utils/variant';
12 | import { ResponsiveValue } from '@styled-system/core';
13 | import { shouldForwardProp } from 'utils/withConfig';
14 |
15 | export type ButtonProps =
16 | {
17 | _spinner?: SpinnerProps;
18 | textTransform?: ResponsiveValue;
19 | }
20 | & ComplementProps
21 | & FlexBaseProps
22 | & TextBaseProps;
23 |
24 | export type ExtendedButtonProps =
25 | & ButtonProps
26 | & {
27 | loading?: boolean;
28 | disabled?: boolean;
29 |
30 | size?: ButtonSize;
31 | s?: ButtonSize;
32 | variant?: ButtonVariant;
33 | v?: ButtonVariant;
34 | }
35 | & ({ as?: 'button' | 'div' } | ({ as: 'a' } & AnchorHTMLAttributes));
36 |
37 | const Button = forwardRef((
38 | {
39 | loading,
40 | as = 'button',
41 | children,
42 | ...p
43 | },
44 | ref
45 | ) => {
46 | const { button } = useTheme();
47 |
48 | const m = useMerge(
49 | p,
50 | typeVariant(button, 'solid', p),
51 | sizeVariant(button, 'md', p),
52 | button.default
53 | );
54 | const [left, right, { _spinner, ...props }] = comp(m);
55 |
56 | return (
57 |
58 |
59 |
60 | {loading && (
61 |
65 | )}
66 |
67 | {children}
68 |
69 |
70 |
71 | );
72 | });
73 |
74 | export default Button;
75 |
76 | const ButtonStyle = styled.div.withConfig({ shouldForwardProp })`
77 | ${compose(textBase, flexBase)}
78 | `;
79 |
--------------------------------------------------------------------------------
/src/components/Button/theme.ts:
--------------------------------------------------------------------------------
1 | import { ButtonProps } from '.';
2 | import { mergeThemes } from 'utils/mergeThemes';
3 | import { darken, opacify } from 'scales/colors';
4 |
5 | export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg';
6 | export type ButtonVariant = 'solid' | 'clear' | 'outline' | 'link';
7 |
8 | export type ButtonAs = 'button' | 'div' | 'a';
9 |
10 | interface ButtonValue {
11 | default: ButtonProps;
12 | sizes: Record;
13 | variants: Record;
14 | }
15 |
16 | export interface ButtonTheme {
17 | button: ButtonValue;
18 | }
19 |
20 | export const button = (
21 | b?: {
22 | default?: ButtonProps;
23 | sizes?: Partial>;
24 | variants?: Partial>;
25 | }
26 | ): ButtonTheme => ({ button: mergeThemes<'variants' | 'sizes', ButtonProps>(b, emptyButton) });
27 |
28 | const emptyButton: ButtonValue = {
29 | default: {
30 | fontWeight: 500,
31 | fontFamily: 'text',
32 | fontSize: '1.4rem',
33 | lineHeight: '2.4rem',
34 | borderRadius: '0.3rem',
35 |
36 | display: 'inline-flex',
37 | bg: 'transparent',
38 | border: '0.1rem solid transparent',
39 | transition: 'background 300ms, color 300ms, border 300ms, box-shadow 300ms',
40 | cursor: 'pointer',
41 | textDecoration: 'none',
42 | alignItems: 'center',
43 | _disabled: {
44 | cursor: 'not-allowed'
45 | }
46 | },
47 | sizes: {
48 | xs: {
49 | py: 0,
50 | px: '0.8rem',
51 | fontSize: '1.2rem',
52 | lineHeight: '1.8rem'
53 | },
54 | sm: {
55 | px: '1.2rem',
56 | py: '0.3rem',
57 | fontSize: '1.3rem'
58 | },
59 | md: {
60 | px: '2rem',
61 | py: '0.7rem',
62 | fontSize: '1.4rem'
63 | },
64 | lg: {
65 | px: '3rem',
66 | py: '1.2rem',
67 | fontSize: '1.6rem'
68 | }
69 | },
70 | variants: {
71 | solid: {
72 | bg: 'primary',
73 | color: 'background',
74 | _hover: {
75 | bg: darken('primary', 0.025)
76 | },
77 | _active: {
78 | bg: darken('primary', 0.05)
79 | },
80 | _focus: {
81 | bg: darken('primary', 0.05),
82 | outline: '2px solid rgba(15, 31, 40, 0.2)',
83 | outlineOffset: '-2px'
84 | },
85 | _disabled: {
86 | opacity: 0.5
87 | }
88 | },
89 | clear: {
90 | color: 'primary',
91 | _hover: {
92 | bg: opacify('primary', 0.1)
93 | },
94 | _active: {
95 | bg: opacify('primary', 0.2)
96 | },
97 | _focus: {
98 | bg: opacify('primary', 0.2),
99 | outline: '2px solid rgba(15, 31, 40, 0.2)',
100 | outlineOffset: '-2px'
101 | },
102 | _disabled: {
103 | opacity: 0.5
104 | }
105 | },
106 | outline: {
107 | border: '1px solid',
108 | borderColor: 'primary',
109 | color: 'primary',
110 | _hover: {
111 | bg: opacify('primary', 0.1)
112 | },
113 | _active: {
114 | bg: opacify('primary', 0.2)
115 | },
116 | _focus: {
117 | bg: opacify('primary', 0.2),
118 | outline: '2px solid rgba(15, 31, 40, 0.2)',
119 | outlineOffset: '-2px'
120 | },
121 | _disabled: {
122 | opacity: 0.5
123 | }
124 | },
125 | link: {
126 | padding: 0,
127 | color: 'primary',
128 | borderRadius: 0,
129 | border: 0,
130 | borderBottom: '1px solid transparent',
131 | _hover: {
132 | borderBottom: '1px solid',
133 | borderColor: 'primary'
134 | },
135 | _active: {
136 | color: '#036199'
137 | },
138 | _focus: {
139 | outline: '2px solid rgba(15, 31, 40, 0.2)',
140 | outlineOffset: '-2px'
141 | },
142 | _disabled: {
143 | opacity: 0.5
144 | }
145 | }
146 | }
147 | };
148 |
--------------------------------------------------------------------------------
/src/components/Card/index.tsx:
--------------------------------------------------------------------------------
1 | import { BoxProps } from 'components/Box';
2 | import Flex, { FlexProps } from 'components/Flex';
3 | import Tag, { TagProps } from 'components/Tag';
4 | import Text, { TextProps } from 'components/Text';
5 | import CSS from 'csstype';
6 | import React, { forwardRef, ReactNode } from 'react';
7 | import { ResponsiveValue } from '@styled-system/core';
8 | import useTheme from 'useTheme';
9 | import renderComponent, { Renderable } from 'utils/renderComponent';
10 | import useMerge from 'utils/useMerge';
11 | import { typeVariant } from 'utils/variant';
12 |
13 | import { CardVariant } from './theme';
14 |
15 | export type CardProps =
16 | {
17 | header?: Renderable;
18 | _header?: FlexProps;
19 |
20 | title?: Renderable;
21 | _title?: TextProps;
22 |
23 | tag?: Renderable;
24 | _tag?: TagProps;
25 |
26 | media?: Renderable;
27 | _media?: FlexProps;
28 |
29 | body?: Renderable;
30 | _body?: FlexProps;
31 |
32 | footer?: Renderable;
33 | _footer?: FlexProps;
34 |
35 | innerPadding?: ResponsiveValue>;
36 | }
37 | & BoxProps;
38 |
39 | export type ExtendedCardProps = {
40 | v?: CardVariant;
41 | variant?: CardVariant;
42 | } & CardProps;
43 |
44 | const Card = forwardRef(({
45 | children,
46 | ...p
47 | }, ref) => {
48 | const { card } = useTheme();
49 |
50 | const type = typeVariant(card, 'elevated', p);
51 |
52 | const {
53 | _header,
54 | header,
55 | _tag,
56 | tag,
57 | _title,
58 | title,
59 | _media,
60 | media,
61 | _body,
62 | body,
63 | _footer,
64 | footer,
65 | innerPadding,
66 | ...props
67 | } = useMerge(
68 | p,
69 | type,
70 | card.default
71 | );
72 |
73 | const getPadding = (
74 | target: (p: CardProps) => { padding?: T } | undefined
75 | ) =>
76 | target(p)?.padding ??
77 | p.innerPadding ??
78 | target(type)?.padding ??
79 | type.innerPadding ??
80 | target(card.default)?.padding ??
81 | card.default.innerPadding;
82 |
83 | return (
84 |
92 | {(header || title) && (
93 |
94 | x._header)}>
95 |
96 | {header
97 | ? renderComponent(header)
98 | : (
99 |
104 | {renderComponent(title)}
105 |
106 | )}
107 |
108 |
109 |
110 | )}
111 |
112 | {tag && (
113 |
114 | {renderComponent(tag)}
115 |
116 | )}
117 |
118 | {media && (
119 |
120 | {renderComponent(media)}
121 |
122 | )}
123 |
124 | {body && (
125 | x._body)}>
126 | {renderComponent(body)}
127 |
128 | )}
129 |
130 | {children && (
131 | x._body)}>
132 | {children}
133 |
134 | )}
135 |
136 | {footer && (
137 |
138 | {renderComponent(footer)}
139 |
140 | )}
141 |
142 |
143 | );
144 | });
145 |
146 | export default Card;
147 |
--------------------------------------------------------------------------------
/src/components/Card/theme.ts:
--------------------------------------------------------------------------------
1 | import { CardProps } from '.';
2 | import { mergeThemes } from 'utils/mergeThemes';
3 |
4 | export type CardVariant = 'normal' | 'elevated' | 'outline';
5 |
6 | interface CardValue {
7 | default: CardProps;
8 | variants: Record;
9 | }
10 |
11 | export interface CardTheme {
12 | card: CardValue;
13 | }
14 |
15 | export const card = (
16 | c?: {
17 | default?: CardProps;
18 | variants?: Partial>;
19 | }
20 | ): CardTheme => ({
21 | card: mergeThemes<'variants', CardProps>(c, emptyCard)
22 | });
23 |
24 | const emptyCard: CardValue = {
25 | default: {
26 | color: 'text',
27 | background: 'background',
28 | maxWidth: '30rem',
29 | _header: { padding: '1rem' },
30 | _body: { padding: '1rem' },
31 | _footer: { padding: '1rem' }
32 | },
33 | variants: {
34 | normal: {},
35 | elevated: {
36 | borderRadius: '0.3rem',
37 | boxShadow: '0 0.3rem 0.8rem 0 rgba(36, 49, 70, 0.25)'
38 | },
39 | outline: {
40 | border: '1px solid rgba(69, 86, 99, 0.5)'
41 | }
42 | }
43 | };
44 |
--------------------------------------------------------------------------------
/src/components/CloseControl/index.tsx:
--------------------------------------------------------------------------------
1 | import { composedFlexBase, FlexBaseProps } from 'bases';
2 | import { FlexProps } from 'components/Flex';
3 | import Icon, { IconProps } from 'components/Icon';
4 | import { CloseIcon } from 'icons/close';
5 | import React, { forwardRef } from 'react';
6 | import styled from 'styled-components';
7 | import useTheme from 'useTheme';
8 | import useMerge from 'utils/useMerge';
9 | import { sizeVariant } from 'utils/variant';
10 | import { shouldForwardProp } from 'utils/withConfig';
11 |
12 | import { CloseControlSizes } from './theme';
13 |
14 | export type CloseControlProps =
15 | {
16 | _icon?: IconProps;
17 | }
18 | & FlexProps;
19 |
20 | export type ExtendedCloseControlProps =
21 | {
22 | size?: CloseControlSizes;
23 | s?: CloseControlSizes;
24 | }
25 | & CloseControlProps;
26 |
27 | const CloseControl = forwardRef((p, ref) => {
28 | const { closeControl } = useTheme();
29 |
30 | const { _icon, ...props } = useMerge(
31 | p,
32 | sizeVariant(closeControl, 'md', p),
33 | closeControl.default
34 | );
35 |
36 | return (
37 |
44 | } {..._icon} />
45 |
46 | );
47 | });
48 |
49 | export default CloseControl;
50 |
51 | const CloseButtonStyle = styled.div.withConfig({ shouldForwardProp })`
52 | ${composedFlexBase}
53 | svg {
54 | width: 100%;
55 | height: 100%;
56 | }
57 | `;
58 |
--------------------------------------------------------------------------------
/src/components/CloseControl/theme.ts:
--------------------------------------------------------------------------------
1 | import { CloseControlProps } from '.';
2 | import { mergeThemes } from 'utils/mergeThemes';
3 |
4 | export type CloseControlSizes = 'xs' | 'sm' | 'md' | 'lg';
5 |
6 | interface CloseControlValue {
7 | default: CloseControlProps;
8 | sizes: Record;
9 | }
10 |
11 | export interface CloseControlTheme {
12 | closeControl: CloseControlValue;
13 | }
14 |
15 | export const closeControl = (c?: {
16 | default: Partial;
17 | sizes?: Partial>;
18 | }): CloseControlTheme =>
19 | ({ closeControl: mergeThemes<'sizes', CloseControlProps>(c, emptyCloseControl) });
20 |
21 | const emptyCloseControl: CloseControlValue = {
22 | default: {
23 | cursor: 'pointer',
24 | _icon: {
25 | fill: '#455663',
26 | fillHover: '#1e1e1e'
27 | }
28 | },
29 | sizes: {
30 | xs: {
31 | width: '1.2rem',
32 | height: '1.2rem',
33 | _icon: {
34 | width: '0.6rem',
35 | height: '0.6rem'
36 | }
37 | },
38 | sm: {
39 | width: '1.6rem',
40 | height: '1.6rem',
41 | _icon: {
42 | width: '0.8rem',
43 | height: '0.8rem'
44 | }
45 | },
46 | md: {
47 | width: '2.4rem',
48 | height: '2.4rem',
49 | _icon: {
50 | width: '1.2rem',
51 | height: '1.2rem'
52 | }
53 | },
54 | lg: {
55 | width: '3.2rem',
56 | height: '3.2rem',
57 | _icon: {
58 | width: '1.6rem',
59 | height: '1.6rem'
60 | }
61 | }
62 | }
63 | };
64 |
--------------------------------------------------------------------------------
/src/components/Collapse.tsx:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from 'components/Box';
2 | import React, { FC } from 'react';
3 | import AnimateHeight from 'react-animate-height';
4 |
5 | export type CollapseProps = {
6 | open?: boolean;
7 | start?: number;
8 | end?: number;
9 | } & BoxProps;
10 |
11 | const Collapse: FC = (
12 | {
13 | open,
14 | start = 0,
15 | end = 'auto',
16 | ...props
17 | }
18 | ) => (
19 |
20 |
21 |
22 | );
23 |
24 | export default Collapse;
25 |
--------------------------------------------------------------------------------
/src/components/Complement.tsx:
--------------------------------------------------------------------------------
1 | import Icon, { IconProps } from 'components/Icon';
2 | import React, { FC } from 'react';
3 | import renderComponent, { Renderable } from 'utils/renderComponent';
4 |
5 | export type SideComplementProps = {
6 | _icon?: IconProps;
7 | icon?: Renderable;
8 | element?: Renderable;
9 | };
10 |
11 | export type ComplementProps = {
12 | _leftIcon?: IconProps;
13 | leftIcon?: Renderable;
14 | leftElement?: Renderable;
15 |
16 | _rightIcon?: IconProps;
17 | rightIcon?: Renderable;
18 | rightElement?: Renderable;
19 | };
20 |
21 | export const sideComp = ({
22 | _icon,
23 | icon,
24 | element,
25 | ...props
26 | }: T): [ComplementSideProps, Omit] =>
27 | [{ _icon, icon, element }, props];
28 |
29 | export const comp = ({
30 | _rightIcon,
31 | rightIcon,
32 | rightElement,
33 | _leftIcon,
34 | leftIcon,
35 | leftElement,
36 | ...props
37 | }: T): [ComplementSideProps, ComplementSideProps, Omit] =>
38 | [
39 | {
40 | _icon: _leftIcon,
41 | icon: leftIcon,
42 | element: leftElement
43 | },
44 | {
45 | _icon: _rightIcon,
46 | icon: rightIcon,
47 | element: rightElement
48 | },
49 | props
50 | ];
51 |
52 | interface ComplementSideProps {
53 | _icon?: IconProps;
54 | icon?: Renderable;
55 | element?: Renderable;
56 | }
57 |
58 | const Complement: FC = ({ _icon, icon, element }) => (
59 | <>
60 | {renderComponent(element)}
61 | {icon && }
62 | >
63 | );
64 |
65 | export default Complement;
66 |
--------------------------------------------------------------------------------
/src/components/Container/index.tsx:
--------------------------------------------------------------------------------
1 | import Flex, { FlexProps } from 'components/Flex';
2 | import React, { forwardRef, ReactNode } from 'react';
3 | import useTheme from 'useTheme';
4 | import useMerge from 'utils/useMerge';
5 | import { typeVariant } from 'utils/variant';
6 |
7 | import { ContainerVariant } from './theme';
8 |
9 | export type ContainerProps = FlexProps;
10 |
11 | export type ExtendedContainerProps = {
12 | variant?: ContainerVariant;
13 | v?: ContainerVariant;
14 | } & ContainerProps;
15 |
16 | const Container = forwardRef((p, ref) => {
17 | const { container } = useTheme();
18 |
19 | const props = useMerge(p, typeVariant(container, 'normal', p), container.default);
20 |
21 | return ;
22 | });
23 |
24 | export default Container;
25 |
--------------------------------------------------------------------------------
/src/components/Container/theme.ts:
--------------------------------------------------------------------------------
1 | import { FlexProps } from 'components/Flex';
2 | import { mergeThemes } from 'utils/mergeThemes';
3 |
4 | type ContainerValue = {
5 | default: FlexProps;
6 | variants: Record;
7 | };
8 |
9 | export type ContainerVariant = 'normal' | 'fluid';
10 |
11 | export interface ContainerTheme {
12 | container: ContainerValue;
13 | }
14 |
15 | export const container = (
16 | c?: {
17 | default?: FlexProps;
18 | variants?: Partial>;
19 | }
20 | ): ContainerTheme => ({ container: mergeThemes<'variants', FlexProps>(c, emptyContainer) });
21 |
22 | const emptyContainer: ContainerValue = {
23 | default: {
24 | ml: 'auto',
25 | mr: 'auto'
26 | },
27 | variants: {
28 | normal: {
29 | width: ['100%', '76.8rem', '102.4rem', '120rem', '132rem'],
30 | px: 3
31 | },
32 | fluid: {
33 | width: '100%',
34 | px: 3
35 | }
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/src/components/Flex.ts:
--------------------------------------------------------------------------------
1 | import { FlexBaseProps, composedFlexBase } from 'bases';
2 | import styled from 'styled-components';
3 | import { shouldForwardProp } from 'utils/withConfig';
4 |
5 | export type FlexProps = FlexBaseProps;
6 | export type ExtendedFlexProps = FlexProps;
7 |
8 | const Flex = styled.div.withConfig({ shouldForwardProp })`
9 | ${composedFlexBase}
10 | `;
11 | Flex.displayName = 'Flex';
12 |
13 | export default Flex;
14 |
--------------------------------------------------------------------------------
/src/components/Grid/Cell.tsx:
--------------------------------------------------------------------------------
1 | import { ResponsiveValue, system } from '@styled-system/core';
2 | import { BoxBaseProps, composedBoxBase } from 'bases';
3 | import React, { FC, useContext } from 'react';
4 | import { Breakpoints } from 'scales/breakpoints';
5 | import styled, { css } from 'styled-components';
6 | import useTheme from 'useTheme';
7 | import { polyfillTheme } from 'utils/baseStyle';
8 | import { parseTwin } from 'utils/gridTemplate';
9 | import { isIE } from 'utils/isIE';
10 | import { mediaQueries } from 'utils/mediaQuery';
11 | import { transform } from 'utils/transform';
12 | import { shouldForwardProp } from 'utils/withConfig';
13 |
14 | import { GridContext } from '.';
15 | import { parseGridAxis } from './data';
16 |
17 | export type CellProps = BoxBaseProps;
18 | export type ExtendedCellProps = CellProps;
19 |
20 | const Cell: FC = ({ column, gridColumn, row, gridRow, ...props }) => {
21 | const { breakpoints } = useTheme();
22 | const { gap } = useContext(GridContext);
23 |
24 | return (
25 |
32 | );
33 | };
34 |
35 | export default Cell;
36 |
37 | type CellStyleProps = {
38 | gap?: ResponsiveValue;
39 | breakpoints: Breakpoints;
40 | } & BoxBaseProps;
41 |
42 | const CellStyle = styled.div.withConfig({ shouldForwardProp })`
43 | ${composedBoxBase}
44 |
45 | ${p => isIE() && [
46 | // Styled system properties
47 | cellSystem(polyfillTheme(p)),
48 | // Manually position element in grid and handle place-self property
49 | mediaQueries(p.breakpoints, i => {
50 | const t = transform(p.breakpoints);
51 | const column = t(p.column);
52 | const row = t(p.row);
53 | const gap = t(p.gap);
54 | const [gc, gr] = parseTwin(gap.get(i));
55 |
56 | const [colStart, colEnd] = parseGridAxis(column.get(i), !!gc);
57 | const [rowStart, rowEnd] = parseGridAxis(row.get(i), !!gr);
58 |
59 | const placeSelf = transform(p.breakpoints)(p.placeSelf).get(i);
60 | const s = placeSelf?.split(' ');
61 |
62 | return [
63 | // Grid position
64 | (!column.empty(i) || !gap.empty(i)) && colStart && css`
65 | -ms-grid-column: ${colStart} !important;
66 | `,
67 | (!column.empty(i) || !gap.empty(i)) && colEnd && css`
68 | -ms-grid-column-span: ${colEnd} !important;
69 | `,
70 |
71 | (!row.empty(i) || !gap.empty(i)) && rowStart && css`
72 | -ms-grid-row: ${rowStart} !important;
73 | `,
74 | (!row.empty(i) || !gap.empty(i)) && rowEnd && css`
75 | -ms-grid-row-span: ${rowEnd} !important;
76 | `,
77 | // place-self
78 | s && css`
79 | -ms-grid-row-align: ${s[0]};
80 | -ms-grid-column-align: ${s[1] ? s[1] : s[0]};
81 | `
82 | ];
83 | })
84 | ]}
85 |
86 | `;
87 |
88 | const cellSystem = system({
89 | alignSelf: {
90 | properties: ['-ms-flex-item-align', '-ms-grid-row-align'] as any[]
91 | },
92 | justifySelf: {
93 | property: '-ms-grid-column-align' as any
94 | }
95 | });
96 |
--------------------------------------------------------------------------------
/src/components/Grid/data.tsx:
--------------------------------------------------------------------------------
1 | export const parseGridCell = (val: string | number): [string, string | undefined] =>
2 | typeof val === 'number'
3 | ? [val.toString(), undefined]
4 | : (val.split('/') as [string, string]);
5 |
6 | export const parseGridAxis = (
7 | axis: string | null | number,
8 | gap: boolean
9 | ): [number | undefined, number | undefined] => {
10 | if (axis === null) {
11 | return [undefined, undefined];
12 | }
13 |
14 | const [start, end] = parseGridCell(axis);
15 |
16 | const getIndex = (n: number) => gap ? 2 * n - 1 : n;
17 |
18 | return [
19 | getIndex(Number(start)),
20 | parseSpan(end, Number(start), getIndex)
21 | ];
22 | };
23 |
24 | export const parseSpan = (end: string | undefined, start: number, getIndex: (n: number) => number) =>
25 | !end
26 | ? undefined
27 | : end.includes('span')
28 | ? getIndex(Number(end.replace('span', '')))
29 | : getIndex(Number(end)) - getIndex(start);
30 |
--------------------------------------------------------------------------------
/src/components/Grid/index.tsx:
--------------------------------------------------------------------------------
1 | import { ResponsiveValue, system } from '@styled-system/core';
2 | import { BoxBaseProps, composedBoxBase } from 'bases';
3 | import { gridConfig } from 'bases/config/grid';
4 | import CSS from 'csstype';
5 | import React, { createContext, FC, forwardRef, ReactNode } from 'react';
6 | import { Breakpoints } from 'scales/breakpoints';
7 | import styled, { css } from 'styled-components';
8 | import useTheme from 'useTheme';
9 | import { polyfillTheme } from 'utils/baseStyle';
10 | import { parseTemplate, parseTwin } from 'utils/gridTemplate';
11 | import { isIE } from 'utils/isIE';
12 | import { mediaQueries } from 'utils/mediaQuery';
13 | import { transform, TransformedValue } from 'utils/transform';
14 | import { shouldForwardProp } from 'utils/withConfig';
15 |
16 | type Col = CSS.GridTemplateColumnsProperty;
17 | export type GridColumnResponsiveValue =
18 | | Col
19 | | null
20 | | Array
21 | | { [key in string | number]?: Col };
22 |
23 | export type GridProps =
24 | {
25 | columns: GridColumnResponsiveValue;
26 | rows: ResponsiveValue>;
27 |
28 | gap?: ResponsiveValue>;
29 | }
30 | & GridPositionProps
31 | & BoxBaseProps;
32 |
33 | export type GridPositionProps = {
34 | justifyItems?: ResponsiveValue;
35 | justifyContent?: ResponsiveValue;
36 | alignItems?: ResponsiveValue;
37 | alignContent?: ResponsiveValue;
38 | };
39 |
40 | export const GridContext = createContext<{ gap?: ResponsiveValue> }>({ gap: [] });
41 |
42 | export type ExtendedGridProps = GridProps;
43 |
44 | const Grid = forwardRef(({ columns, rows, gap, ...props }, ref) => {
45 | const { breakpoints } = useTheme();
46 | const t = transform(breakpoints);
47 |
48 | return (
49 |
50 |
58 |
59 | );
60 | });
61 |
62 | Grid.displayName = 'Grid';
63 |
64 | export default Grid;
65 |
66 | type GridStyleProps = {
67 | columns: TransformedValue;
68 | rows: TransformedValue;
69 | gap: TransformedValue;
70 | breakpoints: Breakpoints;
71 | } & GridPositionProps & BoxBaseProps;
72 |
73 | const GridStyle = styled.div.withConfig({ shouldForwardProp })`
74 | ${composedBoxBase}
75 |
76 | display: grid;
77 | display: -ms-grid;
78 |
79 | ${p => gridSystem(polyfillTheme({
80 | ...p,
81 | rows: p.rows.value,
82 | columns: p.columns.value,
83 | gap: p.gap.value
84 | }))}
85 |
86 | ${({ columns, rows, gap, breakpoints }) =>
87 | isIE() && mediaQueries(breakpoints, i => {
88 | const colVal = columns.get(i)!;
89 | const rowVal = rows.get(i)!;
90 | const gapVal = parseTwin(gap.get(i)!);
91 |
92 | return !(columns.empty(i) && rows.empty(i) && gap.empty(i)) && css`
93 | ${!(columns.empty(i) && gap.empty(i)) && css`
94 | -ms-grid-columns: ${parseTemplate(colVal, gapVal ? gapVal[0] : null).join(' ')};
95 | `}
96 |
97 | ${!(rows.empty(i) && gap.empty(i)) && css`
98 | -ms-grid-rows: ${parseTemplate(rowVal, gapVal ? gapVal[1] : null).join(' ')};
99 | `}
100 |
101 | ${templateQueries(parseTemplate(colVal), parseTemplate(rowVal), !!gapVal)}
102 | `;
103 | })
104 | }
105 | `;
106 |
107 | const gridSystem = system(gridConfig);
108 |
109 | const templateQueries = (columns: string[], rows: string[], gap: boolean) => css`
110 | ${rows.map((_, y) =>
111 | css`
112 | ${columns.map((c, x) => css`
113 | & > *:nth-child(${y * columns.length + x + 1}) {
114 | -ms-grid-column: ${gap ? 2 * x + 1 : x + 1};
115 | -ms-grid-row: ${gap ? 2 * y + 1 : y + 1};
116 | }
117 | `)}
118 | `
119 | )}
120 | `;
121 |
--------------------------------------------------------------------------------
/src/components/Icon.tsx:
--------------------------------------------------------------------------------
1 | import { composedIconBase, IconBaseProps } from 'bases';
2 | import React, { FC, ReactNode } from 'react';
3 | import styled from 'styled-components';
4 | import renderComponent from 'utils/renderComponent';
5 | import { shouldForwardProp } from 'utils/withConfig';
6 |
7 | export type IconProps = IconBaseProps & {
8 | svg?: ReactNode;
9 | };
10 |
11 | export type ExtendedIconProps = IconProps;
12 |
13 | const Icon: FC = ({ svg, children, ...props }) => {
14 | return (
15 |
21 | {renderComponent(svg)}
22 | {children}
23 |
24 | );
25 | };
26 |
27 | export default Icon;
28 |
29 | const IconStyle = styled.span.withConfig({ shouldForwardProp })`
30 | ${composedIconBase}
31 | `;
32 |
--------------------------------------------------------------------------------
/src/components/Img.tsx:
--------------------------------------------------------------------------------
1 | import { composedBoxBase } from 'bases';
2 | import { BoxProps } from 'components/Box';
3 | import * as CSS from 'csstype';
4 | import React, { FC, ImgHTMLAttributes } from 'react';
5 | import styled from 'styled-components';
6 | import { ResponsiveValue, system } from '@styled-system/core';
7 | import { shouldForwardProp } from 'utils/withConfig';
8 |
9 | export type ImgProps =
10 | {
11 | objectFit?: ResponsiveValue;
12 | }
13 | & Omit, 'width' | 'height'>
14 | & BoxProps;
15 |
16 | const Img: FC = ({ ...props }) =>
17 |
18 | ;
19 |
20 | export default Img;
21 |
22 | const imgSystem = system({ objectFit: true });
23 |
24 | const ImgStyle = styled.div.withConfig({ shouldForwardProp })`
25 | ${composedBoxBase}
26 | ${imgSystem}
27 | `;
28 |
--------------------------------------------------------------------------------
/src/components/Link/index.tsx:
--------------------------------------------------------------------------------
1 | import { composedTextBase, TextBaseProps } from 'bases';
2 | import React, { AnchorHTMLAttributes, forwardRef, ReactNode } from 'react';
3 | import styled from 'styled-components';
4 | import useTheme from 'useTheme';
5 | import useMerge from 'utils/useMerge';
6 | import { typeVariant } from 'utils/variant';
7 | import { shouldForwardProp } from 'utils/withConfig';
8 |
9 | import { LinkAs, LinkVariant } from './theme';
10 |
11 | export type LinkProps =
12 | & TextBaseProps
13 | & AnchorHTMLAttributes;
14 |
15 | export type ExtendedLinkProps = {
16 | as?: LinkAs;
17 | variant?: LinkVariant;
18 | v?: LinkVariant;
19 | } & LinkProps;
20 |
21 | const Link = forwardRef((p, ref) => {
22 | const { link } = useTheme();
23 |
24 | const props = useMerge(
25 | p,
26 | typeVariant(link, 'simple', p),
27 | link.default
28 | );
29 |
30 | return (
31 |
32 | );
33 | });
34 |
35 | export default Link;
36 |
37 | const LinkStyle = styled.a.withConfig({ shouldForwardProp })`
38 | ${composedTextBase}
39 |
40 | cursor: pointer;
41 | & * {
42 | cursor: pointer;
43 | }
44 | `;
45 |
--------------------------------------------------------------------------------
/src/components/Link/theme.ts:
--------------------------------------------------------------------------------
1 | import { darken } from 'scales/colors';
2 | import { mergeThemes } from 'utils/mergeThemes';
3 |
4 | import { LinkProps } from '.';
5 |
6 | export type LinkVariant = 'underline' | 'simple';
7 | export type LinkAs = 'a' | 'span';
8 |
9 | interface LinkValue {
10 | default: LinkProps;
11 | variants: Record;
12 | }
13 |
14 | export interface LinkTheme {
15 | link: LinkValue;
16 | }
17 |
18 | export const link = (l?: {
19 | default?: LinkProps;
20 | variants?: Partial>;
21 | }): LinkTheme => ({ link: mergeThemes<'variants', LinkProps>(l, emptyLink) });
22 |
23 | const emptyLink: LinkValue = {
24 | default: {
25 | color: 'primary',
26 | borderBottom: '1px solid',
27 | borderColor: 'primary',
28 | fontSize: '1.6rem',
29 | lineHeight: 'initial',
30 | display: 'inline-flex',
31 | transition: 'color 300ms, border 300ms',
32 | cursor: 'pointer',
33 |
34 | _hover: {
35 | color: darken('primary', 0.025),
36 | borderBottom: '1px solid transparent'
37 | }
38 | },
39 | variants: {
40 | underline: {},
41 | simple: {
42 | borderBottom: '1px solid transparent',
43 |
44 | _hover: {
45 | color: darken('primary', 0.025),
46 | borderBottom: '1px solid transparent'
47 | }
48 | }
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/src/components/List/ListItem.tsx:
--------------------------------------------------------------------------------
1 | import { flexBase, FlexBaseProps, textBase, TextBaseProps } from 'bases';
2 | import Complement, { sideComp, SideComplementProps } from 'components/Complement';
3 | import React, { FC } from 'react';
4 | import styled from 'styled-components';
5 | import { compose } from 'utils/baseStyle';
6 | import { shouldForwardProp } from 'utils/withConfig';
7 |
8 | export type ListItemProps =
9 | & FlexBaseProps
10 | & TextBaseProps
11 | & SideComplementProps;
12 |
13 | const ListItem: FC = p => {
14 | const [comp, { children, ...props }] = sideComp(p);
15 |
16 | return (
17 |
18 |
19 |
20 | {children}
21 |
22 |
23 | );
24 | };
25 |
26 | const ListItemStyle = styled.li.withConfig({ shouldForwardProp })`
27 | ${compose(flexBase, textBase)}
28 | `;
29 |
30 | export default ListItem;
31 |
--------------------------------------------------------------------------------
/src/components/List/index.tsx:
--------------------------------------------------------------------------------
1 | import { composedTextBase, TextBaseProps } from 'bases';
2 | import { TLen } from 'components/Box';
3 | import CSS from 'csstype';
4 | import React, { FC, forwardRef, ReactNode } from 'react';
5 | import styled from 'styled-components';
6 | import { system, ResponsiveValue } from '@styled-system/core';
7 | import useTheme from 'useTheme';
8 | import useMerge from 'utils/useMerge';
9 | import { typeVariant } from 'utils/variant';
10 | import { shouldForwardProp } from 'utils/withConfig';
11 |
12 | import { ListVariant } from './theme';
13 | import { polyfillTheme } from 'utils/baseStyle';
14 |
15 | export type ListProps =
16 | {
17 | counterReset?: ResponsiveValue;
18 | _items?: {
19 | paddingLeft?: ResponsiveValue>;
20 | marginBottom?: ResponsiveValue>;
21 | color?: ResponsiveValue;
22 | fontSize?: ResponsiveValue>;
23 | lineHeight?: ResponsiveValue>;
24 | counterIncrement?: ResponsiveValue;
25 | };
26 | _bullet?: {
27 | content?: ResponsiveValue;
28 | position?: ResponsiveValue;
29 | color?: ResponsiveValue;
30 | marginRight?: ResponsiveValue>;
31 | width?: ResponsiveValue>;
32 | fontSize?: ResponsiveValue>;
33 | lineHeight?: ResponsiveValue>;
34 | };
35 | }
36 | & TextBaseProps;
37 |
38 | export type ExtendedListProps = {
39 | variant?: ListVariant;
40 | v?: ListVariant;
41 | as?: 'ul' | 'ol';
42 | } & ListProps;
43 |
44 | const List = forwardRef((p, ref) => {
45 | const { list } = useTheme();
46 |
47 | const type = p.v ?? p.variant ?? 'unordered';
48 |
49 | const props = useMerge(p, typeVariant(list, 'unordered', p), list.default);
50 |
51 | return (
52 |
53 | );
54 | });
55 |
56 | List.displayName = 'List';
57 |
58 | export default List;
59 |
60 | const listSystem = system({
61 | counterReset: true
62 | });
63 | const itemSystem = system({
64 | paddingLeft: true,
65 | marginBottom: true,
66 | color: true,
67 | fontSize: true,
68 | lineHeight: true,
69 | counterIncrement: true
70 | });
71 | const bulletSystem = system({
72 | content: true,
73 | position: true,
74 | color: true,
75 | marginRight: true,
76 | width: true,
77 | fontSize: true,
78 | lineHeight: true
79 | });
80 |
81 | const ListStyle = styled.ul.withConfig({ shouldForwardProp })`
82 | ${composedTextBase}
83 |
84 | list-style-type: none;
85 |
86 | & li {
87 | display: flex;
88 |
89 | ${p => itemSystem(polyfillTheme(p._items, p.theme))}
90 |
91 | &:before {
92 | ${p => bulletSystem(polyfillTheme(p._bullet, p.theme))}
93 | }
94 | }
95 |
96 | & li:last-child {
97 | margin-bottom: 0;
98 | }
99 |
100 | ${listSystem}
101 | `;
102 |
--------------------------------------------------------------------------------
/src/components/List/theme.ts:
--------------------------------------------------------------------------------
1 | import { mergeThemes } from 'utils/mergeThemes';
2 |
3 | import { ListProps } from '.';
4 |
5 | export type ListVariant = 'ordered' | 'unordered';
6 |
7 | interface ListValue {
8 | default: ListProps;
9 | variants: Record;
10 | }
11 |
12 | export interface ListTheme {
13 | list: ListValue;
14 | }
15 |
16 | export const list = (l?: {
17 | default?: ListProps;
18 | variants?: Partial>;
19 | }): ListTheme => ({ list: mergeThemes<'variants', ListProps>(l, emptyList) });
20 |
21 | const emptyList: ListValue = {
22 | default: {
23 | lineHeight: '2.4rem',
24 | margin: 0,
25 | padding: 0,
26 | _bullet: {
27 | content: '"\\2022"',
28 | color: '#152028',
29 | marginRight: '1.5rem'
30 | },
31 | _items: {
32 | color: 'text',
33 | paddingLeft: '.5rem',
34 | marginBottom: '1rem'
35 | }
36 | },
37 | variants: {
38 | ordered: {
39 | counterReset: 'list-counter',
40 | _items: {
41 | counterIncrement: 'list-counter'
42 | },
43 | _bullet: {
44 | position: 'relative',
45 | content: 'counter(list-counter)"." !important'
46 | }
47 | },
48 | unordered: {
49 | _bullet: {
50 | fontSize: '2.6rem'
51 | }
52 | }
53 | }
54 | };
55 |
--------------------------------------------------------------------------------
/src/components/LoremIpsum.tsx:
--------------------------------------------------------------------------------
1 | import Text, { TextProps } from 'components/Text';
2 | import { loremIpsum } from 'lorem-ipsum';
3 | import React, { forwardRef, useMemo } from 'react';
4 |
5 | export type LoremIpsumProps = {
6 | count?: number;
7 | units?: 'paragraphs' | 'words' | 'sentences';
8 | random?: boolean;
9 | } & Omit;
10 |
11 | const pseudoRandomGenerator = () => {
12 | let acc = 0.5;
13 | return () => {
14 | acc = acc * 1.6564534 % 1;
15 | return acc;
16 | };
17 | };
18 |
19 | const LoremIpsum = forwardRef(
20 | ({ count, units, random, ...props }, ref) => {
21 | const pseudoRandom = useMemo(() => pseudoRandomGenerator(), []);
22 | const text = useMemo(() =>
23 | loremIpsum({
24 | count,
25 | units: units ?? 'paragraphs',
26 | random: random ? Math.random : pseudoRandom
27 | }),
28 | [count, units]);
29 |
30 | return (
31 | {text}
32 | );
33 | }
34 | );
35 |
36 | export default LoremIpsum;
37 |
--------------------------------------------------------------------------------
/src/components/Modal/ModalProvider.tsx:
--------------------------------------------------------------------------------
1 | import React, { createElement, FC, useState, ComponentType } from 'react';
2 |
3 | import { ModalContext, ModalInstanceContext } from './data';
4 |
5 | interface ModalState {
6 | position: number;
7 | queue: [ComponentType, any][];
8 | }
9 |
10 | const ModalProvider: FC = ({ children }) => {
11 | const [{ position, queue }, setState] = useState({ position: -1, queue: [] });
12 |
13 | const context: ModalContext = {
14 | push: (m, props) => {
15 | setState(s => ({
16 | position: s.position + 1,
17 | queue: [...s.queue, [m, props]]
18 | }));
19 | },
20 | replace: (m, props) => {
21 | setState(s => ({
22 | position: s.position + 1,
23 | queue: [...s.queue.slice(0, -1), [m, props]]
24 | }));
25 | },
26 |
27 | pop: () => {
28 | setState(s => ({
29 | position: s.position - 1,
30 | queue: [...s.queue.slice(0, -1)]
31 | }));
32 | },
33 |
34 | go: (n: number) => setState(s => ({
35 | position: s.position + n,
36 | queue: s.queue
37 | })),
38 | back: () => setState(s => ({
39 | position: s.position - 1,
40 | queue: s.queue
41 | })),
42 | forward: () => setState(s => ({
43 | position: s.position + 1,
44 | queue: s.queue
45 | }))
46 | };
47 |
48 | return (
49 |
50 | {children}
51 | {position !== -1 && queue.map(([m, props], i) => (
52 |
53 | {createElement(m, props)}
54 |
55 | ))}
56 |
57 | );
58 | };
59 |
60 | export default ModalProvider;
61 |
--------------------------------------------------------------------------------
/src/components/Modal/data.ts:
--------------------------------------------------------------------------------
1 | import { ComponentType, createContext, useContext, createElement, ReactNode, ReactElement } from 'react';
2 |
3 | export interface ModalContext {
4 | push: (modal: ComponentType, props?: T) => unknown;
5 | replace: (modal: ComponentType, props?: T) => unknown;
6 |
7 | pop: () => unknown;
8 |
9 | go: (n: number) => unknown;
10 | back: () => unknown;
11 | forward: () => unknown;
12 | }
13 |
14 | export const ModalContext = createContext({
15 | push: () => {},
16 | replace: () => {},
17 |
18 | pop: () => {},
19 |
20 | go: () => {},
21 | back: () => {},
22 | forward: () => {}
23 | });
24 |
25 | interface UseModal {
26 | // do not require props, if m is null or undefined
27 | (): [() => unknown, () => unknown];
28 | (m: null): [() => unknown, () => unknown];
29 | (m: undefined): [() => unknown, () => unknown];
30 | (m: () => ReactElement): [() => unknown, () => unknown];
31 | // open modal without defaultProps
32 | (modal: ComponentType): [OpenModal, () => unknown];
33 | // open modal with defaultProps
34 | (modal: ComponentType, defaultProps: U): [OpenModal & Partial>, () => unknown];
35 | }
36 |
37 | export const useModal: UseModal = (modal?: ComponentType | null, defaultProps?: U) => {
38 | const history = useContext(ModalContext);
39 |
40 | return [
41 | (props: T) => modal
42 | ? history.push(modal, { ...defaultProps, ...props })
43 | : history.pop(),
44 | history.pop,
45 | history
46 | ] as any;
47 | };
48 |
49 | export const useModalHistory = () => useContext(ModalContext);
50 |
51 | interface ModalInstanceContext {
52 | hide: boolean;
53 | }
54 |
55 | export const ModalInstanceContext = createContext({ hide: false });
56 |
57 | type OpenModal = T extends {} ? (props: T) => unknown : () => unknown;
58 |
--------------------------------------------------------------------------------
/src/components/Modal/index.tsx:
--------------------------------------------------------------------------------
1 | import InsetBox from 'components/AbsoluteBox/InsetBox';
2 | import CloseButton, { CloseControlProps } from 'components/CloseControl';
3 | import Flex, { ExtendedFlexProps, FlexProps } from 'components/Flex';
4 | import Typography, { ExtendedTypographyProps } from 'components/Typography';
5 | import React, { FC, useContext, useEffect, useRef } from 'react';
6 | import useTheme from 'useTheme';
7 | import renderComponent, { Renderable } from 'utils/renderComponent';
8 | import useMerge from 'utils/useMerge';
9 | import { sizeVariant } from 'utils/variant';
10 |
11 | import Box, { BoxProps } from '../Box';
12 | import { ModalContext, ModalInstanceContext } from './data';
13 | import { ModalSize } from './theme';
14 |
15 | export type ModalProps = {
16 | title?: Renderable;
17 | _title?: ExtendedTypographyProps;
18 |
19 | header?: Renderable;
20 | _header?: BoxProps;
21 |
22 | _close?: CloseControlProps;
23 | _overlay?: ExtendedFlexProps;
24 | } & FlexProps;
25 |
26 | export type ExtendedModalProps = {
27 | s?: ModalSize;
28 | size?: ModalSize;
29 |
30 | persistent?: boolean;
31 |
32 | onClose?: () => unknown;
33 | } & ModalProps;
34 |
35 | const Modal: FC = ({ children, onClose, persistent, ...p }) => {
36 | const { modal } = useTheme();
37 | const { hide } = useContext(ModalInstanceContext);
38 | const { pop } = useContext(ModalContext);
39 | const ref = useRef(null!);
40 |
41 | const { title, _title, header, _header, _close, _overlay, ...props } = useMerge(
42 | p,
43 | sizeVariant(modal, 'md', p),
44 | modal.default
45 | );
46 |
47 | useEffect(() => {
48 | const onEscape = (e: KeyboardEvent) => {
49 | e.key === 'Escape' && (onClose ?? pop)();
50 | };
51 |
52 | window.addEventListener('keyup', onEscape);
53 |
54 | return () => {
55 | window.removeEventListener('keyup', onEscape);
56 | };
57 | }, [onClose, pop, persistent]);
58 |
59 | return (
60 |
64 | {
73 | !persistent && !ref.current.contains(e.target) && (onClose ?? pop)();
74 | _overlay?.onClick?.(e);
75 | }}
76 | {..._overlay}
77 | >
78 |
79 |
80 |
81 |
82 |
83 | {(header || title) && (
84 |
85 | {header
86 | ? renderComponent(header)
87 | : (
88 |
89 | {renderComponent(title)}
90 |
91 | )}
92 |
93 | )}
94 |
95 | {children}
96 |
97 |
98 |
99 |
100 | );
101 | };
102 |
103 | export default Modal;
104 |
--------------------------------------------------------------------------------
/src/components/Modal/theme.ts:
--------------------------------------------------------------------------------
1 | import { mergeThemes } from 'utils/mergeThemes';
2 |
3 | import { ModalProps } from '.';
4 |
5 | export type ModalSize = 'sm' | 'md' | 'lg' | 'full';
6 |
7 | export interface ModalValue {
8 | default: ModalProps;
9 | sizes: Record;
10 | }
11 |
12 | export interface ModalTheme {
13 | modal: ModalValue;
14 | }
15 |
16 | export const modal = (m?: {
17 | default?: ModalProps;
18 | sizes: Partial>;
19 | }): ModalTheme => ({ modal: mergeThemes<'sizes', ModalProps>(m, emptyModal) });
20 |
21 | const emptyModal: ModalValue = {
22 | default: {
23 | _overlay: {
24 | background: 'rgba(15, 31, 40, 0.5)'
25 | },
26 | _header: {
27 | fontSize: '2rem',
28 | lineHeight: '3rem',
29 | fontFamily: 'rubik'
30 | },
31 | _close: {
32 | position: 'absolute',
33 | top: '1rem',
34 | right: '1rem'
35 | },
36 | bg: '#fff',
37 | borderRadius: '0.3rem',
38 | padding: '3rem',
39 | overflow: 'scroll',
40 | maxHeight: '100%'
41 | },
42 | sizes: {
43 | sm: {
44 | maxWidth: '41rem'
45 | },
46 | md: {
47 | maxWidth: '63rem'
48 | },
49 | lg: {
50 | maxWidth: '85rem'
51 | },
52 | full: {
53 | maxWidth: 'none'
54 | }
55 | }
56 | };
57 |
--------------------------------------------------------------------------------
/src/components/SimpleGrid.tsx:
--------------------------------------------------------------------------------
1 | import { ResponsiveValue } from '@styled-system/core';
2 | import { BoxProps } from 'components/Box';
3 | import Grid, { GridPositionProps } from 'components/Grid';
4 | import * as CSS from 'csstype';
5 | import React, { Children, FC } from 'react';
6 | import useTheme from 'useTheme';
7 | import { parseTwin } from 'utils/gridTemplate';
8 | import { transform } from 'utils/transform';
9 |
10 | export type ColumnResponsiveValue =
11 | | number
12 | | null
13 | | Array
14 | | { [key in string | number]?: number } & { _: number };
15 |
16 | export type SimpleGridProps = {
17 | columns: ColumnResponsiveValue;
18 | unit?: ResponsiveValue;
19 |
20 | gap?: ResponsiveValue>;
21 | } & GridPositionProps & BoxProps;
22 |
23 | const SimpleGrid: FC = ({ columns, unit: _unit, children, gap: _gap, ...props }) => {
24 | const { breakpoints } = useTheme();
25 | const t = transform(breakpoints);
26 |
27 | const cols = t(columns);
28 | const gapRow = t(t(_gap).map(g => g && parseTwin(g)[1]));
29 | const unit = t(_unit);
30 |
31 | return (
32 | `repeat(${c}, ${unit.get(i) ?? '1fr'})`)}
34 | rows={breakpoints.map((_, i) =>
35 | !gapRow.empty(i) || !cols.empty(i) || !unit.empty(i)
36 | ? `repeat(${Math.ceil(Children.count(children) / cols.get(i)!)}, ${unit.get(i) ?? '1fr'})`
37 | : null
38 | )}
39 | gap={_gap}
40 | {...props}
41 | >
42 | {children}
43 |
44 | );
45 | };
46 |
47 | export default SimpleGrid;
48 |
--------------------------------------------------------------------------------
/src/components/Spinner.tsx:
--------------------------------------------------------------------------------
1 | import { BoxBaseProps, composedBoxBase } from 'bases';
2 | import React, { FC } from 'react';
3 | import styled, { keyframes } from 'styled-components';
4 | import { shouldForwardProp } from 'utils/withConfig';
5 |
6 | export type SpinnerProps = {
7 | speed?: string;
8 | } & BoxBaseProps;
9 |
10 | const Spinner: FC = props => {
11 | return (
12 |
25 | );
26 | };
27 |
28 | export default Spinner;
29 |
30 | const spin = keyframes`
31 | 0% {
32 | transform: rotate(0deg);
33 | }
34 | 100% {
35 | transform: rotate(360deg);
36 | }
37 | `;
38 |
39 | const SpinnerStyle = styled.div.withConfig({ shouldForwardProp })`
40 | ${composedBoxBase}
41 |
42 | animation: ${spin} ${({ speed }) => speed} linear infinite;
43 | `;
44 |
--------------------------------------------------------------------------------
/src/components/Stack.tsx:
--------------------------------------------------------------------------------
1 | import { ResponsiveValue } from '@styled-system/core';
2 | import Box from 'components/Box';
3 | import Flex, { FlexProps } from 'components/Flex';
4 | import React, { Children, cloneElement, FC, isValidElement, ReactNode } from 'react';
5 | import useTheme from 'useTheme';
6 | import CSS from 'csstype';
7 |
8 | import { transform } from '../utils/transform';
9 |
10 | export interface StackProps extends FlexProps {
11 | gap?: ResponsiveValue;
12 | wrapItems?: boolean;
13 |
14 | children: ReactNode;
15 |
16 | direction?: FlexProps['flexDirection'];
17 | align?: FlexProps['alignItems'];
18 | justify?: FlexProps['justifyContent'];
19 | wrap?: FlexProps['flexWrap'];
20 | }
21 |
22 | export type ExtendedStackProps = StackProps;
23 |
24 | const Stack: FC = ({
25 | direction: _direction,
26 | align,
27 | justify,
28 | wrap,
29 | gap: _gap,
30 | children,
31 | wrapItems,
32 | ...props
33 | }) => {
34 | const { breakpoints } = useTheme();
35 | const t = transform(breakpoints);
36 |
37 | const direction = t(_direction ?? 'row');
38 | const gap = t(_gap);
39 |
40 | const itemStyle = ['_', ...breakpoints.aliases].reduce(
41 | (acc, val) => {
42 | const d = direction.get(val);
43 | const g = gap.get(val);
44 | return {
45 | mb: [...acc.mb, d === 'column' ? g : 0],
46 | mr: [...acc.mr, d === 'row' ? g : 0]
47 | };
48 | },
49 | {
50 | mb: [] as (string | number | null)[],
51 | mr: [] as (string | number | null)[]
52 | }
53 | );
54 |
55 | const maxWidth = direction.map(d => d === 'column' ? '100%' : null);
56 |
57 | const getStyle = (i: number) => {
58 | const last = Children.count(children) === i + 1;
59 | return {
60 | mb: last ? 0 : itemStyle.mb,
61 | mr: last ? 0 : itemStyle.mr,
62 | maxWidth
63 | };
64 | };
65 |
66 | return (
67 |
74 | {Children.map(children, (child, i) =>
75 | wrapItems
76 | ? {child}
77 | : isValidElement(child)
78 | ? cloneElement(child, getStyle(i))
79 | : child
80 | )}
81 |
82 |
83 | );
84 | };
85 |
86 | export default Stack;
87 |
--------------------------------------------------------------------------------
/src/components/Tag/index.tsx:
--------------------------------------------------------------------------------
1 | import { flexBase, FlexBaseProps, textBase, TextBaseProps } from 'bases';
2 | import Complement, { comp, ComplementProps } from 'components/Complement';
3 | import React, { FC, forwardRef, ReactNode } from 'react';
4 | import styled from 'styled-components';
5 | import useTheme from 'useTheme';
6 | import { compose } from 'utils/baseStyle';
7 | import useMerge from 'utils/useMerge';
8 | import { typeVariant } from 'utils/variant';
9 | import { shouldForwardProp } from 'utils/withConfig';
10 |
11 | import { TagVariant } from './theme';
12 |
13 | export type TagProps =
14 | & ComplementProps
15 | & FlexBaseProps
16 | & TextBaseProps;
17 |
18 | export type ExtendedTagProps = {
19 | variant?: TagVariant;
20 | v?: TagVariant;
21 | } & TagProps;
22 |
23 | const Tag = forwardRef(({ children, ...p }, ref) => {
24 | const { tag } = useTheme();
25 |
26 | const m = useMerge(
27 | p,
28 | typeVariant(tag, 'solid', p),
29 | tag.default
30 | );
31 | const [left, right, props] = comp(m);
32 |
33 | return (
34 |
42 |
43 | {children}
44 |
45 |
46 | );
47 | });
48 |
49 | export default Tag;
50 |
51 | const TagStyle = styled.div.withConfig({ shouldForwardProp })`
52 | ${compose(flexBase, textBase)}
53 | `;
54 |
--------------------------------------------------------------------------------
/src/components/Tag/theme.ts:
--------------------------------------------------------------------------------
1 | import { mergeThemes } from 'utils/mergeThemes';
2 |
3 | import { TagProps } from '.';
4 |
5 | export type TagVariant = 'solid' | 'outline' | 'clear';
6 |
7 | interface TagValue {
8 | default: TagProps;
9 | variants: Record;
10 | }
11 |
12 | export interface TagTheme {
13 | tag: TagValue;
14 | }
15 |
16 | export const tag = (t?: {
17 | default?: TagProps;
18 | variants?: Partial>;
19 | }): TagTheme => ({ tag: mergeThemes<'variants', TagProps>(t, emptyTag) });
20 |
21 | const emptyTag: TagValue = {
22 | default: {
23 | borderRadius: '0.3rem',
24 | border: '1px solid #455663',
25 | px: '0.8rem',
26 | fontFamily: 'text',
27 | fontSize: '1.2rem',
28 | fontWeight: 500,
29 | lineHeight: '2rem'
30 | },
31 | variants: {
32 | solid: {
33 | bg: '#455663',
34 | color: 'background'
35 | },
36 | outline: {
37 | color: '#0f1f28'
38 | },
39 | clear: {
40 | borderColor: 'transparent'
41 | }
42 | }
43 | };
44 |
--------------------------------------------------------------------------------
/src/components/Text/aliases.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef } from 'react';
2 |
3 | import Text, { ExtendedTextProps } from '.';
4 | import { TextVariant } from './theme';
5 |
6 | type TextAliasProps = Omit;
7 |
8 | const makeAlias = (v: TextVariant) =>
9 | forwardRef((p, ref) => );
10 |
11 | export const Span = makeAlias('span');
12 | export const Em = makeAlias('em');
13 | export const Strong = makeAlias('strong');
14 | export const Underline = makeAlias('underline');
15 | export const Abbr = makeAlias('abbr');
16 | export const Strikethrough = makeAlias('strikethrough');
17 | export const Sub = makeAlias('sub');
18 | export const Sup = makeAlias('sup');
19 |
--------------------------------------------------------------------------------
/src/components/Text/index.tsx:
--------------------------------------------------------------------------------
1 | import { composedTextBase, TextBaseProps } from 'bases';
2 | import React, { forwardRef, ReactNode } from 'react';
3 | import styled from 'styled-components';
4 | import useTheme from 'useTheme';
5 | import useMerge from 'utils/useMerge';
6 | import { typeVariant } from 'utils/variant';
7 | import { shouldForwardProp } from 'utils/withConfig';
8 |
9 | import { TextAs, TextVariant } from './theme';
10 |
11 | export type TextProps = TextBaseProps;
12 |
13 | export interface ExtendedTextProps extends TextProps {
14 | v?: TextVariant;
15 | variant?: TextVariant;
16 | as?: TextAs;
17 | }
18 |
19 | const Text = forwardRef(({ as: _as, ...p }, ref) => {
20 | const { text } = useTheme();
21 |
22 | const as = (_as ?? {
23 | span: 'span',
24 | em: 'em',
25 | strong: 'strong',
26 | underline: 'u',
27 | abbr: 'abbr',
28 | strikethrough: 's',
29 | sub: 'sub',
30 | sup: 'sup'
31 | }[p.variant ?? p.v ?? 'span']) as TextAs;
32 |
33 | const props = useMerge(
34 | p,
35 | typeVariant(text, 'span', p),
36 | text.default
37 | );
38 |
39 | return (
40 |
41 | );
42 | });
43 |
44 | const TextStyle = styled.span.withConfig({ shouldForwardProp })`
45 | ${composedTextBase}
46 | `;
47 |
48 | export default Text;
49 |
--------------------------------------------------------------------------------
/src/components/Text/theme.ts:
--------------------------------------------------------------------------------
1 | import { TextProps } from '.';
2 | import { mergeThemes } from 'utils/mergeThemes';
3 |
4 | export type TextVariant =
5 | | 'span'
6 | | 'em'
7 | | 'strong'
8 | | 'underline'
9 | | 'abbr'
10 | | 'strikethrough'
11 | | 'sub'
12 | | 'sup';
13 |
14 | export type TextAs =
15 | | 'span'
16 | | 'em'
17 | | 'strong'
18 | | 'u'
19 | | 'abbr'
20 | | 's'
21 | | 'sub'
22 | | 'sup'
23 | | 'time';
24 |
25 | interface TextValue {
26 | default: TextProps;
27 | variants: Record;
28 | }
29 |
30 | export interface TextTheme {
31 | text: TextValue;
32 | }
33 |
34 | export const text = (t?: {
35 | default?: TextProps;
36 | variants?: Partial>;
37 | }): TextTheme => ({
38 | text: mergeThemes<'variants', TextProps>(t, emptyText)
39 | });
40 |
41 | const emptyText: TextValue = {
42 | default: {
43 | fontFamily: 'text',
44 | fontSize: '1.5rem',
45 | lineHeight: '2rem',
46 | transition: 'color 300ms',
47 | color: 'text'
48 | },
49 | variants: {
50 | span: {},
51 | em: {},
52 | strong: {},
53 | underline: {},
54 | abbr: {},
55 | strikethrough: {},
56 | sub: {},
57 | sup: {}
58 | }
59 | };
60 |
--------------------------------------------------------------------------------
/src/components/Tooltip/index.tsx:
--------------------------------------------------------------------------------
1 | import OutsetBox, { OutsetBoxTarget } from 'components/AbsoluteBox/OutsetBox';
2 | import Box, { BoxProps } from 'components/Box';
3 | import Flex from 'components/Flex';
4 | import React, { FC } from 'react';
5 |
6 | export type TooltipProps = {
7 | _outset: BoxProps;
8 | } & BoxProps;
9 |
10 | export type ExtendedTooltipProps = {
11 | target: OutsetBoxTarget;
12 | };
13 |
14 | const Tooltip: FC = ({ target, ...p }) => {
15 | return (
16 |
17 |
18 | Tooltip
19 |
20 |
21 | );
22 | };
23 |
24 | export default Tooltip;
25 |
--------------------------------------------------------------------------------
/src/components/Tooltip/theme.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xcorejs/ui/37ea8728271f8be53a3c21f4064363af2486c813/src/components/Tooltip/theme.ts
--------------------------------------------------------------------------------
/src/components/Typography/aliases.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef } from 'react';
2 |
3 | import Typography, { ExtendedTypographyProps } from '.';
4 | import { TypographyVariant } from './theme';
5 |
6 | type TypograhyAliasProps = Omit;
7 |
8 | const makeAlias = (v: TypographyVariant) =>
9 | forwardRef((p, ref) => );
10 |
11 | export const Heading1 = makeAlias('h1');
12 | export const Heading2 = makeAlias('h2');
13 | export const Heading3 = makeAlias('h3');
14 | export const Heading4 = makeAlias('h4');
15 | export const Heading5 = makeAlias('h5');
16 | export const Heading6 = makeAlias('h6');
17 | export const Paragraph = makeAlias('p');
18 | export const Lead = makeAlias('lead');
19 |
--------------------------------------------------------------------------------
/src/components/Typography/index.tsx:
--------------------------------------------------------------------------------
1 | import { composedTextBase, TextBaseProps } from 'bases';
2 | import React, { forwardRef, ReactNode } from 'react';
3 | import styled from 'styled-components';
4 | import useTheme from 'useTheme';
5 | import useMerge from 'utils/useMerge';
6 | import { typeVariant } from 'utils/variant';
7 | import { shouldForwardProp } from 'utils/withConfig';
8 |
9 | import { TypographyAs, TypographyVariant } from './theme';
10 |
11 | export type TypographyProps = TextBaseProps;
12 |
13 | export type ExtendedTypographyProps =
14 | {
15 | variant?: TypographyVariant;
16 | v?: TypographyVariant;
17 | as?: TypographyAs;
18 | }
19 | & TypographyProps;
20 |
21 | const Typography = forwardRef(({ as: _as, ...p }, ref) => {
22 | const { typography } = useTheme();
23 | const type = p.variant ?? p.v ?? 'p';
24 |
25 | const as: TypographyAs = _as ?? (type === 'lead' ? 'p' : type);
26 |
27 | const props = useMerge(
28 | p,
29 | typeVariant(typography, 'p', p),
30 | typography.default
31 | );
32 |
33 | return (
34 |
35 | );
36 | });
37 |
38 | export default Typography;
39 |
40 | const TypographyStyle = styled.p.withConfig({ shouldForwardProp })`
41 | ${composedTextBase}
42 | `;
43 |
--------------------------------------------------------------------------------
/src/components/Typography/theme.ts:
--------------------------------------------------------------------------------
1 | import { TextProps } from 'components/Text';
2 | import { mergeThemes } from 'utils/mergeThemes';
3 |
4 | interface TypographyValue {
5 | default: TextProps;
6 | variants: Record;
7 | }
8 |
9 | export type TypographyVariant = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'lead';
10 |
11 | export type TypographyAs = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'div';
12 |
13 | export interface TypographyTheme {
14 | typography: TypographyValue;
15 | }
16 |
17 | export const typography = (t?: {
18 | default?: TextProps;
19 | variants?: Partial>;
20 | }) => ({ typography: mergeThemes<'variants', TextProps>(t, emptyTypography) });
21 |
22 | const emptyTypography: TypographyValue = {
23 | default: {
24 | fontFamily: 'heading',
25 | margin: 0,
26 | color: 'text'
27 | },
28 | variants: {
29 | p: {
30 | fontSize: '1.6rem',
31 | lineHeight: '2rem'
32 | },
33 | h1: {
34 | fontSize: '4.4rem',
35 | fontWeight: 500,
36 | lineHeight: '6.6rem'
37 | },
38 | h2: {
39 | fontSize: '3.2rem',
40 | fontWeight: 500,
41 | lineHeight: '4.8rem'
42 | },
43 | h3: {
44 | fontSize: '2.4rem',
45 | fontWeight: 500,
46 | lineHeight: '3.6rem'
47 | },
48 | h4: {
49 |
50 | },
51 | h5: {
52 |
53 | },
54 | h6: {
55 |
56 | },
57 | lead: {}
58 | }
59 | };
60 |
--------------------------------------------------------------------------------
/src/components/XcoreGlobal/index.tsx:
--------------------------------------------------------------------------------
1 | import { globalBase, selectionBase } from 'bases';
2 | import React, { FC } from 'react';
3 | import { createGlobalStyle } from 'styled-components';
4 | import useTheme from 'useTheme';
5 | import { compose } from 'utils/baseStyle';
6 |
7 | import { GlobalValue } from './theme';
8 |
9 | const XcoreGlobal: FC = () => {
10 | const { global } = useTheme();
11 | return (
12 |
13 | );
14 | };
15 |
16 | export default XcoreGlobal;
17 |
18 | const base = compose(globalBase);
19 |
20 | const GlobalStyle = createGlobalStyle`
21 | html {
22 | ${p => base({ ...p._html, theme: p.theme })}
23 | }
24 |
25 | body {
26 | ${p => base({ ...p._body, theme: p.theme })}
27 | }
28 |
29 | *, *:before, *:after {
30 | ${p => base({ ...p._all, theme: p.theme })}
31 | }
32 |
33 | ::-moz-selection {
34 | ${p => selectionBase({ ...p._selection, theme: p.theme })}
35 | }
36 |
37 | ::selection {
38 | ${p => selectionBase({ ...p._selection, theme: p.theme })}
39 | }
40 | `;
41 |
--------------------------------------------------------------------------------
/src/components/XcoreGlobal/theme.ts:
--------------------------------------------------------------------------------
1 | import { GlobalBaseProps, SelectionBaseProps } from 'bases';
2 | import { merge } from 'utils/merge';
3 |
4 | export type GlobalValue = {
5 | _html: GlobalBaseProps;
6 | _body: GlobalBaseProps;
7 | _all: GlobalBaseProps;
8 | _selection: SelectionBaseProps;
9 | };
10 |
11 | export interface GlobalTheme {
12 | global: GlobalValue;
13 | }
14 |
15 | export const global = (g: Partial = emptyGlobal): GlobalTheme => ({
16 | global: merge(g as GlobalValue, emptyGlobal)
17 | });
18 |
19 | const emptyGlobal: GlobalValue = {
20 | _html: {
21 | fontSize: '10px',
22 | webkitFontSmoothing: 'antialiased',
23 | m: 0,
24 | p: 0
25 | },
26 | _body: {
27 | m: 0,
28 | p: 0,
29 | bg: 'background'
30 | },
31 | _all: {
32 | boxSizing: 'border-box'
33 | },
34 | _selection: {
35 | color: 'background',
36 | backgroundColor: 'primary'
37 | }
38 | };
39 |
--------------------------------------------------------------------------------
/src/components/XcoreProvider/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { ThemeProvider } from 'styled-components';
3 |
4 | import { XcoreTheme, emptyTheme } from 'theme';
5 | import ModalProvider from 'components/Modal/ModalProvider';
6 | import XcoreGlobal from 'components/XcoreGlobal';
7 |
8 | export type XcoreProviderProps = {
9 | theme?: XcoreTheme | null;
10 | disableGlobalStyles?: boolean;
11 | };
12 |
13 | const XcoreProvider: FC = ({ children, theme, disableGlobalStyles }) => {
14 | return theme !== null
15 | ? (
16 |
17 | {!disableGlobalStyles && }
18 | {children}
19 |
20 | )
21 | : {children};
22 | };
23 |
24 | export default XcoreProvider;
25 |
--------------------------------------------------------------------------------
/src/hooks/useDisclosure.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | const useDisclosure = (initialState: boolean = false) => {
4 | const [isOpen, setState] = useState(initialState);
5 |
6 | const open = () => setState(true);
7 | const close = () => setState(false);
8 | const toggle = () => setState(!isOpen);
9 |
10 | return {
11 | isOpen,
12 | open,
13 | close,
14 | toggle
15 | };
16 | };
17 |
18 | export default useDisclosure;
19 |
--------------------------------------------------------------------------------
/src/icons/close.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 | import React, { FC } from 'react';
3 |
4 | export const CloseIcon: FC = () => (
5 |
12 | );
13 |
--------------------------------------------------------------------------------
/src/scales/breakpoints.ts:
--------------------------------------------------------------------------------
1 | import { Scale, scale } from './utils';
2 |
3 | export type Breakpoints = Scale;
4 |
5 | export type BreakpointScale = {
6 | breakpoints: Breakpoints;
7 | };
8 |
9 | export const breakpoints = (
10 | b: string[] = ['30em', '48em', '64em', '78em', '85em'],
11 | aliases: string[] = ['xs', 'sm', 'md', 'lg', 'xl']
12 | ) => ({ breakpoints: scale(b, aliases) });
13 |
--------------------------------------------------------------------------------
/src/scales/colors.ts:
--------------------------------------------------------------------------------
1 | import { defaultsScale } from './utils';
2 | import { get } from '@styled-system/core';
3 | import * as polished from 'polished';
4 |
5 | export type Colors = {
6 | primary: string;
7 | text: string;
8 | background: string;
9 | };
10 |
11 | export type ColorScale = {
12 | colors: Colors;
13 | };
14 |
15 | export const colors = (base: Colors = lightColorTheme, c: T & Partial = {} as T) =>
16 | ({ colors: defaultsScale(c, base) as Colors & T });
17 |
18 | export const lightColorTheme: Colors = {
19 | primary: '#0171b6',
20 | text: '#1E3441',
21 | background: '#fff'
22 | };
23 |
24 | export const darkColorTheme: Colors = {
25 | primary: '#DAA520',
26 | text: '#fff',
27 | background: '#211E15'
28 | };
29 |
30 | export const colorMod = (func: string) => (color: string, amount: number) => `__$${func};${color};${amount}`;
31 |
32 | export const darken = colorMod('darken');
33 | export const lighten = colorMod('lighten');
34 |
35 | export const opacify = colorMod('opacify');
36 | export const transparentize = colorMod('transparentize');
37 |
38 | export const saturate = colorMod('saturate');
39 | export const desaturate = colorMod('desaturate');
40 |
41 | export const shade = colorMod('shade');
42 | export const tint = colorMod('tint');
43 |
44 | export const adjustHue = colorMod('adjustHue');
45 |
46 | export const colorTransform = (val: string | number, s: any) => {
47 | const scale = s as Colors;
48 | if (typeof val === 'string' && val.startsWith('__')) {
49 | const reg = /__\$(.+);(\S+);(\S+)/.exec(val);
50 | if (reg) {
51 | const [_, func, color, amount] = reg;
52 | const a = Number(amount);
53 | const value = get(scale, color, lightColorTheme[color as keyof Colors] || color);
54 |
55 | switch (func) {
56 | case 'darken':
57 | return polished.darken(a, value);
58 | case 'lighten':
59 | return polished.lighten(a, value);
60 | case 'opacify':
61 | return polished.opacify(a, value);
62 | case 'transparentize':
63 | return polished.transparentize(a, value);
64 | case 'saturate':
65 | return polished.saturate(a, value);
66 | case 'desaturate':
67 | return polished.desaturate(a, value);
68 | case 'shade':
69 | return polished.shade(a, value);
70 | case 'tint':
71 | return polished.tint(a, value);
72 | case 'adjustHue':
73 | return polished.adjustHue(a, value);
74 | }
75 | }
76 |
77 | return get(scale, val, lightColorTheme[val as keyof Colors] || val);
78 | }
79 | return get(scale, val, lightColorTheme[val as keyof Colors] || val);
80 | };
81 |
--------------------------------------------------------------------------------
/src/scales/fonts.ts:
--------------------------------------------------------------------------------
1 | import { defaultsScale } from './utils';
2 |
3 | export type Fonts = {
4 | heading: string;
5 | text: string;
6 | };
7 |
8 | export type FontScale = {
9 | fonts: Fonts;
10 | };
11 |
12 | export const fonts = (f: Partial = emptyFonts as T) =>
13 | ({ fonts: defaultsScale(f, emptyFonts) as T });
14 |
15 | const emptyFonts: Fonts = {
16 | // eslint-disable-next-line max-len
17 | heading: '"rubik",-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji',
18 | text: '"rubik",-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji'
19 | };
20 |
--------------------------------------------------------------------------------
/src/scales/index.ts:
--------------------------------------------------------------------------------
1 | import { breakpoints, BreakpointScale } from './breakpoints';
2 | import { colors, ColorScale } from './colors';
3 | import { fonts, FontScale } from './fonts';
4 |
5 | export type Scales =
6 | & ColorScale
7 | & FontScale
8 | & BreakpointScale;
9 |
10 | export const createScales = (scale: Partial = {}): Scales => ({
11 | ...colors(),
12 | ...breakpoints(),
13 | ...fonts(),
14 | ...scale
15 | });
16 |
--------------------------------------------------------------------------------
/src/scales/utils.ts:
--------------------------------------------------------------------------------
1 | export type Scale = TValue[] & Record & { aliases: TKey[] };
2 |
3 | export const scale = (values: TValue[], aliases: TKey[]): Scale => {
4 | const s = aliases.reduce((acc, val, i) => {
5 | // @ts-ignore
6 | acc[val] = values[i];
7 | return acc;
8 | }, [...values] as Scale);
9 |
10 | s.aliases = aliases;
11 |
12 | return s;
13 | };
14 |
15 | export const defaultsScale = (target: TTarget, ...sources: TSource[]) =>
16 | sources.reduce((acc, source) => defaultsScalePair(acc, source), target);
17 |
18 | const defaultsScalePair = (t: TTarget, s: TSource) => Object.keys(s).reduce((acc, k) => ({
19 | ...acc,
20 | [k]: typeof acc[k] === 'object' && !Array.isArray(t)
21 | ? { ...s[k], ...acc[k] }
22 | : acc[k] ?? s[k]
23 | }), t as TTarget & TSource);
24 |
--------------------------------------------------------------------------------
/src/theme.ts:
--------------------------------------------------------------------------------
1 | import { button, ButtonTheme } from './components/Button/theme';
2 | import { card, CardTheme } from './components/Card/theme';
3 | import { container, ContainerTheme } from './components/Container/theme';
4 | import { link, LinkTheme } from './components/Link/theme';
5 | import { list, ListTheme } from './components/List/theme';
6 | import { tag, TagTheme } from './components/Tag/theme';
7 | import { text, TextTheme } from './components/Text/theme';
8 | import { typography, TypographyTheme } from './components/Typography/theme';
9 | import { global, GlobalTheme } from './components/XcoreGlobal/theme';
10 | import { createScales, Scales } from './scales';
11 | import { CloseControlTheme, closeControl } from './components/CloseControl/theme';
12 | import { ModalTheme, modal } from './components/Modal/theme';
13 |
14 | export interface XcoreThemeBase {
15 | name: string;
16 | }
17 |
18 | export type XcoreTheme =
19 | & Scales
20 | & XcoreThemeBase
21 | & GlobalTheme
22 | & ContainerTheme
23 | & TextTheme
24 | & ButtonTheme
25 | & TypographyTheme
26 | & LinkTheme
27 | & TagTheme
28 | & CardTheme
29 | & ListTheme
30 | & CloseControlTheme
31 | & ModalTheme;
32 |
33 | export const createTheme = (theme: Partial = {}): XcoreTheme => ({
34 | ['__xcoreTheme' as any]: true,
35 | name: 'Xcore',
36 | ...global(),
37 | ...container(),
38 | ...text(),
39 | ...button(),
40 | ...typography(),
41 | ...link(),
42 | ...tag(),
43 | ...card(),
44 | ...list(),
45 | ...createScales(),
46 | ...closeControl(),
47 | ...modal(),
48 | ...theme
49 | });
50 |
51 | export const emptyTheme = createTheme();
52 |
53 | export { container } from './components/Container/theme';
54 | export { text, TextAs, TextVariant } from './components/Text/theme';
55 | export { button, ButtonSize, ButtonVariant, ButtonAs } from './components/Button/theme';
56 | export { global } from './components/XcoreGlobal/theme';
57 | export { typography, TypographyVariant, TypographyAs } from './components/Typography/theme';
58 | export { link, LinkVariant, LinkAs } from './components/Link/theme';
59 | export { list, ListVariant } from './components/List/theme';
60 | export { tag, TagVariant } from './components/Tag/theme';
61 | export { card, CardVariant } from './components/Card/theme';
62 |
--------------------------------------------------------------------------------
/src/useTheme.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import { ThemeContext } from 'styled-components';
3 |
4 | import { XcoreTheme, emptyTheme } from './theme';
5 |
6 | const useTheme = (): XcoreTheme => {
7 | const theme = useContext(ThemeContext) as XcoreTheme | undefined;
8 |
9 | return theme ?? emptyTheme;
10 | };
11 |
12 | export default useTheme;
13 |
--------------------------------------------------------------------------------
/src/utils/baseStyle.ts:
--------------------------------------------------------------------------------
1 | import { FlattenInterpolation, ThemeProps, css } from 'styled-components';
2 |
3 | import { XcoreTheme, emptyTheme } from '../theme';
4 |
5 | type Style = FlattenInterpolation>;
6 |
7 | export interface BaseStyle {
8 | (p: T): Style;
9 | inherits?: BaseStyle[];
10 | }
11 |
12 | export const base = (inherits: BaseStyle[], b: (props: T) => Style): BaseStyle => {
13 | const f = (p: T) => b(p);
14 | (f as BaseStyle).inherits = inherits;
15 |
16 | return f;
17 | };
18 |
19 | export const polyfillTheme = (
20 | p: T,
21 | theme: XcoreTheme = emptyTheme
22 | ): T & { theme: XcoreTheme } =>
23 | p &&
24 | (!('theme' in p) || (p as any).theme === undefined || Object.keys((p as any).theme).length === 0
25 | ? {
26 | ...p,
27 | theme
28 | }
29 | : p as T & { theme: XcoreTheme });
30 |
31 | export const compose = (...bases: BaseStyle[]) => {
32 | const flattenBases = Array.from(new Set(flatten(bases)));
33 | return (p: T & { theme: {} }) => flattenBases.map(b => b(polyfillTheme(p)));
34 | };
35 |
36 | const flatten = (bases: BaseStyle[]): BaseStyle[] => bases.reduce(
37 | (acc, b) => b.inherits
38 | ? [...acc, ...flatten(b.inherits)]
39 | : acc,
40 | bases
41 | );
42 |
--------------------------------------------------------------------------------
/src/utils/gridTemplate.ts:
--------------------------------------------------------------------------------
1 | const repeatRegex = /(repeat\()([0-9]+)( *, *)(.+)(\))/;
2 |
3 | type ParseState = [string[], string, number];
4 |
5 | // Basicaly split by " " but dont split functions like repeat
6 | export const parseTemplate = (template: string, gap?: string | null): string[] =>
7 | parse(
8 | template
9 | .split('')
10 | .reduce(([result, current, indent], val) => val === ' ' && indent === 0
11 | ? parse([result, current, indent], gap)
12 | : val === '('
13 | ? [result, current + val, indent + 1] as ParseState
14 | : val === ')'
15 | ? [result, current + val, indent - 1] as ParseState
16 | : [result, current + val, indent] as ParseState
17 | , [[], '', 0] as ParseState),
18 | gap,
19 | true
20 | )[0];
21 |
22 | const parse = ([result, current]: ParseState, gap?: string | null, last?: boolean): ParseState => [
23 | [
24 | ...result,
25 | ...repeatRegex.test(current)
26 | ? repeat(current, gap)
27 | : [current],
28 | ...!last && gap ? [gap] : []
29 | ],
30 | '',
31 | 0
32 | ];
33 |
34 | const repeat = (r: string, gap?: string | null) => {
35 | const [_, __, count, ___, val] = repeatRegex.exec(r)!;
36 | const n = parseInt(count);
37 |
38 | return getTemplate(n, val, gap);
39 | };
40 |
41 | export const getTemplate = (n: number, val: string, gap?: string | null) => {
42 | const length = gap ? n * 2 - 1 : n;
43 | return [...Array(length >= 0 ? length : 0)].map((v, i) => i % 2 === 1 ? gap ?? val : val);
44 | };
45 |
46 | export const parseTwin = (val: T): [string | T, string | T] => {
47 | if (!val) {
48 | return [null as T, null as T];
49 | }
50 | const [a, b] = val.split(' ');
51 |
52 | return [
53 | a,
54 | b || a
55 | ];
56 | };
57 |
--------------------------------------------------------------------------------
/src/utils/isEmptyValue.ts:
--------------------------------------------------------------------------------
1 | import { ResponsiveValue } from '@styled-system/core';
2 |
3 | export const isValueEmpty = (value: ResponsiveValue): boolean =>
4 | typeof value !== 'object'
5 | ? value === null || value === undefined
6 | : Array.isArray(value)
7 | ? value.length === 0
8 | : Object.keys(value as object).length === 0;
9 |
--------------------------------------------------------------------------------
/src/utils/isIE.ts:
--------------------------------------------------------------------------------
1 | export const isIE = () => true;// window.navigator.userAgent.includes('MSIE');
2 |
--------------------------------------------------------------------------------
/src/utils/mediaQuery.ts:
--------------------------------------------------------------------------------
1 | import { css } from 'styled-components';
2 | import { Breakpoints } from 'scales/breakpoints';
3 |
4 | export const mediaQueries = (breakpoints: Breakpoints, predicate: (i: number, br: string) => any) =>
5 | breakpoints
6 | .map((br, i) => {
7 | const val = filterVal(predicate(i, br));
8 |
9 | return isValueEmpty(val)
10 | ? ''
11 | : i === 0
12 | ? val
13 | : css`
14 | @media screen and (min-width: ${breakpoints[i - 1]}) {
15 | ${val}
16 | }
17 | `;
18 | });
19 |
20 | const isValueEmpty = (val: T) =>
21 | // eslint-disable-next-line @typescript-eslint/no-extra-parens
22 | val === false || val === undefined || (Array.isArray(val) && val.length === 0);
23 |
24 | const filterVal = (val: T) => Array.isArray(val) ? val.filter(Boolean) : val;
25 |
--------------------------------------------------------------------------------
/src/utils/merge.ts:
--------------------------------------------------------------------------------
1 |
2 | export const merge = (target: T, ...sources: T[]): T => {
3 | const next = appendTo({} as T, target);
4 | sources.forEach(s => appendTo(next, s));
5 | return appendTo({} as Required, next as Required);
6 | };
7 |
8 | const appendTo = (t: T, s: T): T => {
9 | Object.keys(s).reverse().forEach(k => {
10 | t[k] && k[0] === '_' && typeof t[k] === 'object'
11 | ? appendTo(t[k], s[k])
12 | : !(k in t) &&
13 | (t[k] = s[k]);
14 | });
15 | return t;
16 | };
17 |
--------------------------------------------------------------------------------
/src/utils/mergeThemes.ts:
--------------------------------------------------------------------------------
1 | import { merge } from './merge';
2 |
3 | type ThemeItem = {
4 | [P in Exclude]: Record;
5 | } & {
6 | default: TValue;
7 | };
8 |
9 | type PartialThemeItem = {
10 | [P in Exclude]?: Partial>> | undefined;
11 | } & {
12 | default?: Partial;
13 | };
14 |
15 | export const mergeThemes = <
16 | TKey extends string,
17 | TValue extends {},
18 | >(
19 | target: PartialThemeItem | undefined,
20 | source: ThemeItem
21 | ): ThemeItem =>
22 | target ? mergeThemePair(target, source) : source;
23 |
24 | const mergeThemePair = <
25 | TKey extends string,
26 | TValue extends {},
27 | >(
28 | target: PartialThemeItem,
29 | source: ThemeItem
30 | ): ThemeItem => {
31 | const next: Record = {
32 | ...target,
33 | default: target.default
34 | ? merge(target.default, source.default) as any
35 | : source.default
36 | };
37 |
38 | Object.keys(source).forEach(
39 | k => k !== 'default' && (next[k] = mergeVariantPair((target as any)[k], source[k]))
40 | );
41 |
42 | return next as ThemeItem;
43 | };
44 |
45 | const mergeVariantPair = <
46 | TTarget extends Record,
47 | TSource extends Record
48 | >(
49 | target: TTarget,
50 | source: TSource
51 | ): TTarget & TSource => {
52 | const next = { ...target } as Record;
53 |
54 | Object.keys(source).forEach(
55 | k => {
56 | next[k] = target && target[k as keyof TTarget]
57 | ? merge(target[k as keyof TTarget]!, source[k] as any)
58 | : source[k];
59 | }
60 | );
61 |
62 | return next as any;
63 | };
64 |
--------------------------------------------------------------------------------
/src/utils/renderComponent.ts:
--------------------------------------------------------------------------------
1 | import { ComponentType, ReactNode, isValidElement, createElement } from 'react';
2 |
3 | export type Renderable = ReactNode | ComponentType;
4 |
5 | const renderComponent = (c: Renderable): ReactNode =>
6 | typeof c === 'function' ? createElement(c as ComponentType) : c;
7 |
8 | export default renderComponent;
9 |
--------------------------------------------------------------------------------
/src/utils/transform.ts:
--------------------------------------------------------------------------------
1 | import { Breakpoints } from 'scales/breakpoints';
2 | import { ResponsiveValue } from '@styled-system/core';
3 |
4 | export interface TransformedValue {
5 | map: (unit: (x: T, key: number | string) => U | null | undefined) => ResponsiveValue;
6 | mapAll: (unit: (x: T | null | undefined, key: number | string) => U) => ResponsiveValue;
7 | reduce: (unit: (accumulator: U, value: T, key: number | string) => U, initialValue: U) => U;
8 |
9 | get: (index: number | string) => T | null;
10 | empty: (index: number | string) => boolean;
11 | value: ResponsiveValue | undefined;
12 | type: 'array' | 'object' | 'value';
13 | }
14 |
15 | export const transform = (breakpoints: Breakpoints) => (val: ResponsiveValue | undefined): TransformedValue =>
16 | typeof val === 'object' && val !== null
17 | ? Array.isArray(val)
18 | ? transformArray(breakpoints, val)
19 | : transformObject(breakpoints, val as Record)
20 | : transformValue(breakpoints, val);
21 |
22 | const transformArray = (breakpoints: Breakpoints, val: (T | null)[]): TransformedValue => ({
23 | map: unit => val.map((x, i) =>
24 | x !== null && x !== undefined
25 | ? unit(x, i) ?? null
26 | : null
27 | ),
28 | mapAll: unit => val.map(unit),
29 | reduce: null as any,
30 | get: index => {
31 | const numIndex = typeof index === 'number'
32 | ? index
33 | : index === '_'
34 | ? 0
35 | : breakpoints.aliases.indexOf(index) + 1;
36 |
37 | return val[numIndex] ?? val.reduceRight(
38 | (acc, v, i) => acc === null && i < numIndex ? v : acc,
39 | null as T | null
40 | );
41 | },
42 | empty: i => {
43 | const numIndex = typeof i === 'number'
44 | ? i
45 | : i === '_'
46 | ? 0
47 | : breakpoints.aliases.indexOf(i) + 1;
48 | return val[numIndex] === null || val[numIndex] === undefined;
49 | },
50 | value: val,
51 | type: 'array'
52 | });
53 |
54 | const transformObject = (breakpoints: Breakpoints, val: Record): TransformedValue => ({
55 | map: (unit: (x: T, key: number | string) => U | undefined): U =>
56 | Object.keys(val).reduce(
57 | (acc, k) => {
58 | const v = val[k] === undefined ? val[k] : unit(val[k]!, k);
59 | return v !== null && v !== undefined
60 | ? {
61 | ...acc,
62 | [k]: v
63 | }
64 | : acc;
65 | },
66 | {} as U
67 | ),
68 | mapAll: null as any,
69 | reduce: null as any,
70 | get: (index) => {
71 | const key = typeof index === 'string'
72 | ? index
73 | : breakpoints.aliases[index - 1] ?? '_';
74 |
75 | return val[key] ??
76 | // Look for value in previous values
77 | ['_', ...breakpoints.aliases].reduceRight(
78 | (acc, k) =>
79 | k === key
80 | ? null
81 | : acc === null && val[k]
82 | ? val[k]
83 | : acc,
84 | undefined as T | null | undefined
85 | ) ??
86 | // Return null instead of undefined
87 | null;
88 | },
89 | empty: i => {
90 | const key = typeof i === 'string' ? i : breakpoints.aliases[i - 1] ?? '_';
91 | return val[key] === null || val[key] === undefined;
92 | },
93 | value: val,
94 | type: 'object'
95 | });
96 |
97 | const transformValue =