├── .eslintignore ├── .gitignore ├── .editorconfig ├── __tests__ └── index.test.js ├── babel.config.js ├── .prettierrc ├── src └── index.js ├── jest.config.js ├── .travis.yml ├── webpack.config.js ├── jest └── customMatchers.js ├── .github └── ISSUE_TEMPLATE │ ├── ---feature-request.md │ └── ---bug-report.md ├── .eslintrc.json ├── LICENSE ├── package.json ├── README.md └── CODE_OF_CONDUCT.md /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | import plugin from '../src/index' 2 | 3 | describe('plugin', () => { 4 | it('should return an object', () => { 5 | expect(plugin).toBeDefined() 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | }, 10 | ], 11 | ], 12 | } 13 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "printWidth": 100, 5 | "tabWidth": 2, 6 | "useTabs": false, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true, 9 | "parser": "flow", 10 | "endOfLine": "auto" 11 | } 12 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ addVariant, e }) => { 2 | addVariant('dropdown', ({ modifySelectors, separator }) => { 3 | modifySelectors(({ className }) => { 4 | return `.${e(`dropdown${separator}${className}`)}:focus-within > ul` 5 | }) 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | collectCoverageFrom: ['src/**/*.js'], 3 | coverageThreshold: { 4 | global: { 5 | branches: 100, 6 | functions: 0, 7 | lines: 25, 8 | statements: 25, 9 | }, 10 | }, 11 | setupFilesAfterEnv: ['./jest/customMatchers.js'], 12 | } 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: npm 3 | notifications: 4 | email: false 5 | node_js: 6 | - 12 7 | - 14 8 | - node 9 | install: 10 | - npm install 11 | script: 12 | - npm run lint 13 | - npm run test:coverage 14 | - npm run build 15 | after_success: 16 | - npm run codecov 17 | - npm run semantic-release 18 | branches: 19 | only: 20 | - master 21 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | mode: 'production', 5 | entry: './src/index.js', 6 | output: { 7 | path: path.resolve(__dirname, 'dist'), 8 | filename: 'index.js', 9 | libraryTarget: 'commonjs2', 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.m?js$/, 15 | exclude: /(node_modules|bower_components)/, 16 | use: { 17 | loader: 'babel-loader', 18 | options: { 19 | presets: ['@babel/preset-env'], 20 | }, 21 | }, 22 | }, 23 | ], 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /jest/customMatchers.js: -------------------------------------------------------------------------------- 1 | expect.extend({ 2 | // Compare two CSS strings with all whitespace removed 3 | // This is probably naive but it's fast and works well enough. 4 | toMatchCss(received, argument) { 5 | function stripped(str) { 6 | return str.replace(/\s/g, '') 7 | } 8 | 9 | if (stripped(received) === stripped(argument)) { 10 | return { 11 | message: () => `expected ${received} not to match CSS ${argument}`, 12 | pass: true, 13 | } 14 | } else { 15 | return { 16 | message: () => `expected ${received} to match CSS ${argument}`, 17 | pass: false, 18 | } 19 | } 20 | }, 21 | }) 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F4A1 Feature request" 3 | about: 'I have a suggestion (and might want to implement myself)! ' 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "parserOptions": { 6 | "ecmaVersion": 2018, 7 | "sourceType": "module" 8 | }, 9 | "extends": ["standard", "prettier"], 10 | "plugins": ["prettier"], 11 | "rules": { 12 | "no-unused-vars": [ 13 | 2, 14 | { 15 | "args": "all", 16 | "argsIgnorePattern": "^_" 17 | } 18 | ], 19 | "no-warning-comments": 0, 20 | "prettier/prettier": [ 21 | "error", 22 | { 23 | "semi": false, 24 | "singleQuote": true, 25 | "printWidth": 100, 26 | "tabWidth": 2, 27 | "useTabs": false, 28 | "trailingComma": "es5", 29 | "bracketSpacing": true, 30 | "parser": "flow", 31 | "endOfLine": "auto" 32 | } 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Estevan Maito 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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: 'Bugs, missing documentation, or unexpected behavior ' 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | 19 | 20 | - `tailwindcss-dropdown` version: 21 | - Are you using plugins? Which? 22 | 23 | ### Relevant code or config: 24 | 25 | ```html 26 | 27 | ``` 28 | 29 | ### What you did: 30 | 31 | 32 | 33 | ### What happened: 34 | 35 | 36 | 37 | ### Reproduction: 38 | 39 | 45 | 46 | ### Problem description: 47 | 48 | 49 | 50 | ### Suggested solution: 51 | 52 | 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tailwindcss-dropdown", 3 | "version": "0.0.0-development", 4 | "description": "Tailwind CSS plugin to style create accessible dropdowns", 5 | "main": "src/index.js", 6 | "files": [ 7 | "src", 8 | "README.md" 9 | ], 10 | "scripts": { 11 | "cz": "git-cz", 12 | "test": "jest", 13 | "codecov": "codecov", 14 | "test:watch": "jest --watch", 15 | "test:coverage": "jest --coverage", 16 | "lint": "eslint .", 17 | "lint:fix": "eslint . --fix", 18 | "prebuild": "rimraf dist", 19 | "build": "webpack", 20 | "semantic-release": "semantic-release" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/estevanmaito/tailwindcss-dropdown.git" 25 | }, 26 | "keywords": [], 27 | "author": "Estevan Maito ", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/estevanmaito/tailwindcss-dropdown/issues" 31 | }, 32 | "homepage": "https://github.com/estevanmaito/tailwindcss-dropdown#readme", 33 | "devDependencies": { 34 | "@babel/cli": "7.8.4", 35 | "@babel/core": "7.9.6", 36 | "@babel/preset-env": "7.9.6", 37 | "babel-loader": "8.1.0", 38 | "codecov": "3.6.5", 39 | "commitizen": "4.1.2", 40 | "cz-conventional-changelog": "3.2.0", 41 | "eslint": "7.0.0", 42 | "eslint-config-prettier": "6.11.0", 43 | "eslint-config-standard": "14.1.1", 44 | "eslint-plugin-import": "2.20.2", 45 | "eslint-plugin-node": "11.1.0", 46 | "eslint-plugin-prettier": "3.1.3", 47 | "eslint-plugin-promise": "4.2.1", 48 | "eslint-plugin-standard": "4.0.1", 49 | "husky": "4.2.5", 50 | "jest": "26.0.1", 51 | "lodash": "4.17.19", 52 | "postcss": "7.0.30", 53 | "postcss-selector-parser": "6.0.2", 54 | "prettier": "2.0.5", 55 | "rimraf": "3.0.2", 56 | "semantic-release": "^17.0.8", 57 | "tailwindcss": "1.4.6", 58 | "webpack": "4.43.0", 59 | "webpack-cli": "3.3.11" 60 | }, 61 | "config": { 62 | "commitizen": { 63 | "path": "./node_modules/cz-conventional-changelog" 64 | } 65 | }, 66 | "husky": { 67 | "hooks": { 68 | "pre-commit": "npm run lint && npm run test:coverage" 69 | } 70 | }, 71 | "dependencies": {} 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tailwind CSS Dropdown 2 | 3 |

4 | codecov 5 | Travis (.org) 6 | npm 7 | MIT License 8 |

9 | 10 | A plugin to create accessible, JavaScript free dropdowns with Tailwind CSS. This is not a component (although you can use the examples below as a blueprint). 11 | 12 | [🧪 See it live on CodeSandbox](https://codesandbox.io/s/tailwind-css-dropdown-plugin-y8n6w?file=/public/index.html) 13 | 14 | ## 💿 Install 15 | 16 | ```sh 17 | npm install tailwindcss-dropdown 18 | ``` 19 | 20 | In `tailwind.config.js` add `dropdown` to your `display` variants and require the plugin, like this: 21 | 22 | ```js 23 | module.exports = { 24 | theme: {}, 25 | variants: { 26 | display: ['responsive', 'dropdown'] 27 | }, 28 | plugins: [require('tailwindcss-dropdown')], 29 | } 30 | ``` 31 | 32 | 33 | ## 🚀 Usage 34 | 35 | This plugin adds `dropdown` as a variant for `display` utilities. Translating: you can use `dropdown:block` to change the `display` of a child `ul` to `block` (or any other property listed [here](https://tailwindcss.com/docs/display/#app)) 36 | 37 | It depends heavily on the structure of your HTML. eg: 38 | 39 | ```html 40 | 53 | ``` 54 | 55 | Note that `dropdown:block` must contain a `ul` as child. The same could be done with a `button`: 56 | 57 | ```html 58 | 59 | 67 | ``` 68 | 69 | [More examples on CodeSandbox](https://codesandbox.io/s/tailwind-css-dropdown-plugin-y8n6w?file=/public/index.html) 70 | 71 | ## 🧙‍♂️ How it works? 72 | 73 | The code for `dropdown:block` (and for every other display value, like `dropdown:grid`, etc) will look like this: 74 | 75 | ```css 76 | .dropdown\:block:focus-within > ul { 77 | display: block; 78 | } 79 | ``` 80 | 81 | So, when the element with class `dropdown:block` has focus within, the `ul` inside it turns into a `block`. 82 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [INSERT CONTACT]. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | --------------------------------------------------------------------------------