├── .eslintrc.json ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmrc ├── .posthtmlrc ├── .prettierignore ├── .prettierrc.json ├── .releaserc.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── src │ ├── index.html │ ├── main.ts │ ├── style.css │ ├── ts │ │ ├── color-picker.ts │ │ ├── modals.ts │ │ ├── show-code.ts │ │ ├── tabs.ts │ │ ├── theme-switcher.ts │ │ └── toasts.ts │ └── usage │ │ ├── buttons.html │ │ ├── icons.html │ │ ├── inputs.html │ │ ├── modals.html │ │ ├── tabs.html │ │ ├── toasts.html │ │ └── tooltips.html └── tsconfig.json ├── package-lock.json ├── package.json ├── public ├── chevron_down.svg ├── chevron_up.svg ├── email_icon.svg ├── lock_icon.svg └── search_icon.svg ├── src ├── components │ ├── button-groups.css │ ├── buttons.css │ ├── checkboxes.css │ ├── inputs.css │ ├── modals.css │ ├── radios.css │ ├── scrollbars.css │ ├── selects.css │ ├── tabs.css │ ├── textareas.css │ ├── toasts.css │ └── tooltips.css ├── main.css └── props │ ├── dark-extended.css │ ├── light-extended.css │ ├── theme-extended.css │ └── variable-sizes.css └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "prettier" 10 | ], 11 | "overrides": [], 12 | "parser": "@typescript-eslint/parser", 13 | "parserOptions": { 14 | "ecmaVersion": "latest", 15 | "sourceType": "module" 16 | }, 17 | "plugins": ["@typescript-eslint"], 18 | "rules": {} 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will release the library and deploy the docs 2 | name: CI 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["master"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Allow one concurrent deployment 13 | concurrency: 14 | group: "pages" 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | build: 19 | if: "!contains(github.event.head_commit.message, 'skip ci')" 20 | runs-on: ubuntu-20.04 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v3 24 | 25 | - name: Use Node.js 18 26 | uses: actions/setup-node@v2-beta 27 | with: 28 | node-version: 18 29 | 30 | - name: Install dependencies 31 | run: npm install 32 | 33 | - name: Build library 34 | run: npm run build 35 | 36 | - name: Run Semantic Release 37 | run: npm run semantic-release 38 | env: 39 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 40 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 41 | 42 | deploy: 43 | permissions: 44 | contents: read 45 | pages: write 46 | id-token: write 47 | environment: 48 | name: github-pages 49 | url: ${{ steps.deployment.outputs.page_url }} 50 | runs-on: ubuntu-latest 51 | steps: 52 | - name: Checkout 53 | uses: actions/checkout@v3 54 | 55 | - name: Setup Pages 56 | uses: actions/configure-pages@v2 57 | 58 | - name: Install dependencies 59 | run: npm install 60 | 61 | - name: Build docs 62 | run: npm run build-docs 63 | 64 | - name: Delete node_modules 65 | run: rm -rf node_modules 66 | 67 | - name: Upload artifact 68 | uses: actions/upload-pages-artifact@v1 69 | with: 70 | # Upload entire repository 71 | path: "." 72 | 73 | - name: Deploy to GitHub Pages 74 | id: deployment 75 | uses: actions/deploy-pages@v1 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | .parcel-cache 15 | /*.svg 16 | /*.css* 17 | /*.js* 18 | /*.html 19 | /docs/*.svg 20 | /docs/*.css* 21 | /docs/*.js* 22 | /docs/*.html 23 | secrets 24 | 25 | # Exceptions, do include this 26 | !*.json 27 | 28 | # Editor directories and files 29 | .vscode/* 30 | !.vscode/extensions.json 31 | .idea 32 | .DS_Store 33 | *.suo 34 | *.ntvs* 35 | *.njsproj 36 | *.sln 37 | *.sw? 38 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | access=public -------------------------------------------------------------------------------- /.posthtmlrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "posthtml-include": { 4 | "root": "docs/src" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .parcel-cache 2 | dist 3 | /*.css* 4 | /*.svg -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "branches": "master", 3 | "repositoryUrl": "git@github.com:RiskChallenger/open-components.git", 4 | "debug": "true", 5 | "plugins": [ 6 | "@semantic-release/commit-analyzer", 7 | "@semantic-release/release-notes-generator", 8 | "@semantic-release/npm", 9 | "@semantic-release/github", 10 | [ 11 | "@semantic-release/changelog", 12 | { 13 | "changelogFile": "CHANGELOG.md" 14 | } 15 | ], 16 | [ 17 | "@semantic-release/git", 18 | { 19 | "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" 20 | } 21 | ] 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.6.4](https://github.com/RiskChallenger/open-components/compare/v1.6.3...v1.6.4) (2024-06-05) 2 | 3 | ### Bug Fixes 4 | 5 | - set correct selector for inputs ([a8d0cbc](https://github.com/RiskChallenger/open-components/commit/a8d0cbc6edb8146326c172df95219bf2af92f03c)) 6 | 7 | ## [1.6.3](https://github.com/RiskChallenger/open-components/compare/v1.6.2...v1.6.3) (2024-05-14) 8 | 9 | ### Bug Fixes 10 | 11 | - show form buttons correctly ([c99fda6](https://github.com/RiskChallenger/open-components/commit/c99fda67739a950741fc7dd65fd986c324c42ed6)) 12 | 13 | ## [1.6.2](https://github.com/RiskChallenger/open-components/compare/v1.6.1...v1.6.2) (2024-05-14) 14 | 15 | ### Bug Fixes 16 | 17 | - remove interaction from tooltips, update packages ([e1072f9](https://github.com/RiskChallenger/open-components/commit/e1072f9f25d08d9f12c6c977b3f5b897efdbfab1)) 18 | 19 | ## [1.6.1](https://github.com/RiskChallenger/open-components/compare/v1.6.0...v1.6.1) (2023-11-24) 20 | 21 | ### Bug Fixes 22 | 23 | - set correct file for unpkg publishing ([a2bacf9](https://github.com/RiskChallenger/open-components/commit/a2bacf908fe669ac41bca2fcffb59db133d55853)) 24 | 25 | # [1.6.0](https://github.com/RiskChallenger/open-components/compare/v1.5.3...v1.6.0) (2023-08-15) 26 | 27 | ### Features 28 | 29 | - respect system ui fonts ([d8c98ed](https://github.com/RiskChallenger/open-components/commit/d8c98eddb45c2817de995fa9877a8c292b2a8fd8)), closes [#16](https://github.com/RiskChallenger/open-components/issues/16) 30 | 31 | ## [1.5.3](https://github.com/RiskChallenger/open-components/compare/v1.5.2...v1.5.3) (2023-08-15) 32 | 33 | ### Bug Fixes 34 | 35 | - allow for tooltips on material-icons ([f143411](https://github.com/RiskChallenger/open-components/commit/f143411a9e150aaf089d4bb1bdf11756b9fcc56e)), closes [#15](https://github.com/RiskChallenger/open-components/issues/15) 36 | - change media query syntax for better browser support ([dd2e7ff](https://github.com/RiskChallenger/open-components/commit/dd2e7ff99ef5543d18782149fd89b5ee92d6a21f)), closes [#13](https://github.com/RiskChallenger/open-components/issues/13) 37 | 38 | ## [1.5.2](https://github.com/RiskChallenger/open-components/compare/v1.5.1...v1.5.2) (2023-06-30) 39 | 40 | ### Bug Fixes 41 | 42 | - set danger colours consistently ([babc0c8](https://github.com/RiskChallenger/open-components/commit/babc0c8b94f9ec087e494b17c8f4ebd67455a2a0)) 43 | 44 | ## [1.5.1](https://github.com/RiskChallenger/open-components/compare/v1.5.0...v1.5.1) (2023-06-30) 45 | 46 | ### Bug Fixes 47 | 48 | - add padding to end of file input for other languages ([33a13a4](https://github.com/RiskChallenger/open-components/commit/33a13a480941a02069329f4ef0004f6a8da284b9)), closes [#12](https://github.com/RiskChallenger/open-components/issues/12) 49 | 50 | # [1.5.0](https://github.com/RiskChallenger/open-components/compare/v1.4.13...v1.5.0) (2023-06-28) 51 | 52 | ### Features 53 | 54 | - add danger and warning buttons, upgrade dependencies ([8e4ee81](https://github.com/RiskChallenger/open-components/commit/8e4ee81df363d0ddadc69836c8dd3767005f25c7)) 55 | 56 | ## [1.4.13](https://github.com/RiskChallenger/open-components/compare/v1.4.12...v1.4.13) (2023-06-19) 57 | 58 | ### Bug Fixes 59 | 60 | - correct discrete hover/active background ([edbb751](https://github.com/RiskChallenger/open-components/commit/edbb7515e8865e5f2e49a93d5268c38241691227)) 61 | 62 | ## [1.4.12](https://github.com/RiskChallenger/open-components/compare/v1.4.11...v1.4.12) (2023-06-19) 63 | 64 | ### Bug Fixes 65 | 66 | - simplify button states, fix active tab border ([8ff1fee](https://github.com/RiskChallenger/open-components/commit/8ff1fee5598619699d16392ee0a75353c5a87eed)) 67 | 68 | ## [1.4.11](https://github.com/RiskChallenger/open-components/compare/v1.4.10...v1.4.11) (2023-06-15) 69 | 70 | ### Bug Fixes 71 | 72 | - make modals scrollable with long content ([6c9249a](https://github.com/RiskChallenger/open-components/commit/6c9249ab06280ede41ecb2de0e7df7aabe83abff)) 73 | 74 | ## [1.4.10](https://github.com/RiskChallenger/open-components/compare/v1.4.9...v1.4.10) (2023-06-02) 75 | 76 | ### Bug Fixes 77 | 78 | - make extended props work with manual theme switching ([bddda36](https://github.com/RiskChallenger/open-components/commit/bddda36f6049d43c4d8b62f258f45c2d4f782f03)) 79 | 80 | ## [1.4.9](https://github.com/RiskChallenger/open-components/compare/v1.4.8...v1.4.9) (2023-06-02) 81 | 82 | ### Bug Fixes 83 | 84 | - improve input visibility with higher contrast ([c718c31](https://github.com/RiskChallenger/open-components/commit/c718c3107c2bca7067532687d91f1091e220b63c)) 85 | 86 | ## [1.4.8](https://github.com/RiskChallenger/open-components/compare/v1.4.7...v1.4.8) (2023-05-27) 87 | 88 | ### Bug Fixes 89 | 90 | - improve disabled look of buttons ([e724f50](https://github.com/RiskChallenger/open-components/commit/e724f50e1857a08a53a7e55980948c241dc8f0fd)) 91 | 92 | ## [1.4.7](https://github.com/RiskChallenger/open-components/compare/v1.4.6...v1.4.7) (2023-05-26) 93 | 94 | ### Bug Fixes 95 | 96 | - improve disabled styling on checkbox, radio, select ([6239e5b](https://github.com/RiskChallenger/open-components/commit/6239e5b44a3df42e0a238d4c787cbf7a49de7c5a)), closes [#11](https://github.com/RiskChallenger/open-components/issues/11) 97 | 98 | ## [1.4.6](https://github.com/RiskChallenger/open-components/compare/v1.4.5...v1.4.6) (2023-05-26) 99 | 100 | ### Bug Fixes 101 | 102 | - show tooltips on one line ([b81a3e6](https://github.com/RiskChallenger/open-components/commit/b81a3e66d743c43df82acb9a4f23406e50dfb921)) 103 | 104 | ## [1.4.5](https://github.com/RiskChallenger/open-components/compare/v1.4.4...v1.4.5) (2023-05-26) 105 | 106 | ### Bug Fixes 107 | 108 | - allow combination of tooltips and buttons ([24fe876](https://github.com/RiskChallenger/open-components/commit/24fe87640b9a26c85a4d95199f303b758a94dc9e)) 109 | 110 | ## [1.4.4](https://github.com/RiskChallenger/open-components/compare/v1.4.3...v1.4.4) (2023-05-11) 111 | 112 | ### Bug Fixes 113 | 114 | - change input type mail to email ([7255e14](https://github.com/RiskChallenger/open-components/commit/7255e14e88615540a2ef8077eb8284d83db8feee)), closes [#10](https://github.com/RiskChallenger/open-components/issues/10) 115 | 116 | ## [1.4.3](https://github.com/RiskChallenger/open-components/compare/v1.4.2...v1.4.3) (2023-05-04) 117 | 118 | ### Bug Fixes 119 | 120 | - improve flexibility of modal structure ([31ba03f](https://github.com/RiskChallenger/open-components/commit/31ba03fa2d5bcc35834f96d46e87f230156a1fc9)) 121 | 122 | ## [1.4.2](https://github.com/RiskChallenger/open-components/compare/v1.4.1...v1.4.2) (2023-05-03) 123 | 124 | ### Bug Fixes 125 | 126 | - allow for other modal headings ([2e0c6d7](https://github.com/RiskChallenger/open-components/commit/2e0c6d7ceae15494de9d75ac991fcf51bf2ec374)) 127 | 128 | ## [1.4.1](https://github.com/RiskChallenger/open-components/compare/v1.4.0...v1.4.1) (2023-05-02) 129 | 130 | ### Bug Fixes 131 | 132 | - slightly better range input ([bf77040](https://github.com/RiskChallenger/open-components/commit/bf77040fe5b0e5fcc505c07e14fb3d83aad6196d)) 133 | 134 | # [1.4.0](https://github.com/RiskChallenger/open-components/compare/v1.3.0...v1.4.0) (2023-05-02) 135 | 136 | ### Features 137 | 138 | - add basic range input ([29ea848](https://github.com/RiskChallenger/open-components/commit/29ea848103936febdf30300e9a91a640f6ec9669)) 139 | 140 | # [1.3.0](https://github.com/RiskChallenger/open-components/compare/v1.2.0...v1.3.0) (2023-05-01) 141 | 142 | ### Features 143 | 144 | - add button groups ([09904ee](https://github.com/RiskChallenger/open-components/commit/09904eea4ea082cb0a70ad5887bb8d918184e05b)) 145 | 146 | # [1.2.0](https://github.com/RiskChallenger/open-components/compare/v1.1.1...v1.2.0) (2023-04-28) 147 | 148 | ### Features 149 | 150 | - add scrollbar styling and fix some overflow issues ([8b2618a](https://github.com/RiskChallenger/open-components/commit/8b2618a6ef2d2d67da19e9c6347f4749f6a96174)) 151 | 152 | ## [1.1.1](https://github.com/RiskChallenger/open-components/compare/v1.1.0...v1.1.1) (2023-04-28) 153 | 154 | ### Bug Fixes 155 | 156 | - bring outline button background in line ([7897e87](https://github.com/RiskChallenger/open-components/commit/7897e8701da9d7e9ba6433ac9d20ae2a22ce17fb)) 157 | 158 | # [1.1.0](https://github.com/RiskChallenger/open-components/compare/v1.0.1...v1.1.0) (2023-04-28) 159 | 160 | ### Bug Fixes 161 | 162 | - align styling of checks/radios with other inputs ([39b6164](https://github.com/RiskChallenger/open-components/commit/39b6164b05602637dee133da0e71382c15e3df0b)), closes [#6](https://github.com/RiskChallenger/open-components/issues/6) 163 | - move from px to rem for font size ([eba57a0](https://github.com/RiskChallenger/open-components/commit/eba57a02231f77e497c31fd9ee922b9c93be1dc1)), closes [#5](https://github.com/RiskChallenger/open-components/issues/5) 164 | - put icon button size in line with other buttons ([1286135](https://github.com/RiskChallenger/open-components/commit/128613540f70f5bad7a83c615031f124e382ef64)), closes [#9](https://github.com/RiskChallenger/open-components/issues/9) 165 | 166 | ### Features 167 | 168 | - add disabled state for tabs ([f245d21](https://github.com/RiskChallenger/open-components/commit/f245d213b04eea66a4ed0448803c3ef4de62caae)), closes [#8](https://github.com/RiskChallenger/open-components/issues/8) 169 | 170 | ## [1.0.1](https://github.com/RiskChallenger/open-components/compare/v1.0.0...v1.0.1) (2023-04-25) 171 | 172 | ### Bug Fixes 173 | 174 | - remove default padding from body ([2079015](https://github.com/RiskChallenger/open-components/commit/207901590a18d67aa156cb13b69a513591076389)) 175 | 176 | # 1.0.0 (2023-04-24) 177 | 178 | ### Features 179 | 180 | - add textareas and file inputs ([283d9af](https://github.com/RiskChallenger/open-components/commit/283d9af0dd82839538f3326341e53e2260b99029)) 181 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Rien Heuver 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 | # Open Components 2 | 3 | Docs at https://riskchallenger.github.io/open-components/ 4 | 5 | - [Installation](#installation) 6 | - [Getting started](#getting-started) 7 | - [Usage](#usage) 8 | - [Implemented components](#implemented-components) 9 | 10 | ## Installation 11 | 12 | `npm i @riskchallenger/open-components` 13 | 14 | Import main.css in your application, here are some options depending on your bundler: 15 | 16 | - `@import "@riskchallenger/open-components/main.css";` 17 | - `@import "npm:@riskchallenger/open-components/main.css";` 18 | - `@import "/node_modules/@riskchallenger/open-components/main.css";` 19 | 20 | That's it 😄 21 | 22 | ## Getting started 23 | 24 | ### Brand color 25 | 26 | All you need to do is set a brand color. Go to the Usage-tab to have a preview of the components in your brand color. These options are from 27 | [the color-scheme on Open Props](https://open-props.style/#colors). However, feel free to use any other color you like. 28 | 29 | ### Border radius 30 | 31 | If you want a straight corner look, simply set the default border radius to 0. Check the example below 32 | 33 | ### Font size 34 | 35 | If you want to change the font-size, simply do so as in the below examples. 36 | 37 | ### Examples 38 | 39 | These are the default values in Open Components. 40 | 41 | ``` 42 | html { 43 | --brand: var(--cyan-6); 44 | --default-radius: var(--radius-2); 45 | --default-font-size: 16px; 46 | } 47 | ``` 48 | 49 | Here's an example for a pink theme with straight corners and smaller text. 50 | 51 | ``` 52 | html { 53 | --brand: var(--pink-6); 54 | --default-radius: 0; 55 | --default-font-size: 12px; 56 | } 57 | ``` 58 | 59 | ## Usage 60 | 61 | To learn how to use the components, simply check out the Usage tab on the Open Components website where you can see them all in action with code samples as well. Check here: https://riskchallenger.github.io/open-components/ 62 | 63 | ## Implemented components 64 | 65 | - Buttons 66 | - Inputs (including autocomplete) 67 | - Selects 68 | - Checkboxes / radios 69 | - Textareas 70 | - Icons 71 | - Tabs / tab-menu 72 | - Modals 73 | - Tooltips 74 | - Toasts 75 | 76 | ## Future components 77 | 78 | - Progress bars? 79 | - Pills? 80 | - Suggestions are welcome! Please do make an issue 😄 81 | -------------------------------------------------------------------------------- /docs/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Open Components 7 | 8 | 9 | 10 | 11 | 32 |
33 |

Pick your brand color

34 | 35 | 36 | 37 | 38 | 43 | 48 | 53 | 54 | 55 | 56 | 57 | 58 | 63 | 68 | 69 | 70 | 71 | 72 | 77 |
78 | 79 |
80 |

About

81 |

82 | Open Components is a library of components styled with pure CSS. It 83 | heavily relies on Open Props. 84 | The design and implementation of the components was done by 85 | RiskChallenger. 86 |

87 |
88 | 89 |
90 |
Installation
91 |
Getting started
92 |
Usage
93 |
94 | 95 |
96 |

Installation

97 |
    98 |
  1. npm i @riskchallenger/open-components
  2. 99 |
  3. 100 | Import main.css in your application, here are some options depending 101 | on your bundler 102 |
      103 |
    1. 104 | @import "@riskchallenger/open-components/main.css"; 105 |
    2. 106 |
    3. 107 | @import "npm:@riskchallenger/open-components/main.css"; 110 |
    4. 111 |
    5. 112 | 113 | @import "/node_modules/@riskchallenger/open-components/main.css"; 116 |
    6. 117 |
    118 |
  4. 119 |
  5. That's it 😄
  6. 120 |
121 |
122 | 123 |
124 |

Getting started

125 |
Brand color
126 |

127 | All you need to do is set a brand color. Hover over the options above to 128 | get the color name. Check out the Usage-tab to have a preview of the 129 | components in your brands color. These options are from 130 | the color-scheme on Open Props. However, feel free to use any other color you like. 133 |

134 | 135 |
Border radius
136 |

137 | If you want a straight corner look, simply set the default border radius 138 | to 0. Check the example below 139 |

140 | 141 |
Font size
142 |

143 | If you want to change the font-size, simply do so as in the below 144 | examples. 145 |

146 | 147 |
Examples
148 |

These are the default values in Open Components.

149 |
150 | html {
151 |   --brand: var(--cyan-6);
152 |   --default-radius: var(--radius-2);
153 |   --default-font-size: 16px;
154 | }
155 | 
157 |

158 | Here's an example for a pink theme with straight corners and smaller 159 | text. 160 |

161 |
162 | html {
163 |   --brand: var(--pink-6);
164 |   --default-radius: 0;
165 |   --default-font-size: 12px;
166 | }
167 | 
169 |
170 | 171 |
172 |
173 | 174 |
175 | 176 |
177 | 178 |
179 | 180 |
181 | 182 |
183 | 184 |
185 | 186 |
187 | 188 |
189 | 190 |
191 | 192 |
193 | 194 |
195 | 196 |
197 | 198 |
199 |
200 | 201 | 202 | -------------------------------------------------------------------------------- /docs/src/main.ts: -------------------------------------------------------------------------------- 1 | import { initColorPicker } from "./ts/color-picker"; 2 | import { initModals } from "./ts/modals"; 3 | import { showCode } from "./ts/show-code"; 4 | import { initTabs } from "./ts/tabs"; 5 | import { initThemeSwitcher } from "./ts/theme-switcher"; 6 | import { initToasts } from "./ts/toasts"; 7 | 8 | window.onload = () => { 9 | initModals(); 10 | initToasts(); 11 | initTabs(); 12 | initColorPicker(); 13 | showCode(); 14 | initThemeSwitcher(); 15 | }; 16 | -------------------------------------------------------------------------------- /docs/src/style.css: -------------------------------------------------------------------------------- 1 | @import "../../src/main.css"; 2 | 3 | body { 4 | display: flex; 5 | flex-direction: column; 6 | gap: var(--size-3); 7 | padding: var(--size-5); 8 | font-size: 1rem; 9 | } 10 | 11 | nav { 12 | display: flex; 13 | justify-content: space-between; 14 | align-items: center; 15 | } 16 | 17 | .menu { 18 | display: flex; 19 | gap: var(--size-3); 20 | align-items: center; 21 | } 22 | 23 | h1 { 24 | color: var(--brand); 25 | filter: brightness(0.9); 26 | } 27 | 28 | section { 29 | padding: var(--size-3); 30 | border-radius: var(--default-radius); 31 | box-shadow: var(--shadow-3); 32 | border: var(--border-size-1) solid var(--surface-2); 33 | background-color: var(--surface-1); 34 | overflow: auto; 35 | 36 | & h2 { 37 | margin-block-end: var(--size-5); 38 | } 39 | } 40 | 41 | [data-color-picker] { 42 | display: inline-block; 43 | width: 60px; 44 | height: 20px; 45 | border-radius: var(--radius-6); 46 | margin: var(--size-2); 47 | cursor: pointer; 48 | } 49 | 50 | .three-columns { 51 | display: grid; 52 | grid-template-columns: min-content repeat(2, minmax(min-content, 1fr)); 53 | align-items: center; 54 | justify-items: flex-start; 55 | gap: var(--size-5); 56 | white-space: nowrap; 57 | 58 | & .header { 59 | font-weight: var(--font-weight-7); 60 | } 61 | } 62 | 63 | code { 64 | white-space: pre; 65 | } 66 | 67 | #usage code, 68 | pre { 69 | background-color: var(--surface-2); 70 | padding: var(--size-relative-2) var(--size-relative-3); 71 | } 72 | 73 | .tab-body { 74 | display: none; 75 | flex-direction: column; 76 | } 77 | 78 | #installation, 79 | #getting-started { 80 | gap: var(--size-3); 81 | } 82 | 83 | #usage { 84 | gap: var(--size-7); 85 | } 86 | 87 | :is([data-theme="light"], .light, .light-theme) { 88 | & .github-icon path { 89 | fill: black; 90 | } 91 | } 92 | 93 | :is([data-theme="dark"], .dark, .dark-theme) { 94 | & .github-icon path { 95 | fill: white; 96 | } 97 | } 98 | 99 | @media (prefers-color-scheme: dark) { 100 | .github-icon path { 101 | fill: white; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /docs/src/ts/color-picker.ts: -------------------------------------------------------------------------------- 1 | export const initColorPicker = () => { 2 | document.querySelectorAll("[data-color-picker]").forEach((el) => { 3 | el.addEventListener("click", setColor); 4 | (el as HTMLElement).style.setProperty( 5 | "background-color", 6 | `var(--${el.classList[0]}-6)` 7 | ); 8 | }); 9 | }; 10 | 11 | function setColor(event: Event) { 12 | const target = event.target as HTMLElement; 13 | const color = target.classList[0]; 14 | const root = document.querySelector(":root") as HTMLElement; 15 | root.style.setProperty("--brand", `var(--${color}-6)`); 16 | } 17 | -------------------------------------------------------------------------------- /docs/src/ts/modals.ts: -------------------------------------------------------------------------------- 1 | export const initModals = () => { 2 | document.querySelectorAll("[data-open-modal]").forEach((button) => { 3 | button.addEventListener("click", () => { 4 | const templateId = (button as HTMLElement).dataset.template; 5 | const template = document.getElementById( 6 | templateId! 7 | ) as HTMLTemplateElement; 8 | const backdrop = template?.content.firstElementChild?.cloneNode( 9 | true 10 | ) as HTMLElement; 11 | document.body.appendChild(backdrop); 12 | 13 | function closeModal(event: Event) { 14 | if (event.target == event.currentTarget) { 15 | backdrop.classList.add("removing"); 16 | backdrop.addEventListener("animationend", () => { 17 | backdrop.remove(); 18 | }); 19 | } 20 | } 21 | 22 | backdrop.addEventListener("click", closeModal); 23 | backdrop 24 | .querySelectorAll("button") 25 | .forEach((button) => button.addEventListener("click", closeModal)); 26 | }); 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /docs/src/ts/show-code.ts: -------------------------------------------------------------------------------- 1 | export const showCode = () => { 2 | document.querySelectorAll("#usage code:empty").forEach((code) => { 3 | let html = code.previousElementSibling; 4 | if (html instanceof HTMLTemplateElement) { 5 | html = html.content.firstElementChild; 6 | } 7 | 8 | const encoded = encodeHTMLEntities(html?.outerHTML!); 9 | code.innerHTML = encoded; 10 | }); 11 | }; 12 | 13 | function encodeHTMLEntities(text: string) { 14 | let textArea = document.createElement("textarea"); 15 | textArea.innerText = text; 16 | let encodedOutput = textArea.innerHTML; 17 | let arr = encodedOutput.split("
"); 18 | const extraWhitespace = countWhitespace(arr[1]) - 2; 19 | arr = arr.slice(0, 1).concat( 20 | arr.slice(1).map((line) => { 21 | return line.slice(extraWhitespace); 22 | }) 23 | ); 24 | 25 | encodedOutput = arr.join("\n"); 26 | return encodedOutput; 27 | } 28 | 29 | function countWhitespace(text: string): number { 30 | const matches = /^\s+/.exec(text); 31 | return matches?.[0]?.length ?? 0; 32 | } 33 | -------------------------------------------------------------------------------- /docs/src/ts/tabs.ts: -------------------------------------------------------------------------------- 1 | export const initTabs = () => { 2 | document.querySelectorAll(".tabs > .active").forEach((activeTab) => { 3 | const activeBody = document.getElementById( 4 | (activeTab as HTMLElement).dataset.elementId! 5 | ); 6 | if (activeBody) { 7 | activeBody.style.display = "flex"; 8 | } 9 | }); 10 | document.querySelectorAll(".tabs > *").forEach((tab) => { 11 | tab.addEventListener("click", () => { 12 | tab.parentElement?.querySelectorAll("*").forEach((t) => { 13 | t.classList.remove("active"); 14 | 15 | const tBody = document.getElementById( 16 | (t as HTMLElement).dataset.elementId! 17 | ); 18 | if (tBody) { 19 | tBody.style.display = "none"; 20 | } 21 | }); 22 | tab.classList.add("active"); 23 | 24 | const tabBody = document.getElementById( 25 | (tab as HTMLElement).dataset.elementId! 26 | ); 27 | if (tabBody) { 28 | tabBody.style.display = "flex"; 29 | } 30 | }); 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /docs/src/ts/theme-switcher.ts: -------------------------------------------------------------------------------- 1 | export const initThemeSwitcher = () => { 2 | let prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; 3 | document 4 | .querySelector("[data-theme-switcher]") 5 | ?.addEventListener("click", (e) => { 6 | console.log(e); 7 | prefersDark = !prefersDark; 8 | 9 | (e.target as HTMLButtonElement).innerText = prefersDark 10 | ? "light_mode" 11 | : "dark_mode"; 12 | document.documentElement.setAttribute( 13 | "data-theme", 14 | prefersDark ? "dark" : "light" 15 | ); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /docs/src/ts/toasts.ts: -------------------------------------------------------------------------------- 1 | export const initToasts = () => { 2 | document.querySelectorAll("[data-open-toast]").forEach((button) => { 3 | button.addEventListener("click", () => { 4 | const templateId = (button as HTMLElement).dataset.template; 5 | const template = document.getElementById( 6 | templateId! 7 | ) as HTMLTemplateElement; 8 | const toast = template?.content.firstElementChild?.cloneNode( 9 | true 10 | ) as HTMLElement; 11 | document.body.appendChild(toast); 12 | setTimeout(() => { 13 | toast.remove(); 14 | }, 60000); 15 | 16 | toast.querySelector("button")?.addEventListener("click", () => { 17 | toast.classList.add("early-out"); 18 | }); 19 | }); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /docs/src/usage/buttons.html: -------------------------------------------------------------------------------- 1 |

Buttons

2 |
3 |
Type
4 |
Example
5 |
Source code
6 | 7 | Regular / primary 8 | 9 | 10 | 11 | Outline / secondary 12 | 13 | 14 | 15 | Discrete 16 | 17 | 18 | 19 | Disabled 20 | 21 | 22 | 23 | Icon left 24 | 25 | 26 | 27 | Icon right 28 | 29 | 30 | 31 | Icon only 32 | 33 | 34 | 35 | Icon only, discrete 36 | 37 | 38 | 39 | Form button 40 |
41 | 42 |
43 | 44 | 45 | Button group 46 |
47 | 48 | 49 | 50 |
51 | 52 | 53 | Cool usecase 54 |
55 | 58 | 59 |
60 | 61 | 62 | Danger outline button 63 | 64 | 65 | 66 | Warning outline button 67 | 68 | 69 |
70 | -------------------------------------------------------------------------------- /docs/src/usage/icons.html: -------------------------------------------------------------------------------- 1 |

Icons

2 |

3 | Open Components uses Material Icons. For a full list of avaiable icons see: 4 | https://fonts.google.com/icons?icon.set=Material+Icons 9 |

10 |
11 |
Type
12 |
Example
13 |
Source code
14 | 15 | Icon 16 | mood 17 | 18 |
19 | -------------------------------------------------------------------------------- /docs/src/usage/inputs.html: -------------------------------------------------------------------------------- 1 |

Inputs

2 |
3 |
Type
4 |
Example
5 |
Source code
6 | 7 | Text 8 | 9 | 10 | 11 | Search 12 | 13 | 14 | 15 | Email 16 | 17 | 18 | 19 | Password 20 | 21 | 22 | 23 | Textarea 24 | 25 | 26 | 27 | File input 28 | 29 | 30 | 31 | Select 32 | 35 | 36 | 37 | Range 38 | 39 | 40 | 41 | Autocomplete 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 | <input type="text" list="items" />
50 | <datalist id="items">
51 |   <option>Lorem ipsum</option>
52 |   <option>dolor sit</option>
53 |   <option>amet</option>
54 | </datalist>
55 |   
56 | 57 | Date 58 | 59 | 60 | 61 | Disabled 62 | 63 | 64 | 65 | Invalid 66 | 67 | 68 | 69 | Checkbox 70 | 75 | 76 | 77 | Radio 78 | 83 | 84 |
85 | -------------------------------------------------------------------------------- /docs/src/usage/modals.html: -------------------------------------------------------------------------------- 1 |

Modals

2 |
3 |
Type
4 |
Example
5 |
Source code
6 | 7 | Body only 8 | 9 | 18 | 19 | 20 | Body and footer 21 | 24 | 36 | 37 | 38 | Header, body and footer 39 | 40 | 54 | 55 | 56 | With close button 57 | 58 | 69 | 70 |
71 | -------------------------------------------------------------------------------- /docs/src/usage/tabs.html: -------------------------------------------------------------------------------- 1 |

Tabs

2 |
3 |
Type
4 |
Example
5 |
Source code
6 | 7 | Tabs only 8 |
9 |
General
10 |
Account
11 |
Disabled
12 |
13 | 14 | 15 | With icons 16 |
17 |
18 | General settings 19 |
20 |
Account person
21 |
22 | Disabled do_not_disturb_on 23 |
24 |
25 | 26 |
27 | -------------------------------------------------------------------------------- /docs/src/usage/toasts.html: -------------------------------------------------------------------------------- 1 |

Toasts

2 |

3 | Also known as a snackbar or simply notification. Toasts should only contain 4 | non-crucial information and should always dissapear automatically, with or 5 | without an action. TODO make them stack 6 |

7 |
8 |
Type
9 |
Example
10 |
Source code
11 | 12 | Text only 13 | 14 | 17 | 18 | 19 | With action 20 | 21 | 27 | 28 |
29 | -------------------------------------------------------------------------------- /docs/src/usage/tooltips.html: -------------------------------------------------------------------------------- 1 |

Tooltips

2 |

3 | Warning: when you set the `data-tooltip` property on an element, it will get 4 | `position: relative`. This might cause unexpected behaviour but can easily be 5 | overriden of course. 6 |

7 |
8 |
Type
9 |
Example
10 |
Source code
11 | 12 | Simple tooltip 13 |

Hover over me

14 | 15 |
16 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM"], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | // "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "noEmit": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noImplicitReturns": true, 17 | "skipLibCheck": true 18 | }, 19 | "include": ["src", "docs/main.ts"] 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@riskchallenger/open-components", 3 | "private": false, 4 | "version": "1.6.4", 5 | "author": "RiskChallenger (https://riskchallenger.nl)", 6 | "source": "src/main.css", 7 | "scripts": { 8 | "build": "parcel build --dist-dir .", 9 | "dev": "parcel", 10 | "dev-docs": "parcel docs/src/index.html --target default", 11 | "build-docs": "parcel build docs/src/index.html --dist-dir . --public-url . --no-optimize", 12 | "prepare": "husky install", 13 | "semantic-release": "semantic-release" 14 | }, 15 | "dependencies": { 16 | "open-props": "^1.4.16" 17 | }, 18 | "devDependencies": { 19 | "@semantic-release/changelog": "^6.0.2", 20 | "@semantic-release/git": "^10.0.1", 21 | "@typescript-eslint/eslint-plugin": "^5.49.0", 22 | "@typescript-eslint/parser": "^5.49.0", 23 | "eslint": "^8.32.0", 24 | "eslint-config-prettier": "^8.6.0", 25 | "husky": "^8.0.3", 26 | "lint-staged": "^13.1.0", 27 | "parcel": "^2.9.3", 28 | "postcss": "^8.4.21", 29 | "postcss-nesting": "^10.2.0", 30 | "posthtml-include": "^1.7.4", 31 | "prettier": "2.8.3", 32 | "semantic-release": "^19.0.3" 33 | }, 34 | "files": [ 35 | "main.css", 36 | "*.svg" 37 | ], 38 | "unpkg": "main.css", 39 | "lint-staged": { 40 | "**/*": "prettier --write --ignore-unknown" 41 | }, 42 | "repository": { 43 | "type": "git", 44 | "url": "git+https://github.com/RiskChallenger/open-components.git" 45 | }, 46 | "description": "Docs at https://riskchallenger.github.io/open-components/", 47 | "bugs": { 48 | "url": "https://github.com/RiskChallenger/open-components/issues" 49 | }, 50 | "homepage": "https://github.com/RiskChallenger/open-components#readme", 51 | "directories": { 52 | "doc": "docs" 53 | }, 54 | "keywords": [ 55 | "css", 56 | "open", 57 | "components", 58 | "props" 59 | ], 60 | "license": "ISC", 61 | "@parcel/transformer-css": { 62 | "drafts": { 63 | "nesting": true 64 | } 65 | }, 66 | "browserslist": "> 0.5%, not dead" 67 | } 68 | -------------------------------------------------------------------------------- /public/chevron_down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/chevron_up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/email_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/lock_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/search_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/button-groups.css: -------------------------------------------------------------------------------- 1 | :where(.button-group) { 2 | display: flex; 3 | 4 | & > * { 5 | border-radius: 0; 6 | 7 | &:not(:last-child) { 8 | border-right-width: 0; 9 | } 10 | } 11 | 12 | & > :first-child { 13 | border-top-left-radius: var(--default-radius); 14 | border-bottom-left-radius: var(--default-radius); 15 | } 16 | & > :last-child { 17 | border-top-right-radius: var(--default-radius); 18 | border-bottom-right-radius: var(--default-radius); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/buttons.css: -------------------------------------------------------------------------------- 1 | :where(button, .button, input[type="button"]) { 2 | --button-color: var(--brand); 3 | 4 | position: relative; 5 | display: inline-flex; 6 | align-items: center; 7 | padding-inline: var(--size-relative-3); 8 | padding-block: var(--size-relative-2); 9 | border-radius: var(--default-radius); 10 | box-shadow: var(--shadow-3); 11 | background-color: var(--button-color); 12 | gap: var(--size-relative-2); 13 | border: var(--border-size-1) solid transparent; 14 | 15 | color: var(--gray-1); 16 | font-weight: var(--font-weight-5); 17 | 18 | transition-duration: var(--default-animation-time); 19 | 20 | &:hover { 21 | background-color: color-mix(in srgb, var(--button-color) 80%, white); 22 | } 23 | 24 | &:active { 25 | background-color: color-mix(in srgb, var(--button-color) 70%, white); 26 | transition: 0s; 27 | } 28 | 29 | &:disabled { 30 | pointer-events: none; 31 | background-color: color-mix(in srgb, var(--button-color) 60%, white); 32 | } 33 | 34 | &.outline { 35 | background-color: transparent; 36 | color: var(--button-color); 37 | border-color: var(--button-color); 38 | 39 | &:hover { 40 | background-color: color-mix( 41 | in srgb, 42 | transparent 90%, 43 | var(--surface-contrast) 44 | ); 45 | } 46 | 47 | &:active { 48 | background-color: color-mix( 49 | in srgb, 50 | transparent 80%, 51 | var(--surface-contrast) 52 | ); 53 | } 54 | 55 | &:disabled { 56 | color: var(--text-3); 57 | background-color: transparent; 58 | border-color: var(--surface-4); 59 | } 60 | } 61 | 62 | &.discrete { 63 | background-color: transparent; 64 | color: var(--text-1); 65 | border-color: transparent; 66 | box-shadow: none; 67 | 68 | &:hover { 69 | background-color: color-mix( 70 | in srgb, 71 | transparent 85%, 72 | var(--surface-contrast) 73 | ); 74 | } 75 | 76 | &:active { 77 | background-color: color-mix( 78 | in srgb, 79 | transparent 75%, 80 | var(--surface-contrast) 81 | ); 82 | } 83 | 84 | &:disabled { 85 | color: var(--text-3); 86 | background-color: transparent; 87 | } 88 | } 89 | 90 | &.material-icons { 91 | padding-inline: var(--size-2); 92 | padding-block: var(--size-2); 93 | } 94 | 95 | & span.material-icons { 96 | &.left { 97 | margin-left: calc(-1 * var(--size-relative-1)); 98 | } 99 | &.right { 100 | margin-right: calc(-1 * var(--size-relative-1)); 101 | } 102 | } 103 | 104 | &.danger { 105 | --button-color: var(--red-9); 106 | } 107 | &.warning { 108 | --button-color: var(--yellow-7); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/components/checkboxes.css: -------------------------------------------------------------------------------- 1 | :where(.checkbox) { 2 | display: inline-block; 3 | position: relative; 4 | padding-left: calc(var(--size-relative-5) + var(--size-relative-3)); 5 | cursor: pointer; 6 | 7 | &:hover :where(input:not(:checked)) ~ :where(.checkmark) { 8 | background-color: var(--surface-2); 9 | } 10 | 11 | & :where(input) { 12 | position: absolute; 13 | height: 0; 14 | width: 0; 15 | opacity: 0; 16 | 17 | &:checked ~ :where(.checkmark) { 18 | background-color: var(--brand); 19 | 20 | &:after { 21 | transform: translate(-50%, -50%) rotate(45deg) scale(1); 22 | } 23 | } 24 | 25 | &:disabled ~ :where(.checkmark) { 26 | cursor: not-allowed; 27 | background-color: var(--surface-1); 28 | border: var(--border-size-1) solid var(--surface-4); 29 | color: var(--text-3); 30 | } 31 | 32 | &:checked:disabled ~ :where(.checkmark) { 33 | background-color: var(--brand); 34 | 35 | &:after { 36 | border-color: var(--gray-3); 37 | } 38 | } 39 | } 40 | 41 | & :where(.checkmark) { 42 | position: absolute; 43 | inset: 0; 44 | height: var(--size-relative-5); 45 | width: var(--size-relative-5); 46 | background-color: var(--surface-0); 47 | box-shadow: var(--shadow-2), 0 0 1px 1px var(--surface-3); 48 | border-radius: var(--default-radius); 49 | transition: background-color 300ms ease-in-out; 50 | 51 | &:after { 52 | content: ""; 53 | position: absolute; 54 | left: 50%; 55 | top: calc(50% - 0.2em / 2); 56 | width: 0.4em; 57 | height: 0.8em; 58 | border: solid var(--gray-1); 59 | border-width: 0 0.2em 0.2em 0; 60 | transform: translate(-50%, -50%) rotate(45deg) scale(0); 61 | transition: transform 300ms ease-in-out; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/inputs.css: -------------------------------------------------------------------------------- 1 | :where(input:not([type="range"], [type="button"])) { 2 | background-color: var(--surface-0); 3 | box-shadow: var(--shadow-2), 0 0 1px 1px var(--surface-3); 4 | padding-inline: var(--size-relative-3); 5 | padding-block: var(--size-relative-2); 6 | border-radius: var(--default-radius); 7 | transition: outline-width var(--default-animation-time); 8 | outline: 0px solid var(--brand); 9 | outline-offset: 0; 10 | 11 | &:focus-visible { 12 | outline-width: var(--border-size-2); 13 | outline-color: var(--brand); 14 | } 15 | 16 | &::placeholder { 17 | font-weight: var(--font-weight-5); 18 | color: var(--text-3); 19 | opacity: 1; 20 | } 21 | 22 | &:disabled { 23 | cursor: not-allowed; 24 | background-color: var(--surface-1); 25 | border: var(--border-size-1) solid var(--surface-4); 26 | color: var(--text-3); 27 | } 28 | 29 | &:where(.invalid) { 30 | outline: var(--border-size-1) solid var(--red-9); 31 | } 32 | 33 | &[type="search"] { 34 | padding-inline-start: calc(2 * var(--size-relative-2) + 24px); 35 | background-image: url("/public/search_icon.svg"); 36 | background-position: var(--size-relative-2) center; 37 | background-size: var(--size-relative-5); 38 | } 39 | 40 | &[type="email"] { 41 | padding-inline-start: calc(2 * var(--size-relative-2) + 24px); 42 | background-image: url("/public/email_icon.svg"); 43 | background-position: var(--size-relative-2) center; 44 | background-size: var(--size-relative-5); 45 | } 46 | 47 | &[type="password"] { 48 | padding-inline-start: calc(2 * var(--size-relative-2) + 24px); 49 | background-image: url("/public/lock_icon.svg"); 50 | background-position: var(--size-relative-2) center; 51 | background-size: var(--size-relative-5); 52 | } 53 | 54 | &[type="text"][list] { 55 | background-image: url("/public/chevron_down.svg"); 56 | background-position: calc(100% - 0.5em) center; 57 | background-size: var(--size-relative-5); 58 | 59 | &:focus { 60 | background-image: url("/public/chevron_up.svg"); 61 | } 62 | 63 | &::-webkit-calendar-picker-indicator { 64 | color: transparent; 65 | } 66 | } 67 | 68 | &[type="file"] { 69 | border: none; 70 | max-inline-size: 100%; 71 | padding: 0; 72 | padding-inline-end: var(--size-relative-3); 73 | background-color: var(--surface-0); 74 | } 75 | } 76 | 77 | :where(input[type="file"])::-webkit-file-upload-button, 78 | :where(input[type="file"])::file-selector-button { 79 | padding-inline: var(--size-relative-3); 80 | padding-block: var(--size-relative-2); 81 | border-radius: var(--default-radius); 82 | box-shadow: var(--shadow-2), 0 0 1px 1px var(--surface-3); 83 | background-color: var(--surface-1); 84 | border: var(--border-size-1) solid transparent; 85 | 86 | color: var(--text-1); 87 | font-weight: var(--font-weight-5); 88 | 89 | margin: var(--size-3); 90 | cursor: pointer; 91 | } 92 | 93 | input[type="range"] { 94 | height: var(--size-1); 95 | } 96 | -------------------------------------------------------------------------------- /src/components/modals.css: -------------------------------------------------------------------------------- 1 | :where(.backdrop) { 2 | --animation-time: 500ms; 3 | --body-padding: var(--size-4) var(--size-6); 4 | 5 | display: flex; 6 | flex-direction: column; 7 | align-items: center; 8 | 9 | position: fixed; 10 | inset: 0; 11 | z-index: var(--layer-4); 12 | background-color: hsla(0, 0%, 0%, 60%); 13 | opacity: 0; 14 | animation: fade-in var(--animation-time) var(--ease-3) forwards; 15 | } 16 | 17 | :where(.modal) { 18 | position: relative; 19 | display: flex; 20 | flex-direction: column; 21 | background-color: var(--surface-1); 22 | min-width: 100vw; 23 | max-width: 1200px; 24 | max-height: 100vh; 25 | width: 100%; 26 | animation: var(--animation-slide-in-down) forwards; 27 | 28 | @media (min-width: 600px) { 29 | width: max-content; 30 | min-width: var(--size-xs); 31 | max-height: calc(100vh - 2 * var(--size-8)); 32 | top: calc(var(--size-8)); 33 | margin-bottom: var(--size-8); 34 | border-radius: var(--radius-3); 35 | } 36 | 37 | & :where(:not(.modal-heading) > h3, .modal-heading) { 38 | padding: var(--body-padding); 39 | max-width: 100%; 40 | border-bottom: 1px solid var(--gray-4); 41 | display: flex; 42 | align-items: center; 43 | justify-content: space-between; 44 | 45 | &.modal-heading { 46 | padding-inline-end: var(--size-3); 47 | } 48 | } 49 | 50 | & :where(.modal-body) { 51 | padding: var(--body-padding); 52 | overflow: auto; 53 | } 54 | 55 | & :where(.modal-footer) { 56 | display: flex; 57 | gap: var(--size-relative-3); 58 | justify-content: flex-end; 59 | border-top: 1px solid var(--gray-4); 60 | padding: var(--body-padding); 61 | } 62 | } 63 | 64 | :where(.removing) { 65 | animation: fade-out var(--animation-time) var(--ease-3) forwards; 66 | 67 | & :where(.modal) { 68 | animation: var(--animation-slide-out-up) forwards; 69 | } 70 | } 71 | 72 | @keyframes fade-in { 73 | from { 74 | opacity: 0; 75 | } 76 | to { 77 | opacity: 1; 78 | } 79 | } 80 | 81 | @keyframes fade-out { 82 | from { 83 | opacity: 1; 84 | } 85 | to { 86 | opacity: 0; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/components/radios.css: -------------------------------------------------------------------------------- 1 | :where(.radio) { 2 | display: inline-block; 3 | position: relative; 4 | padding-left: calc(var(--size-relative-5) + var(--size-relative-3)); 5 | cursor: pointer; 6 | 7 | &:hover :where(input:not(:checked)) ~ :where(.checkmark) { 8 | background-color: var(--surface-2); 9 | } 10 | 11 | & :where(input) { 12 | position: absolute; 13 | height: 0; 14 | width: 0; 15 | opacity: 0; 16 | 17 | &:checked ~ :where(.checkmark) { 18 | background-color: var(--brand); 19 | 20 | &:after { 21 | transform: translate(-50%, -50%) rotate(45deg) scale(1); 22 | } 23 | } 24 | 25 | &:disabled ~ :where(.checkmark) { 26 | cursor: not-allowed; 27 | background-color: var(--surface-1); 28 | border: var(--border-size-1) solid var(--surface-4); 29 | color: var(--text-3); 30 | } 31 | 32 | &:checked:disabled ~ :where(.checkmark) { 33 | background-color: var(--brand); 34 | 35 | &:after { 36 | border-color: var(--gray-3); 37 | } 38 | } 39 | } 40 | 41 | & :where(.checkmark) { 42 | position: absolute; 43 | inset: 0; 44 | height: var(--size-relative-5); 45 | width: var(--size-relative-5); 46 | background-color: var(--surface-0); 47 | box-shadow: var(--shadow-2), 0 0 1px 1px var(--surface-3); 48 | border-radius: 50%; 49 | transition: background-color 300ms ease-in-out; 50 | 51 | &:after { 52 | content: ""; 53 | position: absolute; 54 | left: 50%; 55 | top: 50%; 56 | width: 40%; 57 | height: 40%; 58 | border-radius: 50%; 59 | background-color: var(--gray-1); 60 | transform: translate(-50%, -50%) scale(0); 61 | transition: transform 300ms ease-in-out; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/scrollbars.css: -------------------------------------------------------------------------------- 1 | /* Width and height */ 2 | ::-webkit-scrollbar { 3 | width: var(--size-2); 4 | height: var(--size-1); 5 | } 6 | 7 | /* Track */ 8 | ::-webkit-scrollbar-track { 9 | background-color: var(--surface-1); 10 | } 11 | 12 | /* Handle */ 13 | ::-webkit-scrollbar-thumb { 14 | background-color: var(--surface-4); 15 | } 16 | -------------------------------------------------------------------------------- /src/components/selects.css: -------------------------------------------------------------------------------- 1 | :where(select) { 2 | background-color: var(--surface-0); 3 | box-shadow: var(--shadow-2), 0 0 1px 1px var(--surface-3); 4 | padding-block: var(--size-relative-2); 5 | padding-inline: var(--size-relative-3) var(--size-relative-7); 6 | border-radius: var(--default-radius); 7 | transition: outline-width var(--default-animation-time); 8 | outline: 0px solid var(--brand); 9 | outline-offset: 0; 10 | 11 | appearance: none; 12 | background-image: url("/public/chevron_down.svg"); 13 | background-position: calc(100% - 0.5em) center; 14 | background-size: var(--size-relative-5); 15 | 16 | &:focus { 17 | outline-width: var(--border-size-2); 18 | background-image: url("/public/chevron_up.svg"); 19 | } 20 | 21 | &:disabled { 22 | cursor: not-allowed; 23 | background-color: var(--surface-1); 24 | border: var(--border-size-1) solid var(--surface-4); 25 | color: var(--text-3); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/tabs.css: -------------------------------------------------------------------------------- 1 | :where(.tabs) { 2 | display: flex; 3 | border-bottom: var(--border-size-1) solid var(--gray-5); 4 | overflow: auto; 5 | 6 | & > :where(*) { 7 | display: flex; 8 | align-items: center; 9 | gap: var(--size-relative-1); 10 | color: var(--text-3); 11 | padding-inline: var(--size-relative-3); 12 | padding-block: var(--size-relative-1); 13 | 14 | &:where(:not(.disabled)):hover { 15 | cursor: pointer; 16 | color: var(--text-1); 17 | border-bottom: var(--border-size-1) solid var(--text-1); 18 | } 19 | 20 | &:where(.disabled) { 21 | cursor: not-allowed; 22 | } 23 | 24 | &:where(.active) { 25 | color: var(--text-1); 26 | border-bottom: var(--border-size-1) solid var(--text-1); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/textareas.css: -------------------------------------------------------------------------------- 1 | :where(textarea) { 2 | background-color: var(--surface-0); 3 | box-shadow: var(--shadow-2), 0 0 1px 1px var(--surface-3); 4 | padding-inline: var(--size-relative-3); 5 | padding-block: var(--size-relative-2); 6 | border-radius: var(--default-radius); 7 | transition: outline-width var(--default-animation-time); 8 | outline: 0px solid var(--brand); 9 | outline-offset: 0; 10 | 11 | &:focus-visible { 12 | outline-width: var(--border-size-2); 13 | outline-color: var(--brand); 14 | } 15 | 16 | &::placeholder { 17 | font-weight: var(--font-weight-5); 18 | color: var(--text-3); 19 | opacity: 1; 20 | } 21 | 22 | &:disabled { 23 | cursor: not-allowed; 24 | background-color: var(--surface-1); 25 | border: var(--border-size-1) solid var(--surface-4); 26 | color: var(--text-3); 27 | } 28 | 29 | &:where(.invalid) { 30 | outline: var(--border-size-1) solid var(--red-9); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/toasts.css: -------------------------------------------------------------------------------- 1 | :where(.toast) { 2 | position: fixed; 3 | z-index: var(--layer-important); 4 | bottom: var(--size-5); 5 | left: 50%; 6 | min-width: var(--size-xs); 7 | margin-inline: var(--size-7); 8 | background-color: var(--surface-contrast); 9 | color: var(--text-contrast); 10 | padding: var(--size-relative-3) var(--size-relative-4); 11 | border-radius: var(--default-radius); 12 | box-shadow: var(--shadow-3); 13 | transform: translateX(-50%); 14 | 15 | display: flex; 16 | justify-content: space-between; 17 | 18 | animation: slide-up 0.5s var(--ease-3) forwards, 19 | slide-up 0.5s var(--ease-3) reverse forwards; 20 | animation-delay: 0s, 4s; 21 | 22 | & :where(button) { 23 | background: transparent; 24 | color: var(--text-contrast); 25 | margin-block: calc(-1 * var(--size-relative-2)); 26 | margin-right: calc(-1 * var(--size-relative-2)); 27 | padding-inline: var(--size-relative-2); 28 | padding-block: var(--size-relative-1); 29 | } 30 | 31 | &.early-out { 32 | animation: fade-scale-out 0.5s var(--ease-3) forwards; 33 | } 34 | } 35 | 36 | @keyframes slide-up { 37 | from { 38 | transform: translate(-50%, calc(100% + var(--size-5))); 39 | } 40 | } 41 | 42 | @keyframes fade-scale-out { 43 | to { 44 | transform: translateX(-50%) scale(0.75); 45 | opacity: 0; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/components/tooltips.css: -------------------------------------------------------------------------------- 1 | :where([data-tooltip]) { 2 | position: relative; 3 | 4 | &:disabled { 5 | pointer-events: auto; 6 | } 7 | 8 | &:before { 9 | position: absolute; 10 | content: attr(data-tooltip); 11 | padding: var(--size-relative-1) var(--size-relative-2); 12 | color: var(--text-1); 13 | background-color: var(--surface-1); 14 | box-shadow: var(--shadow-3); 15 | display: none; 16 | opacity: 0; 17 | border-radius: var(--default-radius); 18 | top: 100%; 19 | z-index: var(--layer-important); 20 | white-space: nowrap; 21 | 22 | /* Explicitly set font in case of tooltip on material-icons */ 23 | font-family: var(--font-sans); 24 | font-size: var(--font-size-1); 25 | pointer-events: none; 26 | } 27 | 28 | &:hover:before { 29 | display: block; 30 | animation: var(--animation-fade-in) forwards; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main.css: -------------------------------------------------------------------------------- 1 | @import "npm:open-props/open-props.min.css"; 2 | @import "npm:open-props/normalize.min.css"; 3 | @import "npm:open-props/theme.light.switch.min.css"; 4 | @import "npm:open-props/theme.dark.switch.min.css"; 5 | 6 | @import url("https://fonts.googleapis.com/icon?family=Material+Icons"); 7 | 8 | @import "props/variable-sizes.css"; 9 | @import "props/theme-extended.css"; 10 | @import "props/light-extended.css"; 11 | @import "props/dark-extended.css"; 12 | 13 | @import "components/buttons.css"; 14 | @import "components/button-groups.css"; 15 | 16 | @import "components/inputs.css"; 17 | @import "components/selects.css"; 18 | @import "components/checkboxes.css"; 19 | @import "components/radios.css"; 20 | @import "components/textareas.css"; 21 | 22 | @import "components/tabs.css"; 23 | @import "components/modals.css"; 24 | @import "components/tooltips.css"; 25 | @import "components/toasts.css"; 26 | @import "components/scrollbars.css"; 27 | 28 | :where(html) { 29 | --brand: var(--cyan-6); 30 | --default-radius: var(--radius-2); 31 | --default-animation-time: 300ms; 32 | 33 | @media (prefers-reduced-motion: reduce) { 34 | --default-animation-time: 0s; 35 | } 36 | 37 | font-family: var(--font-sans); 38 | } 39 | 40 | :where(body) { 41 | color: var(--text-1); 42 | } 43 | -------------------------------------------------------------------------------- /src/props/dark-extended.css: -------------------------------------------------------------------------------- 1 | :where([data-theme="dark"], .dark, .dark-theme) { 2 | --surface-0: var(--gray-12); 3 | --surface-contrast: var(--gray-0); 4 | --text-contrast: var(--gray-9); 5 | } 6 | -------------------------------------------------------------------------------- /src/props/light-extended.css: -------------------------------------------------------------------------------- 1 | :where([data-theme="light"], .light, .light-theme) { 2 | --surface-0: white; 3 | --surface-contrast: var(--gray-10); 4 | --text-contrast: var(--gray-0); 5 | } 6 | -------------------------------------------------------------------------------- /src/props/theme-extended.css: -------------------------------------------------------------------------------- 1 | :where(html) { 2 | --surface-0: white; 3 | --surface-contrast: var(--gray-9); 4 | --text-3: var(--gray-6); /* same for light and dark */ 5 | --text-contrast: var(--gray-0); 6 | } 7 | 8 | @media (prefers-color-scheme: dark) { 9 | :where(html) { 10 | --surface-0: var(--gray-10); 11 | --surface-contrast: var(--gray-0); 12 | --text-contrast: var(--gray-9); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/props/variable-sizes.css: -------------------------------------------------------------------------------- 1 | :where(html) { 2 | --size-relative-1: 0.25em; 3 | --size-relative-2: 0.5em; 4 | --size-relative-3: 1em; 5 | --size-relative-4: 1.25em; 6 | --size-relative-5: 1.5em; 7 | --size-relative-6: 1.75em; 8 | --size-relative-7: 2em; 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM"], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "resolveJsonModule": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "noEmit": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "skipLibCheck": true 17 | }, 18 | "include": ["src"] 19 | } 20 | --------------------------------------------------------------------------------