diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b20595..8908016 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,18 +5,6 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased - @headlessui/vue] - -### Fixed - -- Allow to override the `type` on the `ComboboxInput` ([#1476](https://github.com/tailwindlabs/headlessui/pull/1476)) -- Ensure the the `` closes correctly ([#1477](https://github.com/tailwindlabs/headlessui/pull/1477)) -- Only render the `FocusSentinel` if required in the `Tabs` component ([#1493](https://github.com/tailwindlabs/headlessui/pull/1493)) - -### Added - -- Add `by` prop for `Listbox`, `Combobox` and `RadioGroup` ([#1482](https://github.com/tailwindlabs/headlessui/pull/1482)) - ## [Unreleased - @headlessui/react] ### Fixed @@ -28,6 +16,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add `by` prop for `Listbox`, `Combobox` and `RadioGroup` ([#1482](https://github.com/tailwindlabs/headlessui/pull/1482)) +- Add `@headlessui/tailwindcss` plugin ([#1487](https://github.com/tailwindlabs/headlessui/pull/1487)) + +## [Unreleased - @headlessui/vue] + +### Fixed + +- Allow to override the `type` on the `ComboboxInput` ([#1476](https://github.com/tailwindlabs/headlessui/pull/1476)) +- Ensure the the `` closes correctly ([#1477](https://github.com/tailwindlabs/headlessui/pull/1477)) +- Only render the `FocusSentinel` if required in the `Tabs` component ([#1493](https://github.com/tailwindlabs/headlessui/pull/1493)) + +### Added + +- Add `by` prop for `Listbox`, `Combobox` and `RadioGroup` ([#1482](https://github.com/tailwindlabs/headlessui/pull/1482)) +- Add `@headlessui/tailwindcss` plugin ([#1487](https://github.com/tailwindlabs/headlessui/pull/1487)) + +## [Unreleased - @headlessui/tailwindcss] + +### Added + +- Add `@headlessui/tailwindcss` plugin ([#1487](https://github.com/tailwindlabs/headlessui/pull/1487)) ## [@headlessui/vue@v1.6.2] - 2022-05-19 diff --git a/packages/@headlessui-react/src/utils/render.ts b/packages/@headlessui-react/src/utils/render.ts index ce3047d..36369ea 100644 --- a/packages/@headlessui-react/src/utils/render.ts +++ b/packages/@headlessui-react/src/utils/render.ts @@ -125,6 +125,22 @@ function _render( ;(rest as any).className = rest.className(slot) } + let dataAttributes: Record = {} + if (slot) { + let exposeState = false + let states = [] + for (let [k, v] of Object.entries(slot)) { + if (typeof v === 'boolean') { + exposeState = true + } + if (v === true) { + states.push(k) + } + } + + if (exposeState) dataAttributes[`data-headlessui-state`] = states.join(' ') + } + if (Component === Fragment) { if (Object.keys(compact(rest)).length > 0) { if ( @@ -158,6 +174,7 @@ function _render( {}, // Filter out undefined values so that they don't override the existing values mergeProps(resolvedChildren.props, compact(omit(rest, ['ref']))), + dataAttributes, refRelatedProps ) ) @@ -166,7 +183,12 @@ function _render( return createElement( Component, - Object.assign({}, omit(rest, ['ref']), Component !== Fragment && refRelatedProps), + Object.assign( + {}, + omit(rest, ['ref']), + Component !== Fragment && refRelatedProps, + Component !== Fragment && dataAttributes + ), resolvedChildren ) } diff --git a/packages/@headlessui-tailwindcss/build/index.cjs b/packages/@headlessui-tailwindcss/build/index.cjs new file mode 100644 index 0000000..a67ef10 --- /dev/null +++ b/packages/@headlessui-tailwindcss/build/index.cjs @@ -0,0 +1,8 @@ +'use strict' + +let plugin = + process.env.NODE_ENV === 'production' + ? require('./headlessui.prod.cjs') + : require('./headlessui.dev.cjs') + +module.exports = (plugin.__esModule ? plugin : { default: plugin }).default diff --git a/packages/@headlessui-tailwindcss/package.json b/packages/@headlessui-tailwindcss/package.json new file mode 100644 index 0000000..1ddf821 --- /dev/null +++ b/packages/@headlessui-tailwindcss/package.json @@ -0,0 +1,44 @@ +{ + "name": "@headlessui/tailwindcss", + "version": "0.0.0", + "description": "A complementary Tailwind CSS plugin", + "main": "dist/index.cjs", + "types": "dist/index.d.ts", + "license": "MIT", + "files": [ + "README.md", + "dist" + ], + "exports": { + "require": "./dist/index.cjs" + }, + "sideEffects": false, + "engines": { + "node": ">=10" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/tailwindlabs/headlessui.git", + "directory": "packages/@headlessui-tailwindcss" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "prepublishOnly": "npm run build", + "build": "../../scripts/build.sh --external:tailwindcss && node ./scripts/fix-types.js", + "watch": "../../scripts/watch.sh --external:tailwindcss", + "test": "../../scripts/test.sh", + "lint": "../../scripts/lint.sh", + "clean": "rimraf ./dist" + }, + "peerDependencies": { + "tailwindcss": "^3.0" + }, + "devDependencies": { + "esbuild": "^0.11.18" + }, + "dependencies": { + "tailwindcss": "^0.0.0-insiders.83b4811" + } +} diff --git a/packages/@headlessui-tailwindcss/scripts/fix-types.js b/packages/@headlessui-tailwindcss/scripts/fix-types.js new file mode 100644 index 0000000..9bd2f8b --- /dev/null +++ b/packages/@headlessui-tailwindcss/scripts/fix-types.js @@ -0,0 +1,23 @@ +let fs = require('fs/promises') +let path = require('path') + +/** + * Everything in the Headless UI codebase is written in TypeScript, however the + * `@headlessui/tailwindcss` plugin has to compile to a CommonJs file. This works great, however + * the types that were generated by tsc use `export default _default` instead of `export = + * _default` even if we use `module: CommonJs` in the `tsconfig.json` + * + * Don't want to spend too much time on this problem, so doing this little hack to change the + * exported type. This allows us to use the `@headlessui/tailwindcss` plugin and have types in a + * CommonJs environment. + **/ + +let types = path.resolve(__dirname, '..', 'dist', 'index.d.ts') + +async function run() { + let contents = await fs.readFile(types, 'utf8') + contents = contents.replace('export default', 'export =') + await fs.writeFile(types, contents, 'utf8') +} + +run() diff --git a/packages/@headlessui-tailwindcss/src/index.ts b/packages/@headlessui-tailwindcss/src/index.ts new file mode 100644 index 0000000..50400e6 --- /dev/null +++ b/packages/@headlessui-tailwindcss/src/index.ts @@ -0,0 +1,36 @@ +import plugin from 'tailwindcss/plugin' + +interface Options { + /** + * The prefix used for the variants. This defaults to `ui`. + * + * Usage example: + * ```html + *
+ * ``` + **/ + prefix?: string +} + +export default plugin.withOptions(({ prefix = 'ui' } = {}) => { + return ({ addVariant }) => { + for (let state of ['open', 'checked', 'selected', 'active', 'disabled']) { + // TODO: Once `:has()` is properly supported, then we can switch to this version: + // addVariant(`${prefix}-${state}`, [ + // `&[data-headlessui-state~="${state}"]`, + // `:where([data-headlessui-state~="${state}"]):not(:has([data-headlessui-state])) &`, + // ]) + + // But for now, this will do: + addVariant(`${prefix}-${state}`, [ + `&[data-headlessui-state~="${state}"]`, + `:where([data-headlessui-state~="${state}"]) &`, + ]) + + addVariant(`${prefix}-not-${state}`, [ + `&[data-headlessui-state]:not([data-headlessui-state~="${state}"])`, + `:where([data-headlessui-state]:not([data-headlessui-state~="${state}"]) &:not([data-headlessui-state]))`, + ]) + } + } +}) diff --git a/packages/@headlessui-tailwindcss/tsconfig.json b/packages/@headlessui-tailwindcss/tsconfig.json new file mode 100644 index 0000000..9195421 --- /dev/null +++ b/packages/@headlessui-tailwindcss/tsconfig.json @@ -0,0 +1,31 @@ +{ + "include": ["src"], + "compilerOptions": { + "module": "ESNext", + "lib": [], + "importHelpers": true, + "declaration": true, + "sourceMap": true, + "rootDir": "./src", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "downlevelIteration": true, + "moduleResolution": "node", + "baseUrl": "./", + "paths": { + "*": ["src/*", "node_modules/*"] + }, + "jsx": "preserve", + "esModuleInterop": true, + "target": "ESNext", + "allowJs": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": false + }, + "exclude": ["node_modules", "**/*.test.tsx?"] +} diff --git a/packages/@headlessui-vue/src/utils/render.ts b/packages/@headlessui-vue/src/utils/render.ts index 05221a7..10c7426 100644 --- a/packages/@headlessui-vue/src/utils/render.ts +++ b/packages/@headlessui-vue/src/utils/render.ts @@ -85,6 +85,22 @@ function _render({ let children = slots.default?.(slot) + let dataAttributes: Record = {} + if (slot) { + let exposeState = false + let states = [] + for (let [k, v] of Object.entries(slot)) { + if (typeof v === 'boolean') { + exposeState = true + } + if (v === true) { + states.push(k) + } + } + + if (exposeState) dataAttributes[`data-headlessui-state`] = states.join(' ') + } + if (as === 'template') { if (Object.keys(incomingProps).length > 0 || Object.keys(attrs).length > 0) { let [firstChild, ...other] = children ?? [] @@ -112,7 +128,10 @@ function _render({ ) } - return cloneVNode(firstChild, incomingProps as Record) + return cloneVNode( + firstChild, + Object.assign({}, incomingProps as Record, dataAttributes) + ) } if (Array.isArray(children) && children.length === 1) { @@ -122,7 +141,7 @@ function _render({ return children } - return h(as, incomingProps, children) + return h(as, Object.assign({}, incomingProps, dataAttributes), children) } export function compact>(object: T) { diff --git a/packages/playground-react/package.json b/packages/playground-react/package.json index 211fdf2..bd8d99a 100644 --- a/packages/playground-react/package.json +++ b/packages/playground-react/package.json @@ -3,7 +3,9 @@ "private": true, "version": "0.0.0", "scripts": { - "prebuild": "yarn workspace @headlessui/react build", + "prebuild": "yarn workspace @headlessui/react build && yarn workspace @headlessui/tailwindcss build", + "predev": "yarn workspace @headlessui/react build && yarn workspace @headlessui/tailwindcss build", + "dev:tailwindcss": "yarn workspace @headlessui/tailwindcss watch", "dev:headlessui": "yarn workspace @headlessui/react watch", "dev:next": "next dev", "dev": "npm-run-all -p dev:*", @@ -13,11 +15,17 @@ }, "dependencies": { "@headlessui/react": "*", + "@headlessui/tailwindcss": "*", "@popperjs/core": "^2.6.0", + "@tailwindcss/forms": "^0.5.2", + "@tailwindcss/typography": "^0.5.2", + "autoprefixer": "^10.4.7", "framer-motion": "^6.0.0", "next": "^12.1.4", + "postcss": "^8.4.14", "react": "^18.0.0", "react-dom": "^18.0.0", - "react-flatpickr": "^3.10.9" + "react-flatpickr": "^3.10.9", + "tailwindcss": "^0.0.0-insiders.83b4811" } } diff --git a/packages/playground-react/pages/_app.tsx b/packages/playground-react/pages/_app.tsx index 1fd7642..c598667 100644 --- a/packages/playground-react/pages/_app.tsx +++ b/packages/playground-react/pages/_app.tsx @@ -2,6 +2,8 @@ import React, { useState, useEffect } from 'react' import Link from 'next/link' import Head from 'next/head' +import 'tailwindcss/tailwind.css' + function disposables() { let disposables: Function[] = [] diff --git a/packages/playground-react/pages/_document.tsx b/packages/playground-react/pages/_document.tsx index 8615a9b..b32c3ab 100644 --- a/packages/playground-react/pages/_document.tsx +++ b/packages/playground-react/pages/_document.tsx @@ -8,7 +8,6 @@ export default function Document() { - { - return classNames( - 'relative cursor-default select-none py-2 pl-3 pr-9 focus:outline-none', - active ? 'bg-indigo-600 text-white' : 'text-gray-900' - ) - }} + className="ui-active:bg-indigo-600 ui-active:text-white ui-not-active:text-gray-900 relative cursor-default select-none py-2 pl-3 pr-9 focus:outline-none" > - {({ active, selected }) => ( - <> - - {name} - - {selected && ( - - - - - - )} - - )} + + {name} + + + + + + ))} diff --git a/packages/playground-react/postcss.config.js b/packages/playground-react/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/packages/playground-react/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/packages/playground-react/tailwind.config.js b/packages/playground-react/tailwind.config.js new file mode 100644 index 0000000..65877d3 --- /dev/null +++ b/packages/playground-react/tailwind.config.js @@ -0,0 +1,11 @@ +/** @type {import('tailwindcss/types').Config} */ +let config = { + content: ['./{pages,components}/**/*.{js,ts,jsx,tsx}'], + plugins: [ + require('@tailwindcss/forms'), + require('@tailwindcss/typography'), + require('@headlessui/tailwindcss'), + ], +} + +module.exports = config diff --git a/packages/playground-vue/index.html b/packages/playground-vue/index.html index 83ca364..4517611 100644 --- a/packages/playground-vue/index.html +++ b/packages/playground-vue/index.html @@ -6,7 +6,6 @@ Headless UI - Playground -
diff --git a/packages/playground-vue/package.json b/packages/playground-vue/package.json index 4e0b6d9..4ef3d42 100644 --- a/packages/playground-vue/package.json +++ b/packages/playground-vue/package.json @@ -6,7 +6,9 @@ "example": "examples" }, "scripts": { - "prebuild": "yarn workspace @headlessui/vue build", + "prebuild": "yarn workspace @headlessui/vue build && yarn workspace @headlessui/tailwindcss build", + "predev": "yarn workspace @headlessui/vue build && yarn workspace @headlessui/tailwindcss build", + "dev:tailwindcss": "yarn workspace @headlessui/tailwindcss watch", "dev:headlessui": "yarn workspace @headlessui/vue watch", "dev:next": "vite serve", "dev": "npm-run-all -p dev:*", @@ -16,6 +18,11 @@ "dependencies": { "@headlessui/vue": "*", "@heroicons/vue": "^1.0.6", + "@tailwindcss/forms": "^0.5.2", + "@tailwindcss/typography": "^0.5.2", + "autoprefixer": "^10.4.7", + "postcss": "^8.4.14", + "tailwindcss": "^0.0.0-insiders.83b4811", "vue": "^3.2.27", "vue-flatpickr-component": "^9.0.5", "vue-router": "^4.0.0" diff --git a/packages/playground-vue/postcss.config.js b/packages/playground-vue/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/packages/playground-vue/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/packages/playground-vue/src/components/listbox/listbox.vue b/packages/playground-vue/src/components/listbox/listbox.vue index a66ff98..a166201 100644 --- a/packages/playground-vue/src/components/listbox/listbox.vue +++ b/packages/playground-vue/src/components/listbox/listbox.vue @@ -41,24 +41,17 @@ :value="person" as="template" :disabled="person.disabled" - v-slot="{ active, selected }" > -
  • +
  • {{ person.name }}