├── .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 | Xcore UI 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 | 6 | 11 | 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 = (breakpoints: Breakpoints, val: T | null | undefined): TransformedValue => ({ 98 | map: unit => val !== null && val !== undefined ? unit(val, 0) ?? null : null, 99 | mapAll: unit => unit(val, 0), 100 | reduce: (unit, initialValue) => val ? unit(initialValue, val, 0) : initialValue, 101 | get: _ => val ?? null, 102 | empty: i => i !== 0 || val === null || val === undefined, 103 | value: val, 104 | type: 'value' 105 | }); 106 | -------------------------------------------------------------------------------- /src/utils/useMerge.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { merge } from './merge'; 3 | 4 | const useMerge = (target: T, ...sources: T[]): T => 5 | useMemo(() => merge(target, ...sources), [target, ...sources]); 6 | 7 | export default useMerge; 8 | -------------------------------------------------------------------------------- /src/utils/variant.ts: -------------------------------------------------------------------------------- 1 | 2 | export const variant = ( 3 | variants: Record, 4 | def: TKey, 5 | ...aliases: (TKey | undefined)[] 6 | ) => aliases.reduce( 7 | (acc, val) => acc ?? variants[val!], 8 | undefined as undefined | TValue 9 | ) ?? variants[def]; 10 | 11 | export const typeVariant = ( 12 | theme: { variants: Record }, 13 | def: TKey, 14 | props: { v?: TKey; variant?: TKey } 15 | ) => 16 | variant(theme.variants, def, props.v, props.variant); 17 | 18 | export const sizeVariant = ( 19 | theme: { sizes: Record }, 20 | def: TKey, 21 | props: { s?: TKey; size?: TKey } 22 | ) => 23 | variant(theme.sizes, def, props.s, props.size); 24 | -------------------------------------------------------------------------------- /src/utils/withConfig.ts: -------------------------------------------------------------------------------- 1 | import { ThemedStyledFunction } from 'styled-components'; 2 | import { borderConfig } from '../bases/config/border'; 3 | import { cursorConfig } from '../bases/config/cursor'; 4 | import { flexConfig } from '../bases/config/flex'; 5 | import { gridConfig } from '../bases/config/grid'; 6 | import { gridItemConfig } from '../bases/config/gridItem'; 7 | import { layoutConfig } from '../bases/config/layout'; 8 | import { positionConfig } from '../bases/config/position'; 9 | import { shadowConfig } from '../bases/config/shadow'; 10 | import { spaceConfig } from '../bases/config/space'; 11 | import { typographyConfig } from '../bases/config/typography'; 12 | import { visualsConfig } from '../bases/config/visuals'; 13 | import { selectionSystem } from '../bases/configs'; 14 | 15 | const withConfig = >(sc: T): T => sc.withConfig({ shouldForwardProp }) as T; 16 | 17 | export default withConfig; 18 | 19 | export const shouldForwardProp = ( 20 | prop: string | number | symbol, 21 | isValidAttr: (prop: any) => boolean 22 | ) => 23 | isValidAttr(prop) && (typeof prop !== 'string' || !(prop[0] === '_' || blacklist.includes(prop))); 24 | 25 | const blacklist = [ 26 | ...Object.keys(selectionSystem), 27 | ...Object.keys(borderConfig), 28 | ...Object.keys(cursorConfig), 29 | ...Object.keys(flexConfig), 30 | ...Object.keys(gridConfig), 31 | ...Object.keys(gridItemConfig), 32 | ...Object.keys(layoutConfig), 33 | ...Object.keys(positionConfig), 34 | ...Object.keys(shadowConfig), 35 | ...Object.keys(spaceConfig), 36 | ...Object.keys(typographyConfig), 37 | ...Object.keys(visualsConfig) 38 | ]; 39 | -------------------------------------------------------------------------------- /stories/AspectRatio.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import { AspectRatio, Box, Stack } from '../src'; 4 | 5 | export default { title: 'AspectRatio' }; 6 | 7 | export const BasicUsage: FC = () => { 8 | return ( 9 | 10 | 11 | 12 | 16:9 13 | 14 | 15 | 16 | 17 | 4:3 18 | 19 | 20 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /stories/Box.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import { Box } from '../src'; 4 | 5 | export default { title: 'Box' }; 6 | 7 | export const BasicUsage: FC = () => { 8 | return ( 9 | 10 | 11 | Box 1 12 | 13 | 22 | Box 2 23 | 24 | 38 | Box 3 39 | 40 | 41 | 42 | Outside 43 | Inside 44 | 45 | 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /stories/Button/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Button, button, createTheme, Stack, Box, XcoreProvider } from '../../src'; 2 | import React, { FC } from 'react'; 3 | 4 | export default { title: 'Button' }; 5 | 6 | const theme = createTheme({ 7 | ...button({ 8 | default: { 9 | bg: 'crimson', 10 | color: 'white', 11 | p: '1rem', 12 | fontSize: '1rem', 13 | _leftIcon: { 14 | fill: '#fff' 15 | } 16 | }, 17 | sizes: { 18 | lg: { 19 | padding: '3rem' 20 | } 21 | }, 22 | variants: { 23 | outline: { 24 | background: 'transparent', 25 | color: 'grey', 26 | border: '1px solid crimson' 27 | } 28 | } 29 | }) 30 | }); 31 | 32 | export const BasicUsage: FC = () => { 33 | return ( 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | ); 60 | }; 61 | -------------------------------------------------------------------------------- /stories/Card/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import { Card, CardProps, Flex, Img, LoremIpsum, Paragraph, Tag, XcoreProvider, XcoreGlobal } from '../../src'; 4 | 5 | export default { title: 'Card' }; 6 | 7 | export const BasicUsage: FC = () => { 8 | const cardStyle: CardProps = { 9 | width: '400px', 10 | my: '1rem' 11 | }; 12 | 13 | return ( 14 | 15 | 16 | } 20 | footer={Footer} 21 | > 22 | 23 | 24 | 25 | } 29 | body={} 30 | footer={Footer} 31 | /> 32 | 33 | } 38 | body={} 39 | /> 40 | 41 | } 46 | body={} 47 | /> 48 | 49 | } 53 | body={} 54 | /> 55 | 56 | } 62 | body={} 63 | /> 64 | 65 | 66 | ); 67 | }; 68 | -------------------------------------------------------------------------------- /stories/CloseControl/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import { CloseControl, Stack } from '../../src'; 4 | 5 | export default { title: 'Close control' }; 6 | 7 | export const BasicUsage: FC = () => { 8 | return ( 9 | 10 | 11 | 12 | 13 | 14 | 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /stories/Collapse.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useState } from 'react'; 2 | 3 | import { Collapse, LoremIpsum, Stack, Button } from '../src'; 4 | 5 | export default { title: 'Collapse' }; 6 | 7 | export const BasicUsage: FC = () => { 8 | const [open, setOpen] = useState(false); 9 | return ( 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /stories/Container.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Container, container, createTheme, XcoreProvider } from '../src'; 2 | import React, { FC } from 'react'; 3 | 4 | export default { title: 'Container' }; 5 | 6 | export const Normal: FC = () => ( 7 | <> 8 | 9 | 10 | 11 | Box 1 12 | 13 | 14 | Box 2 15 | 16 | 17 | 18 | 19 | 20 | 21 | Box 1 22 | 23 | 24 | Box 2 25 | 26 | 27 | 28 | 29 | ); 30 | 31 | export const Fluid: FC = () => ( 32 | 33 | 34 | 35 | fluid 36 | 37 | 38 | Box 2 39 | 40 | 41 | 42 | ); 43 | 44 | const theme = createTheme({ 45 | name: 'Container theme', 46 | ...container({ 47 | variants: { 48 | normal: { 49 | width: ['100%', '100%', '30rem', '40rem', '50rem', '70rem'], 50 | background: 'grey' 51 | }, 52 | fluid: { 53 | background: 'crimson' 54 | } 55 | } 56 | }) 57 | }); 58 | 59 | export const WithTheme: FC = () => ( 60 | 61 | 62 | 63 | ); 64 | -------------------------------------------------------------------------------- /stories/DarkTheme/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useState } from 'react'; 2 | 3 | import { 4 | Box, 5 | Button, 6 | colors, 7 | createScales, 8 | createTheme, 9 | darkColorTheme, 10 | Flex, 11 | Grid, 12 | Img, 13 | Link, 14 | List, 15 | ListItem, 16 | LoremIpsum, 17 | Text, 18 | XcoreProvider, 19 | Heading1, 20 | Paragraph, 21 | Heading2 22 | } from '../../src'; 23 | 24 | export default { title: 'Dark Theme' }; 25 | 26 | const lightTheme = createTheme({}); 27 | 28 | const darkTheme = createTheme({ 29 | ...createScales({ 30 | ...colors(darkColorTheme) 31 | }) 32 | }); 33 | 34 | export const Basic: FC = () => { 35 | const [light, setTheme] = useState(true); 36 | 37 | return ( 38 | 39 | 40 | 41 | {/* Sidebar */} 42 | Lorem 43 | 44 | 45 | Home 46 | Blog 47 | Jeff 48 | AAAA 49 | 50 | 51 | 52 | @AlfonzAlfonz 53 |
54 | Logout 55 |
56 | 57 | avatar 62 | 63 | 64 | {/* Content */} 65 | 73 | Lorem ipsum dolor sit amet 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | {/* Footer */} 88 | 89 | footer © 2020 90 | 91 |
92 |
93 |
94 | ); 95 | }; 96 | -------------------------------------------------------------------------------- /stories/Flex.stories.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Flex } from '../src'; 2 | import React, { FC } from 'react'; 3 | 4 | export default { title: 'Flex' }; 5 | 6 | export const BasicUsage: FC = () => { 7 | return ( 8 | 9 | 10 | 11 | Box 1 12 | 13 | 14 | Box 2 15 | 16 | 17 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /stories/Global/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import { createTheme, global, XcoreProvider } from '../../src'; 2 | import React, { FC } from 'react'; 3 | 4 | export default { title: 'Global' }; 5 | 6 | const theme = createTheme({ 7 | ...global({ 8 | _html: { 9 | background: 'navy' 10 | }, 11 | _selection: { 12 | bg: 'crimson' 13 | }, 14 | _all: { 15 | color: 'white' 16 | } 17 | }) 18 | }); 19 | 20 | export const BasicUsage: FC = () => { 21 | return ( 22 | 23 | Text to select 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /stories/Grid/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | Cell, 4 | Grid, 5 | Heading1, 6 | Heading2, 7 | List, 8 | LoremIpsum, 9 | Paragraph, 10 | Text, 11 | XcoreProvider, 12 | ListItem 13 | } from '@xcorejs/ui'; 14 | import React, { FC } from 'react'; 15 | 16 | export default { title: 'Grid' }; 17 | 18 | export const Basic: FC = () => ( 19 | 20 | 21 | 22 | {/* Sidebar */} 23 | Lorem 24 | 25 | Home 26 | Blog 27 | Jeff 28 | AAAA 29 | 30 | 31 | @AlfonzAlfonz 32 |
33 | Logout 34 |
35 | 36 | avatar 41 | 42 | 43 | {/* Content */} 44 | Lorem ipsum dolor sit amet 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {/* Footer */} 55 | 56 | footer © 2020 57 | 58 |
59 |
60 |
61 | ); 62 | 63 | export const IE11: FC = () => ( 64 | 65 | 66 | 67 | {/* Sidebar */} 68 | 69 | Lorem 70 | 71 | 72 | 73 | Home 74 | Blog 75 | Jeff 76 | AAAA 77 | 78 | 79 | 80 | @AlfonzAlfonz 81 |
82 | Logout 83 |
84 | 85 | avatar 90 | 91 | 92 | {/* Content */} 93 | 94 | Lorem ipsum dolor sit amet 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | {/* Footer */} 106 | 107 | footer © 2020 108 | 109 |
110 |
111 |
112 | ); 113 | -------------------------------------------------------------------------------- /stories/Img.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useState } from 'react'; 2 | 3 | import { Stack, Img } from '../src'; 4 | 5 | export default { title: 'Img' }; 6 | 7 | export const BasicUsage: FC = () => { 8 | return ( 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /stories/InsetBox/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useRef } from 'react'; 2 | 3 | import { InsetBox, LoremIpsum, Paragraph, Box } from '../../src'; 4 | 5 | export default { title: 'Inset box' }; 6 | 7 | export const BasicUsage: FC = () => { 8 | return ( 9 | <> 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | AAAAA 25 | 26 | 27 | AAAAA 28 | 29 | 30 | AAAAA 31 | 32 | 33 | AAAAA 34 | 35 | 36 | AAAAA 37 | 38 | 39 | AAAAA 40 | 41 | 42 | AAAAA 43 | 44 | 45 | AAAAA 46 | 47 | 48 | AAAAA 49 | 50 | 51 | ); 52 | }; 53 | 54 | export const Stretch: FC = () => { 55 | return ( 56 | <> 57 | 58 | 59 | AAAAA 60 | 61 | 62 | AAAAA 63 | 64 | 65 | ); 66 | }; 67 | 68 | export const WithTarget: FC = () => { 69 | const box = useRef(null!); 70 | 71 | return ( 72 | <> 73 | 74 | 75 | 76 | 77 | AAAAA 78 | 79 | 80 | ); 81 | }; 82 | -------------------------------------------------------------------------------- /stories/Link.stories.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { Box, Link } from '../src'; 3 | import React, { FC } from 'react'; 4 | 5 | export default { title: 'Link' }; 6 | 7 | export const BasicUsage: FC = () => { 8 | return ( 9 | 10 | Burger King burger 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /stories/List.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import { List, ListItem, LoremIpsum, Stack, Text, Box, Heading1, XcoreProvider } from '../src'; 4 | 5 | export default { title: 'List' }; 6 | 7 | export const BasicUsage: FC = () => { 8 | return ( 9 | 10 | 11 | Ordered 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
  • 22 |
  • 23 |
  • 24 |
  • 25 |
  • 26 |
    27 |
    28 | Unordered 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
  • 39 |
  • 40 |
  • 41 |
  • 42 |
  • 43 |
    44 |
    45 |
    46 |
    47 | ); 48 | }; 49 | -------------------------------------------------------------------------------- /stories/Modal/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | Button, 4 | Flex, 5 | Heading1, 6 | Img, 7 | LoremIpsum, 8 | Modal, 9 | Paragraph, 10 | Stack, 11 | useDisclosure, 12 | useModal, 13 | XcoreProvider 14 | } from '@xcorejs/ui'; 15 | import React, { FC, useState } from 'react'; 16 | 17 | export default { title: 'Modal' }; 18 | 19 | export const BasicUsage: FC = () => { 20 | return ( 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | }; 28 | 29 | const NameForm: FC = () => { 30 | const [name, setName] = useState(''); 31 | const [open] = useModal(() => setName(s)} />); 32 | 33 | return ( 34 | <> 35 | 36 | {name} 37 | 38 | ); 39 | }; 40 | 41 | const NameModal: FC<{ submit: (s: string) => unknown }> = ({ submit }) => { 42 | const [close] = useModal(); 43 | const [openNext] = useModal(NextModal); 44 | const [val, setVal] = useState(''); 45 | 46 | return ( 47 | 48 | 49 | setVal(e.target.value)} /> 50 | 57 | 58 | ); 59 | }; 60 | 61 | const NextModal: FC = () => { 62 | return ( 63 | 64 | Another modal 65 | 66 | ); 67 | }; 68 | 69 | const imgs = [ 70 | 'http://placekitten.com/400/600', 71 | 'http://placekitten.com/400/800', 72 | 'http://placekitten.com/600/600', 73 | 'http://placekitten.com/800/600', 74 | 'http://placekitten.com/800/1000', 75 | 'http://placekitten.com/400/700' 76 | ]; 77 | 78 | export const WithProps: FC = () => { 79 | return ( 80 | 81 | 82 | 83 | ); 84 | }; 85 | 86 | const KittenGallery: FC = () => { 87 | const [open, close] = useModal(Gallery, { images: imgs }); 88 | 89 | return ( 90 | 91 | Kitten gallery 92 | 93 | {imgs.map((i, index) => ( 94 | img open({ index })} 101 | /> 102 | ))} 103 | 104 | 105 | ); 106 | }; 107 | 108 | const Gallery: FC<{ index: number; images: string[] }> = ({ index, images }) => { 109 | const [offset, setOffset] = useState(0); 110 | 111 | const prev = (index + offset + images.length - 1) % images.length; 112 | const next = (index + offset + 1) % images.length; 113 | 114 | return ( 115 | 116 | 117 | setOffset(prev - index)}>{'<'} 118 | img 119 | setOffset(next - index)}>{'>'} 120 | 121 | 122 | ); 123 | }; 124 | 125 | export const LowLevelUsage: FC = () => { 126 | const { isOpen, open, close } = useDisclosure(); 127 | 128 | return ( 129 | 130 | 131 | {isOpen && ( 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | )} 141 | 142 | ); 143 | }; 144 | -------------------------------------------------------------------------------- /stories/OutsetBox/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useRef, useState } from 'react'; 2 | 3 | import { InsetBox, Button, LoremIpsum, Paragraph, Box, OutsetBox } from '../../src'; 4 | 5 | export default { title: 'OutsetBox' }; 6 | 7 | export const BasicUsage: FC = () => { 8 | const [state, setState] = useState(0); 9 | const box = useRef(null!); 10 | 11 | return ( 12 | <> 13 | 14 | {[...Array(state)].map(i => )} 15 | 16 | 17 | 18 | 19 | 20 | AAAAA 21 | 22 | 23 | AAAAA 24 | 25 | 26 | AAAAA 27 | 28 | 29 | 30 | AAAAA 31 | 32 | 33 | AAAAA 34 | 35 | 36 | 37 | AAAAA 38 | 39 | 40 | AAAAA 41 | 42 | 43 | AAAAA 44 | 45 | 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /stories/SimpleGrid/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import { Box, SimpleGrid, Text, XcoreProvider, Cell } from '../../src'; 4 | 5 | export default { title: 'Simple grid' }; 6 | 7 | export const Basic: FC = () => { 8 | return ( 9 | 10 | 11 | 12 | Products 13 | Company 14 | Sitemap 15 | Contacts 16 | Disclaimer 17 | Privacy 18 | 19 | 20 | 21 | 22 | ); 23 | }; 24 | 25 | export const IE11: FC = () => { 26 | return ( 27 | <> 28 | 29 | 30 | AAAAAAA 31 | 32 | 33 | 34 | BBBB 35 | 36 | 37 | 38 | 39 | AAAAAA 40 | 41 | 42 | BBBBBB 43 | 44 | 45 | 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /stories/Stack/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import { 4 | ActiveBreakpoint, 5 | Box, 6 | container, 7 | createTheme, 8 | Heading3, 9 | Stack, 10 | Text, 11 | XcoreProvider 12 | } from '../../src'; 13 | 14 | export default { title: 'Stack' }; 15 | 16 | export const BasicUsage: FC = () => { 17 | return ( 18 | 19 | 20 | 21 | Box 1 22 | 23 | 24 | Box 2 25 | 26 | 27 | Lorem ipsum dolor sit amet 28 | 29 | Title 1 30 | Title 2 31 | Title 3 32 | 33 | 34 | ); 35 | }; 36 | 37 | const theme = createTheme({ 38 | name: 'Container theme', 39 | ...container({ 40 | variants: { 41 | normal: { 42 | width: ['100%', '100%', '30rem', '40rem', '50rem', '70rem'], 43 | background: 'grey' 44 | } 45 | } 46 | }) 47 | }); 48 | 49 | export const Responsive: FC = () => ( 50 | 51 | 52 | 59 | 60 | 61 | Box 1 62 | 63 | 64 | Box 2 65 | 66 | 67 | Lorem ipsum dolor sit amet 68 | 69 | 70 | ); 71 | -------------------------------------------------------------------------------- /stories/Tag.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import { Stack, Tag } from '../src'; 4 | 5 | export default { title: 'Tag' }; 6 | 7 | export const BasicUsage: FC = () => { 8 | return ( 9 | 10 | Tag 11 | Tag 12 | 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /stories/Text/Text.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import { Box, createTheme, Link, Text, text, XcoreProvider, Strikethrough, Em } from '../../src'; 4 | 5 | export default { title: 'Text' }; 6 | 7 | export const BasicUsage: FC = () => { 8 | return ( 9 | 10 | 11 | 12 | 13 | Number 15: 14 | {' '} 15 | Burger king foot lettuce. 16 | 17 | The last thing you'd want in your Burger King burger is someone's foot fungus. But as 18 | it turns out, that might be what you get. A 19 | 20 | 4channer 21 | 22 | uploaded a photo anonymously to the site showcasing his feet in a plastic bin of lettuce. With the statement: 23 | "This is the lettuce you eat at Burger King." 24 | Admittedly, he had shoes on. 25 | 26 | 27 | But that's even worse. 28 | 29 | 30 | ); 31 | }; 32 | 33 | const theme = createTheme({ 34 | ...text({ 35 | default: { 36 | fontFamily: 'Impact' 37 | } 38 | }) 39 | }); 40 | 41 | export const WithTheme: FC = () => { 42 | return ( 43 | 44 | 45 | 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /stories/Theme/Breakpoints.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import { 4 | ActiveBreakpoint, 5 | Box, 6 | breakpoints, 7 | Container, 8 | container, 9 | createScales, 10 | createTheme, 11 | useTheme, 12 | XcoreProvider 13 | } from '../../src'; 14 | 15 | export default { title: 'Theme - Breakpoint' }; 16 | 17 | const theme = createTheme({ 18 | name: 'Container theme', 19 | ...container({ 20 | variants: { 21 | normal: { 22 | width: ['100%', '100%', '30rem', '40rem', '50rem', '70rem'], 23 | background: 'grey' 24 | } 25 | } 26 | }), 27 | ...createScales({ 28 | ...breakpoints(['30em', '48em', '64em', '78em', '85em']) 29 | }) 30 | }); 31 | 32 | export const Aliases: FC = () => ( 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ); 43 | 44 | const GetAliases: FC = () => { 45 | // eslint-disable-next-line no-shadow 46 | const { breakpoints } = useTheme(); 47 | 48 | return ( 49 |
    50 |       [0] {breakpoints[0]} | [xs] {breakpoints.xs} 
    51 | [1] {breakpoints[1]} | [sm] {breakpoints.sm}
    52 | [2] {breakpoints[2]} | [md] {breakpoints.md}
    53 | [3] {breakpoints[3]} | [lg] {breakpoints.lg}
    54 | [4] {breakpoints[4]} | [xl] {breakpoints.xl}
    55 |
    56 | ); 57 | }; 58 | 59 | const ThemeToJSON: FC = () => { 60 | const t = useTheme(); 61 | 62 | return
    {JSON.stringify(t, null, 2)}
    ; 63 | }; 64 | -------------------------------------------------------------------------------- /stories/Theme/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import { 4 | Box, 5 | Button, 6 | colors, 7 | Container, 8 | container, 9 | createScales, 10 | createTheme, 11 | useTheme, 12 | XcoreProvider 13 | } from '../../src'; 14 | import { darken, lightColorTheme } from '../../src/scales/colors'; 15 | 16 | export default { title: 'Theme' }; 17 | 18 | const theme = createTheme({ 19 | name: 'Container theme', 20 | ...container({ 21 | variants: { 22 | normal: { 23 | width: '70%', 24 | bg: 'grey' 25 | } 26 | } 27 | }), 28 | ...createScales({ 29 | ...colors(lightColorTheme, { 30 | primary: 'red' 31 | }) 32 | }) 33 | }); 34 | export const WithContainer: FC = () => { 35 | return ( 36 | 37 | 38 | 39 | 40 | Box 1 41 | 42 | 43 | Box 2 44 | 45 | 46 | 47 | 48 | 49 | 50 | ); 51 | }; 52 | 53 | export const GetThemeValue: FC = () => ( 54 | 55 | 56 | 57 | ); 58 | 59 | const ThemeToJSON: FC = () => { 60 | const t = useTheme(); 61 | 62 | return
    {JSON.stringify(t, null, 2)}
    ; 63 | }; 64 | -------------------------------------------------------------------------------- /stories/Tooltip/index.stories.tsx: -------------------------------------------------------------------------------- 1 | // import React, { FC, useEffect, useRef, useState } from 'react'; 2 | 3 | // import { LoremIpsum, Paragraph, Tooltip, XcoreProvider } from '../../src'; 4 | 5 | // export default { title: 'Tooltip' }; 6 | 7 | // export const BasicUsage: FC = () => { 8 | // const ref = useRef(null!); 9 | 10 | // const [_, forceUpdate] = useState(0); 11 | 12 | // useEffect(() => { 13 | // forceUpdate(1); 14 | // }, []); 15 | 16 | // return ( 17 | // 18 | // 19 | // 20 | // 21 | // Tooltip! 22 | // 23 | // 24 | // 25 | // ); 26 | // }; 27 | -------------------------------------------------------------------------------- /stories/Typography/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | 3 | import { Box, Typography, XcoreProvider } from '../../src'; 4 | 5 | export default { title: 'Typography' }; 6 | 7 | export const BasicUsage: FC = () => { 8 | return ( 9 | 10 | 11 | Heading 1 12 | Heading 2 13 | Heading 3 14 | Heading 4 15 | Heading 5 16 | Heading 6 17 | Paragraph 18 | Lead 19 |
    20 | Heading 2 as h3 21 |
    22 |
    23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /tests/components/__snapshots__/aspectRatio.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`AspectRatio component 1`] = ` 4 | Array [ 5 | .c1 { 6 | display: -webkit-box; 7 | display: -webkit-flex; 8 | display: -ms-flexbox; 9 | display: flex; 10 | position: absolute; 11 | -webkit-align-items: center; 12 | -webkit-box-align: center; 13 | -ms-flex-align: center; 14 | align-items: center; 15 | -webkit-box-pack: center; 16 | -webkit-justify-content: center; 17 | -ms-flex-pack: center; 18 | justify-content: center; 19 | left: 0; 20 | top: 0; 21 | } 22 | 23 | .c0 { 24 | width: 100%; 25 | height: 0; 26 | padding-bottom: 56.25%; 27 | position: relative; 28 | } 29 | 30 | .c0 > div { 31 | width: 100%; 32 | height: 100%; 33 | overflow: hidden; 34 | } 35 | 36 | .c0 img { 37 | object-fit: cover; 38 | } 39 | 40 |
    43 |
    46 | 16:9 47 |
    48 |
    , 49 | .c1 { 50 | display: -webkit-box; 51 | display: -webkit-flex; 52 | display: -ms-flexbox; 53 | display: flex; 54 | position: absolute; 55 | -webkit-align-items: center; 56 | -webkit-box-align: center; 57 | -ms-flex-align: center; 58 | align-items: center; 59 | -webkit-box-pack: center; 60 | -webkit-justify-content: center; 61 | -ms-flex-pack: center; 62 | justify-content: center; 63 | left: 0; 64 | top: 0; 65 | } 66 | 67 | .c0 { 68 | width: 100%; 69 | height: 0; 70 | padding-bottom: 75%; 71 | position: relative; 72 | } 73 | 74 | .c0 > div { 75 | width: 100%; 76 | height: 100%; 77 | overflow: hidden; 78 | } 79 | 80 | .c0 img { 81 | object-fit: cover; 82 | } 83 | 84 |
    87 |
    90 | 4:3 91 |
    92 |
    , 93 | ] 94 | `; 95 | -------------------------------------------------------------------------------- /tests/components/__snapshots__/box.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Box component 1`] = ` 4 | Array [ 5 | @media screen and (min-width:64em) { 6 | 7 | } 8 | 9 |
    12 | Empty 13 |
    , 14 | .c0 { 15 | background: crimson; 16 | color: white; 17 | padding: 10px; 18 | margin-top: 15px; 19 | margin-bottom: 15px; 20 | } 21 | 22 | @media screen and (min-width:64em) { 23 | 24 | } 25 | 26 |
    29 | Box 1 30 |
    , 31 | .c0 { 32 | background: navy; 33 | color: white; 34 | padding: 10px; 35 | margin-top: 15px; 36 | margin-bottom: 15px; 37 | } 38 | 39 | .c0:hover { 40 | background: crimson; 41 | } 42 | 43 | @media screen and (min-width:64em) { 44 | .c0:hover { 45 | background: green; 46 | } 47 | } 48 | 49 |
    52 | Box 2 53 |
    , 54 | .c0 { 55 | background: navy; 56 | color: white; 57 | padding: 10px; 58 | margin-top: 15px; 59 | margin-bottom: 15px; 60 | } 61 | 62 | .c0::before { 63 | content: "before"; 64 | background: green; 65 | } 66 | 67 | .c0::after { 68 | content: "after"; 69 | background: green; 70 | } 71 | 72 | @media screen and (min-width:64em) { 73 | 74 | } 75 | 76 |
    79 | Box 3 80 |
    , 81 | [role=group]:hover .c0 { 82 | background: navy; 83 | } 84 | 85 | @media screen and (min-width:64em) { 86 | 87 | } 88 | 89 |
    93 |
    96 | Outside 97 |
    98 |
    101 | Inside 102 |
    103 |
    , 104 | ] 105 | `; 106 | -------------------------------------------------------------------------------- /tests/components/__snapshots__/container.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Container component 1`] = ` 4 | .c1 { 5 | width: 100%; 6 | } 7 | 8 | .c2 { 9 | background: crimson; 10 | color: white; 11 | padding: 10px; 12 | margin-top: 10px; 13 | margin-bottom: 10px; 14 | } 15 | 16 | .c3 { 17 | background: navy; 18 | color: white; 19 | padding: 10px; 20 | margin-top: 10px; 21 | margin-bottom: 10px; 22 | } 23 | 24 | .c0 { 25 | display: -webkit-box; 26 | display: -webkit-flex; 27 | display: -ms-flexbox; 28 | display: flex; 29 | margin-left: auto; 30 | margin-right: auto; 31 | width: 100%; 32 | padding-left: 16px; 33 | padding-right: 16px; 34 | } 35 | 36 | @media screen and (min-width:30em) { 37 | .c0 { 38 | width: 76.8rem; 39 | } 40 | } 41 | 42 | @media screen and (min-width:48em) { 43 | .c0 { 44 | width: 102.4rem; 45 | } 46 | } 47 | 48 | @media screen and (min-width:64em) { 49 | .c0 { 50 | width: 120rem; 51 | } 52 | } 53 | 54 | @media screen and (min-width:78em) { 55 | .c0 { 56 | width: 132rem; 57 | } 58 | } 59 | 60 |
    63 |
    66 |
    69 | Box 1 70 |
    71 |
    74 | Box 2 75 |
    76 |
    77 |
    78 | `; 79 | -------------------------------------------------------------------------------- /tests/components/__snapshots__/list.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`List component 1`] = ` 4 | .c0 { 5 | color: #0171b6; 6 | border-color: #0171b6; 7 | font-size: 1.6rem; 8 | line-height: initial; 9 | display: -webkit-inline-box; 10 | display: -webkit-inline-flex; 11 | display: -ms-inline-flexbox; 12 | display: inline-flex; 13 | -webkit-transition: color 300ms,border 300ms; 14 | transition: color 300ms,border 300ms; 15 | cursor: pointer; 16 | border-bottom: 1px solid transparent; 17 | cursor: pointer; 18 | } 19 | 20 | .c0:hover { 21 | color: #0169a9; 22 | border-bottom: 1px solid transparent; 23 | } 24 | 25 | .c0 * { 26 | cursor: pointer; 27 | } 28 | 29 | 33 | Burger King burger 34 | 35 | `; 36 | -------------------------------------------------------------------------------- /tests/components/__snapshots__/scales.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Scales - dark theme 1`] = ` 4 | .c0 { 5 | color: #DAA520; 6 | } 7 | 8 |
    11 | `; 12 | 13 | exports[`Scales - light theme 1`] = ` 14 | .c0 { 15 | color: #0171b6; 16 | } 17 | 18 |
    21 | `; 22 | 23 | exports[`Scales - no theme 1`] = ` 24 | .c0 { 25 | color: #0171b6; 26 | } 27 | 28 |
    31 | `; 32 | -------------------------------------------------------------------------------- /tests/components/__snapshots__/spinner.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Spinner component 1`] = ` 4 | .c0 { 5 | display: inline-block; 6 | border-color: currentColor; 7 | border-style: solid; 8 | border-radius: full; 9 | border-width: 2px; 10 | border-left-color: transparent; 11 | border-bottom-color: transparent; 12 | width: 1.6rem; 13 | height: 1.6rem; 14 | -webkit-animation: cilQsd 0.5s linear infinite; 15 | animation: cilQsd 0.5s linear infinite; 16 | } 17 | 18 |
    22 | `; 23 | -------------------------------------------------------------------------------- /tests/components/__snapshots__/tag.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Tag component 1`] = ` 4 | .c0 { 5 | display: -webkit-box; 6 | display: -webkit-flex; 7 | display: -ms-flexbox; 8 | display: flex; 9 | white-space: nowrap; 10 | -webkit-align-items: center; 11 | -webkit-box-align: center; 12 | -ms-flex-align: center; 13 | align-items: center; 14 | -webkit-user-select: none; 15 | -moz-user-select: none; 16 | -ms-user-select: none; 17 | user-select: none; 18 | -webkit-transition: color 300ms,background 300ms,border-color 300ms; 19 | transition: color 300ms,background 300ms,border-color 300ms; 20 | border-radius: 0.3rem; 21 | border: 1px solid #455663; 22 | padding-left: 0.8rem; 23 | padding-right: 0.8rem; 24 | font-family: "rubik",-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; 25 | font-size: 1.2rem; 26 | font-weight: 500; 27 | line-height: 2rem; 28 | background: #455663; 29 | color: #fff; 30 | } 31 | 32 |
    35 | Tag 36 |
    37 | `; 38 | -------------------------------------------------------------------------------- /tests/components/__snapshots__/text.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Text component 1`] = ` 4 | .c0 { 5 | font-family: "rubik",-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; 6 | font-size: 1.5rem; 7 | line-height: 2rem; 8 | -webkit-transition: color 300ms; 9 | transition: color 300ms; 10 | color: #1E3441; 11 | } 12 | 13 | .c1 { 14 | color: #0171b6; 15 | border-color: #0171b6; 16 | font-size: 1.6rem; 17 | line-height: initial; 18 | display: -webkit-inline-box; 19 | display: -webkit-inline-flex; 20 | display: -ms-inline-flexbox; 21 | display: inline-flex; 22 | -webkit-transition: color 300ms,border 300ms; 23 | transition: color 300ms,border 300ms; 24 | cursor: pointer; 25 | border-bottom: 1px solid transparent; 26 | cursor: pointer; 27 | } 28 | 29 | .c1:hover { 30 | color: #0169a9; 31 | border-bottom: 1px solid transparent; 32 | } 33 | 34 | .c1 * { 35 | cursor: pointer; 36 | } 37 | 38 | 41 |
    44 | 47 | Number 15: 48 | 49 | 52 | Burger king foot lettuce. 53 | 54 | 55 | The last thing you'd want in your 56 | 60 | Burger King burger 61 | 62 | is someone's foot fungus. But as it turns out, that might be what you get. A 63 | 66 | 4channer 67 | 68 | uploaded a photo anonymously to the site showcasing his feet in a plastic bin of lettuce. With the statement: 69 | 72 | "This is the lettuce you eat at Burger King." 73 | 74 | Admittedly, he had shoes on. 75 |
    76 |
    79 | But that's even worse. 80 |
    81 |
    82 | `; 83 | -------------------------------------------------------------------------------- /tests/components/__snapshots__/typography.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Typography component 1`] = ` 4 | .c0 { 5 | font-family: "rubik",-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; 6 | margin: 0; 7 | color: #1E3441; 8 | font-size: 4.4rem; 9 | font-weight: 500; 10 | line-height: 6.6rem; 11 | margin-bottom: 4rem; 12 | } 13 | 14 | .c1 { 15 | font-family: "rubik",-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; 16 | margin: 0; 17 | color: #1E3441; 18 | font-size: 3.2rem; 19 | font-weight: 500; 20 | line-height: 4.8rem; 21 | } 22 | 23 | .c2 { 24 | font-family: "rubik",-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; 25 | margin: 0; 26 | color: #1E3441; 27 | font-size: 2.4rem; 28 | font-weight: 500; 29 | line-height: 3.6rem; 30 | } 31 | 32 | .c3 { 33 | font-family: "rubik",-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; 34 | margin: 0; 35 | color: #1E3441; 36 | } 37 | 38 | .c4 { 39 | font-family: "rubik",-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; 40 | margin: 0; 41 | color: #1E3441; 42 | font-size: 1.6rem; 43 | line-height: 2rem; 44 | } 45 | 46 |
    49 |

    52 | Heading 1 53 |

    54 |

    57 | Heading 2 58 |

    59 |

    62 | Heading 3 63 |

    64 |

    67 | Heading 4 68 |

    69 |
    72 | Heading 5 73 |
    74 |
    77 | Heading 6 78 |
    79 |

    82 | Paragraph 83 |

    84 |

    87 | Lead 88 |

    89 |
    90 |

    93 | Heading 2 as h3 94 |

    95 |
    96 | `; 97 | -------------------------------------------------------------------------------- /tests/components/__snapshots__/xcoreProvider.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`XcoreProvider component 1`] = `null`; 4 | -------------------------------------------------------------------------------- /tests/components/aspectRatio.test.tsx: -------------------------------------------------------------------------------- 1 | import 'jest-styled-components'; 2 | 3 | import { AspectRatio } from '@xcorejs/ui'; 4 | import React from 'react'; 5 | import renderer from 'react-test-renderer'; 6 | 7 | test('AspectRatio component', () => { 8 | const component = renderer.create( 9 | <> 10 | 11 | 16:9 12 | 13 | 14 | 4:3 15 | 16 | 17 | ); 18 | const tree = component.toJSON(); 19 | expect(tree).toMatchSnapshot(); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/components/box.test.tsx: -------------------------------------------------------------------------------- 1 | import 'jest-styled-components'; 2 | 3 | import { Box } from '@xcorejs/ui'; 4 | import React from 'react'; 5 | import renderer from 'react-test-renderer'; 6 | 7 | test('Box component', () => { 8 | const component = renderer.create( 9 | <> 10 | Empty 11 | 17 | Box 1 18 | 19 | 28 | Box 2 29 | 30 | 44 | Box 3 45 | 46 | 47 | 48 | Outside 49 | Inside 50 | 51 | 52 | ); 53 | const tree = component.toJSON(); 54 | expect(tree).toMatchSnapshot(); 55 | }); 56 | -------------------------------------------------------------------------------- /tests/components/button.test.tsx: -------------------------------------------------------------------------------- 1 | import 'jest-styled-components'; 2 | 3 | import { Button } from '@xcorejs/ui'; 4 | import React from 'react'; 5 | import renderer from 'react-test-renderer'; 6 | 7 | test('Button component', () => { 8 | const component = renderer.create( 9 | <> 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | const tree = component.toJSON(); 20 | expect(tree).toMatchSnapshot(); 21 | }); 22 | -------------------------------------------------------------------------------- /tests/components/card.test.tsx: -------------------------------------------------------------------------------- 1 | import 'jest-styled-components'; 2 | 3 | import { Card, Img, LoremIpsum, Paragraph } from '@xcorejs/ui'; 4 | import React from 'react'; 5 | import renderer from 'react-test-renderer'; 6 | 7 | test('Card component', () => { 8 | const component = renderer.create( 9 | <> 10 | } 13 | footer={Footer} 14 | > 15 | 16 | 17 | 18 | } 21 | body={} 22 | footer={Footer} 23 | /> 24 | 25 | } 29 | body={} 30 | /> 31 | 32 | } 36 | body={} 37 | /> 38 | 39 | } 42 | body={} 43 | /> 44 | 45 | } 50 | body={} 51 | /> 52 | 53 | ); 54 | const tree = component.toJSON(); 55 | expect(tree).toMatchSnapshot(); 56 | }); 57 | -------------------------------------------------------------------------------- /tests/components/closeControl.test.tsx: -------------------------------------------------------------------------------- 1 | import 'jest-styled-components'; 2 | 3 | import { CloseControl } from '@xcorejs/ui'; 4 | import React from 'react'; 5 | import renderer from 'react-test-renderer'; 6 | 7 | test('CloseControl component', () => { 8 | const component = renderer.create( 9 | <> 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | const tree = component.toJSON(); 18 | expect(tree).toMatchSnapshot(); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/components/container.test.tsx: -------------------------------------------------------------------------------- 1 | import 'jest-styled-components'; 2 | 3 | import { Box, Container } from '@xcorejs/ui'; 4 | import React from 'react'; 5 | import renderer from 'react-test-renderer'; 6 | 7 | test('Container component', () => { 8 | const component = renderer.create( 9 | 10 | 11 | 12 | Box 1 13 | 14 | 15 | Box 2 16 | 17 | 18 | 19 | ); 20 | const tree = component.toJSON(); 21 | expect(tree).toMatchSnapshot(); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/components/grid.test.tsx: -------------------------------------------------------------------------------- 1 | import 'jest-styled-components'; 2 | 3 | import { Box, Grid, LoremIpsum } from '@xcorejs/ui'; 4 | import React from 'react'; 5 | import renderer from 'react-test-renderer'; 6 | 7 | test('Grid component', () => { 8 | Object.defineProperty(window.navigator, 'userAgent', { 9 | value: 'MSIE' 10 | }); 11 | 12 | const component = renderer.create( 13 | <> 14 | 15 | Logo 16 | 17 | Menu 18 | 19 | 20 | User bar 21 | 22 | 23 | avatar 28 | 29 | 30 | {/* Content */} 31 | Header 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | {/* Footer */} 42 | 43 | Footer 44 | 45 | 46 | 47 | ); 48 | const tree = component.toJSON(); 49 | expect(tree).toMatchSnapshot(); 50 | }); 51 | -------------------------------------------------------------------------------- /tests/components/link.test.tsx: -------------------------------------------------------------------------------- 1 | import 'jest-styled-components'; 2 | 3 | import { List, ListItem, LoremIpsum } from '@xcorejs/ui'; 4 | import React from 'react'; 5 | import renderer from 'react-test-renderer'; 6 | 7 | test('Link component', () => { 8 | const component = renderer.create( 9 | <> 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | const tree = component.toJSON(); 27 | expect(tree).toMatchSnapshot(); 28 | }); 29 | -------------------------------------------------------------------------------- /tests/components/list.test.tsx: -------------------------------------------------------------------------------- 1 | import 'jest-styled-components'; 2 | 3 | import { Link } from '@xcorejs/ui'; 4 | import React from 'react'; 5 | import renderer from 'react-test-renderer'; 6 | 7 | test('List component', () => { 8 | const component = renderer.create( 9 | <> 10 | Burger King burger 11 | 12 | ); 13 | const tree = component.toJSON(); 14 | expect(tree).toMatchSnapshot(); 15 | }); 16 | -------------------------------------------------------------------------------- /tests/components/scales.test.tsx: -------------------------------------------------------------------------------- 1 | import 'jest-styled-components'; 2 | 3 | import { Box, colors, createScales, createTheme, darkColorTheme, XcoreProvider } from '@xcorejs/ui'; 4 | import React from 'react'; 5 | import renderer from 'react-test-renderer'; 6 | 7 | test('Scales - no theme', () => { 8 | const component = renderer.create( 9 | 10 | 11 | 12 | ); 13 | const tree = component.toJSON(); 14 | expect(tree).toMatchSnapshot(); 15 | }); 16 | 17 | test('Scales - light theme', () => { 18 | const lightTheme = createTheme(); 19 | 20 | const component = renderer.create( 21 | 22 | 23 | 24 | ); 25 | const tree = component.toJSON(); 26 | expect(tree).toMatchSnapshot(); 27 | }); 28 | 29 | test('Scales - dark theme', () => { 30 | const darkTheme = createTheme({ 31 | ...createScales({ 32 | ...colors(darkColorTheme) 33 | }) 34 | }); 35 | 36 | const component = renderer.create( 37 | 38 | 39 | 40 | ); 41 | const tree = component.toJSON(); 42 | expect(tree).toMatchSnapshot(); 43 | }); 44 | -------------------------------------------------------------------------------- /tests/components/simpleGrid.test.tsx: -------------------------------------------------------------------------------- 1 | import 'jest-styled-components'; 2 | 3 | import { Box, SimpleGrid, Cell } from '@xcorejs/ui'; 4 | import React from 'react'; 5 | import renderer from 'react-test-renderer'; 6 | 7 | test('SimpleGrid component', () => { 8 | Object.defineProperty(window.navigator, 'userAgent', { 9 | value: 'MSIE' 10 | }); 11 | 12 | const component = renderer.create( 13 | 14 | Products 15 | Company 16 | Sitemap 17 | Contacts 18 | Disclaimer 19 | Privacy 20 | 21 | ); 22 | const tree = component.toJSON(); 23 | expect(tree).toMatchSnapshot(); 24 | }); 25 | 26 | test('SimpleGrid component - responsive', () => { 27 | Object.defineProperty(window.navigator, 'userAgent', { 28 | value: 'MSIE' 29 | }); 30 | 31 | const component = renderer.create( 32 | <> 33 | 34 | 35 | AAAAAAA 36 | 37 | 38 | 39 | BBBB 40 | 41 | 42 | 43 | 44 | AAAAAA 45 | 46 | 47 | BBBBBB 48 | 49 | 50 | 51 | ); 52 | const tree = component.toJSON(); 53 | expect(tree).toMatchSnapshot(); 54 | }); 55 | -------------------------------------------------------------------------------- /tests/components/spinner.test.tsx: -------------------------------------------------------------------------------- 1 | import 'jest-styled-components'; 2 | 3 | import { Spinner } from '@xcorejs/ui'; 4 | import React from 'react'; 5 | import renderer from 'react-test-renderer'; 6 | 7 | test('Spinner component', () => { 8 | const component = renderer.create( 9 | <> 10 | 11 | 12 | ); 13 | const tree = component.toJSON(); 14 | expect(tree).toMatchSnapshot(); 15 | }); 16 | -------------------------------------------------------------------------------- /tests/components/stack.test.tsx: -------------------------------------------------------------------------------- 1 | import 'jest-styled-components'; 2 | 3 | import { Box, Heading3, Stack } from '@xcorejs/ui'; 4 | import React from 'react'; 5 | import renderer from 'react-test-renderer'; 6 | 7 | test('Stack component', () => { 8 | const component = renderer.create( 9 | <> 10 | 11 | 12 | Box 1 13 | 14 | 15 | Box 2 16 | 17 | 18 | 19 | Title 1 20 | Title 2 21 | Title 3 22 | 23 | 24 | ); 25 | const tree = component.toJSON(); 26 | expect(tree).toMatchSnapshot(); 27 | }); 28 | 29 | test('Stack component - responsive', () => { 30 | const component = renderer.create( 31 | 32 | 33 | Box 1 34 | 35 | 36 | Box 2 37 | 38 | 39 | ); 40 | const tree = component.toJSON(); 41 | expect(tree).toMatchSnapshot(); 42 | }); 43 | -------------------------------------------------------------------------------- /tests/components/tag.test.tsx: -------------------------------------------------------------------------------- 1 | import 'jest-styled-components'; 2 | 3 | import { Tag } from '@xcorejs/ui'; 4 | import React from 'react'; 5 | import renderer from 'react-test-renderer'; 6 | 7 | test('Tag component', () => { 8 | const component = renderer.create( 9 | Tag 10 | ); 11 | const tree = component.toJSON(); 12 | expect(tree).toMatchSnapshot(); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/components/text.test.tsx: -------------------------------------------------------------------------------- 1 | import 'jest-styled-components'; 2 | 3 | import { Box, Em, Link, Strikethrough, Text } from '@xcorejs/ui'; 4 | import React from 'react'; 5 | import renderer from 'react-test-renderer'; 6 | 7 | test('Text component', () => { 8 | const component = renderer.create( 9 | 10 | 11 | 12 | Number 15: 13 | {' '} 14 | Burger king foot lettuce. 15 | 16 | The last thing you'd want in your Burger King burger is someone's foot fungus. But as 17 | it turns out, that might be what you get. A 18 | 19 | 4channer 20 | 21 | uploaded a photo anonymously to the site showcasing his feet in a plastic bin of lettuce. With the statement: 22 | "This is the lettuce you eat at Burger King." 23 | Admittedly, he had shoes on. 24 | 25 | 26 | But that's even worse. 27 | 28 | ); 29 | const tree = component.toJSON(); 30 | expect(tree).toMatchSnapshot(); 31 | }); 32 | -------------------------------------------------------------------------------- /tests/components/typography.test.tsx: -------------------------------------------------------------------------------- 1 | import 'jest-styled-components'; 2 | 3 | import { Box, Typography } from '@xcorejs/ui'; 4 | import React from 'react'; 5 | import renderer from 'react-test-renderer'; 6 | 7 | test('Typography component', () => { 8 | const component = renderer.create( 9 | <> 10 | 11 | Heading 1 12 | Heading 2 13 | Heading 3 14 | Heading 4 15 | Heading 5 16 | Heading 6 17 | Paragraph 18 | Lead 19 |
    20 | Heading 2 as h3 21 |
    22 | 23 | ); 24 | const tree = component.toJSON(); 25 | expect(tree).toMatchSnapshot(); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/components/xcoreProvider.test.tsx: -------------------------------------------------------------------------------- 1 | import 'jest-styled-components'; 2 | 3 | import { XcoreProvider } from '@xcorejs/ui'; 4 | import React from 'react'; 5 | import renderer from 'react-test-renderer'; 6 | 7 | test('XcoreProvider component', () => { 8 | const component = renderer.create( 9 | 10 | ); 11 | const tree = component.toJSON(); 12 | expect(tree).toMatchSnapshot(); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/utils/merge.test.ts: -------------------------------------------------------------------------------- 1 | import { CardProps } from '@xcorejs/ui'; 2 | import { merge } from 'utils/merge'; 3 | import { createTheme } from '../../src/theme'; 4 | 5 | test('deeply merge props', () => { 6 | expect(merge({ 7 | _header: { 8 | padding: { _: 1, sm: 2, md: 3 } 9 | }, 10 | color: 'green', 11 | margin: [10, 20, 30] 12 | })).toEqual({ 13 | _header: { 14 | padding: { _: 1, sm: 2, md: 3 } 15 | }, 16 | color: 'green', 17 | margin: [10, 20, 30] 18 | }); 19 | 20 | expect( 21 | merge({ 22 | _header: { 23 | padding: { xl: 5 }, 24 | fontSize: '2rem' 25 | }, 26 | color: 'red', 27 | margin: [0, 20, 30, 40] 28 | }, { 29 | _header: { 30 | padding: { _: 1, sm: 2, md: 3 }, 31 | color: 'crimson' 32 | }, 33 | color: 'green', 34 | margin: [10, 20, 30] 35 | }) 36 | ).toEqual({ 37 | _header: { 38 | padding: { xl: 5 }, 39 | fontSize: '2rem', 40 | color: 'crimson' 41 | }, 42 | color: 'red', 43 | margin: [0, 20, 30, 40] 44 | }); 45 | }); 46 | 47 | test('merged props key order', () => { 48 | expect( 49 | Object.keys(merge({ 50 | my: '30' 51 | }, { 52 | margin: [10, 20, 30], 53 | marginTop: '30' 54 | })) 55 | ).toEqual( 56 | ['margin', 'marginTop', 'my'] 57 | ); 58 | 59 | expect( 60 | Object.keys(merge({ 61 | my: '30', 62 | padding: 20 63 | }, { 64 | margin: [10, 20, 30], 65 | marginTop: '30', 66 | padding: 20, 67 | paddingTop: 5 68 | })) 69 | ).toEqual( 70 | ['margin', 'marginTop', 'paddingTop', 'my', 'padding'] 71 | ); 72 | }); 73 | 74 | test('merge button props', () => { 75 | const { button } = createTheme(); 76 | 77 | const props = merge( 78 | { 79 | color: 'blue' 80 | }, 81 | button.variants.solid, 82 | button.sizes.md, 83 | button.default 84 | ); 85 | 86 | expect(props) 87 | .toEqual({ 88 | _hover: { 89 | bg: '__$darken;primary;0.025' 90 | }, 91 | _active: { 92 | bg: '__$darken;primary;0.05' 93 | }, 94 | _focus: { 95 | bg: '__$darken;primary;0.05', 96 | outline: '2px solid rgba(15, 31, 40, 0.2)', 97 | outlineOffset: '-2px' 98 | }, 99 | _disabled: { 100 | cursor: 'not-allowed', 101 | opacity: 0.5 102 | }, 103 | fontWeight: 500, 104 | fontFamily: 'text', 105 | lineHeight: '2.4rem', 106 | borderRadius: '0.3rem', 107 | display: 'inline-flex', 108 | border: '0.1rem solid transparent', 109 | transition: 'background 300ms, color 300ms, border 300ms, box-shadow 300ms', 110 | cursor: 'pointer', 111 | textDecoration: 'none', 112 | alignItems: 'center', 113 | px: '2rem', 114 | py: '0.7rem', 115 | fontSize: '1.4rem', 116 | bg: 'primary', 117 | color: 'blue' 118 | }); 119 | 120 | expect(Object.keys(props)) 121 | .toEqual([ 122 | 'fontWeight', 123 | 'fontFamily', 124 | 'lineHeight', 125 | 'borderRadius', 126 | 'display', 127 | 'border', 128 | 'transition', 129 | 'cursor', 130 | 'textDecoration', 131 | 'alignItems', 132 | 'px', 133 | 'py', 134 | 'fontSize', 135 | 'bg', 136 | '_hover', 137 | '_active', 138 | '_focus', 139 | '_disabled', 140 | 'color' 141 | ]); 142 | }); 143 | 144 | test('merge container props with falsy value', () => { 145 | const { container } = createTheme(); 146 | 147 | const props = merge( 148 | { 149 | px: 0 150 | }, 151 | container.variants.normal, 152 | container.default 153 | ); 154 | 155 | expect(props) 156 | .toEqual({ 157 | ml: 'auto', 158 | mr: 'auto', 159 | px: 0, 160 | width: ['100%', '76.8rem', '102.4rem', '120rem', '132rem'] 161 | }); 162 | }); 163 | -------------------------------------------------------------------------------- /tests/utils/variant.test.ts: -------------------------------------------------------------------------------- 1 | import { createTheme, darken, opacify } from '@xcorejs/ui'; 2 | import { variant } from 'utils/variant'; 3 | 4 | const { button } = createTheme({}); 5 | 6 | test('variantThemed', () => { 7 | expect( 8 | variant( 9 | button.variants, 10 | 'solid', 11 | 'outline' 12 | ) 13 | ).toEqual({ 14 | border: '1px solid', 15 | borderColor: 'primary', 16 | color: 'primary', 17 | _hover: { 18 | bg: opacify('primary', 0.1) 19 | }, 20 | _active: { 21 | bg: opacify('primary', 0.2) 22 | }, 23 | _focus: { 24 | bg: opacify('primary', 0.2), 25 | outline: '2px solid rgba(15, 31, 40, 0.2)', 26 | outlineOffset: '-2px' 27 | }, 28 | _disabled: { 29 | opacity: 0.5 30 | } 31 | }); 32 | 33 | expect( 34 | variant( 35 | button.variants, 36 | 'solid' 37 | ) 38 | ).toEqual({ 39 | bg: 'primary', 40 | color: 'background', 41 | _hover: { 42 | bg: darken('primary', 0.025) 43 | }, 44 | _active: { 45 | bg: darken('primary', 0.05) 46 | }, 47 | _focus: { 48 | bg: darken('primary', 0.05), 49 | outline: '2px solid rgba(15, 31, 40, 0.2)', 50 | outlineOffset: '-2px' 51 | }, 52 | _disabled: { 53 | opacity: 0.5 54 | } 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "target": "es5", 5 | "declaration": true, 6 | "declarationMap": true, 7 | "paths": { 8 | "*": ["./src/*"] 9 | } 10 | }, 11 | "include": ["src", "src/lib.es5.d.ts", "src/styled.d.ts", "**/*.d.ts"], 12 | "exclude": ["test", "stories"] 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["./**/*.js", "src", "stories", "tests", "**/*.d.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./src", 4 | "target": "es5", 5 | "jsx": "react", 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "lib": ["dom", "dom.iterable", "esnext"], 9 | "skipLibCheck": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "isolatedModules": false, 13 | "esModuleInterop": true, 14 | "resolveJsonModule": true, 15 | "declaration": false, 16 | "sourceMap": true, 17 | "paths": { 18 | "@xcorejs/ui": ["./"], 19 | } 20 | }, 21 | "exclude": ["node_modules", "_build_internal"], 22 | "include": ["src", "stories", "**/*.d.ts", "tests", "**/*.d.ts"] 23 | } 24 | -------------------------------------------------------------------------------- /types/lib.es5.d.ts: -------------------------------------------------------------------------------- 1 | interface ObjectConstructor { 2 | keys(o: T): (keyof T & string)[]; 3 | } 4 | 5 | declare var Object: ObjectConstructor; 6 | -------------------------------------------------------------------------------- /types/styled-system__core.d.ts: -------------------------------------------------------------------------------- 1 | 2 | type Scale = Record; 3 | 4 | declare module '@styled-system/core' { 5 | import CSS from 'csstype'; 6 | export type ObjectOrArray = T[] | Record | T[]>; 7 | export type Scale = ObjectOrArray; 8 | 9 | export type Config = { 10 | [P in string]?: boolean | ConfigStyle; 11 | }; 12 | 13 | export interface ConfigStyle { 14 | property?: keyof CSS.Properties; 15 | properties?: Array; 16 | 17 | scale?: string; 18 | 19 | defaultScale?: Scale; 20 | 21 | transform?: (value: any, scale?: Scale) => any; 22 | } 23 | 24 | export interface Theme { 25 | breakpoints?: ObjectOrArray; 26 | mediaQueries?: { [size: string]: string }; 27 | space?: ObjectOrArray>; 28 | fontSizes?: ObjectOrArray>; 29 | colors?: ObjectOrArray; 30 | fonts?: ObjectOrArray; 31 | fontWeights?: ObjectOrArray; 32 | lineHeights?: ObjectOrArray>; 33 | letterSpacings?: ObjectOrArray>; 34 | sizes?: ObjectOrArray | CSS.WidthProperty<{}>>; 35 | borders?: ObjectOrArray>; 36 | borderStyles?: ObjectOrArray>; 37 | borderWidths?: ObjectOrArray>; 38 | radii?: ObjectOrArray>; 39 | shadows?: ObjectOrArray; 40 | zIndices?: ObjectOrArray; 41 | buttons?: ObjectOrArray; 42 | colorStyles?: ObjectOrArray; 43 | textStyles?: ObjectOrArray; 44 | } 45 | 46 | export type RequiredTheme = Required; 47 | 48 | export type ResponsiveValue = 49 | | T 50 | | null 51 | | Array 52 | | { [key in ThemeValue<'breakpoints', ThemeType> & string | number]?: T }; 53 | 54 | export type ThemeValue = 55 | ThemeType[K] extends TVal[] ? number : 56 | ThemeType[K] extends Record ? E : 57 | ThemeType[K] extends ObjectOrArray ? F : never; 58 | 59 | export function get(obj: Scale, ...paths: Array): T; 60 | 61 | export function system(styleDefinitions: Config): styleFn; 62 | 63 | export interface styleFn { 64 | (...args: any[]): any; 65 | 66 | config?: object; 67 | propNames?: string[]; 68 | cache?: object; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /types/styled.d.ts: -------------------------------------------------------------------------------- 1 | import 'styled-components'; 2 | import { XcoreTheme } from '../src/theme'; 3 | 4 | declare module 'styled-components' { 5 | export interface DefaultTheme extends XcoreTheme { 6 | 7 | } 8 | } 9 | --------------------------------------------------------------------------------