diff --git a/README.md b/README.md index 86b32c5..49ccf53 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ## Installation ```sh -npm install unocss unocss-preset-daisy @kidonng/daisyui +npm install unocss daisyui unocss-preset-daisy ``` ## Usage @@ -17,13 +17,12 @@ npm install unocss unocss-preset-daisy @kidonng/daisyui ```js import {defineConfig} from 'vite' import unocss from 'unocss/vite' -import {presetUno, transformerDirectives} from 'unocss' +import {presetUno} from 'unocss' import {presetDaisy} from 'unocss-preset-daisy' export default defineConfig({ plugins: [ unocss({ - transformers: [transformerDirectives()], presets: [presetUno(), presetDaisy()], }), ], @@ -35,14 +34,13 @@ export default defineConfig({ ```js import {defineConfig} from 'astro/config' import unocss from 'unocss/vite' -import {presetUno, transformerDirectives} from 'unocss' +import {presetUno} from 'unocss' import {presetDaisy} from 'unocss-preset-daisy' export default defineConfig({ vite: { plugins: [ unocss({ - transformers: [transformerDirectives()], presets: [presetUno(), presetDaisy()], }), ], @@ -56,13 +54,12 @@ To use UnoCSS with Nuxt, `@unocss/nuxt` must be installed as well. ```js import {defineNuxtConfig} from 'nuxt' -import {presetUno, transformerDirectives} from 'unocss' +import {presetUno} from 'unocss' import {presetDaisy} from 'unocss-preset-daisy' export default defineNuxtConfig({ modules: ['@unocss/nuxt'], unocss: { - transformers: [transformerDirectives()], presets: [presetUno(), presetDaisy()], }, }) @@ -76,27 +73,35 @@ After configuring the framework, add these imports to your entrypoint: // `@unocss/reset` comes with `unocss` // If you are using pnpm, install it separately unless you enable hoisting import '@unocss/reset/tailwind.css' -// Import `@kidonng/daisyui` **BEFORE** `uno.css` -import '@kidonng/daisyui/index.css' import 'uno.css' ``` -### Custom themes +## Config + +This preset accepts [the same config as daisyUI](https://daisyui.com/docs/config/) (except for `logs`, `prefix` and `darkTheme`). + +```js +{ + presets: [ + presetUno(), + presetDaisy({ + styled: false, + themes: ['light', 'dark'], + }), + ], +} +``` -> **Note**: Refer to [`@kidonng/daisyui` documentation](https://github.com/kidonng/daisyui#themes) for built-in themes. +### Custom themes Use [UnoCSS's theming system](https://github.com/unocss/unocss#extend-theme) to customize the theme. ```js { - // UnoCSS config - transformers: [transformerDirectives()], presets: [presetUno(), presetDaisy()], - // Custom themes theme: { - // This is NOT a theme name, it must be `colors` colors: { - // Refer to https://daisyui.com/docs/colors/ for the list of color names + // Refer to https://daisyui.com/docs/colors/ for colors neutral: 'red', // Use camelCase instead of kebab-case (e.g. `neutral-focus`) neutralFocus: 'green', @@ -111,26 +116,10 @@ Use [UnoCSS's theming system](https://github.com/unocss/unocss#extend-theme) to For details, please read [issue #9](https://github.com/kidonng/unocss-preset-daisy/issues/9#issuecomment-1452292840). -## Questions - -**How to use a built-in theme / unstyled components?** - -Please refer to [`@kidonng/daisyui` usage](https://github.com/kidonng/daisyui#usage). - -**`@kidonng/daisyui/index.css` imports EVERYTHING!** - -This entry imports all styles for easier use. - -Since daisyUI is utility-first, the styles can be compressed very efficiently. Minified size of all styles is about 143 KB (as of writing), but **only 20 KB** after gzipping. - -If this is unsatisfying, you can [only import the styles you actually use](https://github.com/kidonng/daisyui#usage). It may sound cumbersome but in fact not so, since they only need to be imported once globally. - -You can also use [PurgeCSS](https://purgecss.com/), though it doesn't play nice with UnoCSS (or Vite in large). - -**Why use `@kidonng/daisyui` instead of the official `daisyui` package?** +## Limitations -[`@kidonng/daisyui`](https://github.com/kidonng/daisyui) is a redistribution of daisyUI, to make it compatible with UnoCSS. +**This is not a full daisyUI port.** All daisyUI components/utilities should work but they may not work with some UnoCSS features: -**I'm expecting a full UnoCSS port!** +- [#14](https://github.com/kidonng/unocss-preset-daisy/issues/14): [variants](https://windicss.org/utilities/general/variants.html) do not work -Not currently, sorry. I envision a future where we don't need `@kidonng/daisyui`, which is one of the reasons this is a separate module. +**Unused styles may be imported.** This is both due to lots of hacks being used and how UnoCSS works. However, the preset try to figure out the minimum styles needed, thus the cost is trivial most of the time. diff --git a/daisyui.d.ts b/daisyui.d.ts index 58532fc..1778a1a 100644 --- a/daisyui.d.ts +++ b/daisyui.d.ts @@ -3,3 +3,70 @@ declare module 'daisyui/src/colors/index.js' { const colors: Record string> export default colors } + +declare module 'daisyui/dist/utilities.js' { + import type {CssInJs} from 'postcss-js' + + const utilities: CssInJs + export default utilities +} + +declare module 'daisyui/dist/base.js' { + import type {CssInJs} from 'postcss-js' + + const base: CssInJs + export default base +} + +declare module 'daisyui/dist/unstyled.js' { + import type {CssInJs} from 'postcss-js' + + const unstyled: CssInJs + export default unstyled +} + +declare module 'daisyui/dist/unstyled.rtl.js' { + import type {CssInJs} from 'postcss-js' + + const unstyledRtl: CssInJs + export default unstyledRtl +} + +declare module 'daisyui/dist/styled.js' { + import type {CssInJs} from 'postcss-js' + + const styled: CssInJs + export default styled +} + +declare module 'daisyui/dist/styled.rtl.js' { + import type {CssInJs} from 'postcss-js' + + const styledRtl: CssInJs + export default styledRtl +} + +declare module 'daisyui/dist/utilities-unstyled.js' { + import type {CssInJs} from 'postcss-js' + + const utilitiesUnstyled: CssInJs + export default utilitiesUnstyled +} + +declare module 'daisyui/dist/utilities-styled.js' { + import type {CssInJs} from 'postcss-js' + + const utilitiesStyled: CssInJs + export default utilitiesStyled +} + +declare module 'daisyui/src/colors/themes.js' { + const themes: Record> + export default themes +} + +declare module 'daisyui/src/colors/functions.js' { + export function convertToHsl( + input: Record, + ): Record +} diff --git a/demo/index.html b/demo/index.html index e345975..0fb56e9 100644 --- a/demo/index.html +++ b/demo/index.html @@ -101,7 +101,10 @@

Badge

Card

- Shoes + Shoes

Shoes!

@@ -246,6 +249,16 @@

Radio

+

Layout

+ +
+

Mask

+ +
+

Navigation

@@ -382,7 +395,6 @@

Congratulations random Internet user!

diff --git a/demo/vite.config.ts b/demo/vite.config.ts index 44eda82..df8f94c 100644 --- a/demo/vite.config.ts +++ b/demo/vite.config.ts @@ -1,13 +1,12 @@ import {defineConfig} from 'vite' // eslint-disable-next-line n/file-extension-in-import import unocss from 'unocss/vite' -import {presetUno, presetIcons, transformerDirectives} from 'unocss' +import {presetUno, presetIcons} from 'unocss' import {presetDaisy} from '../index.js' export default defineConfig({ plugins: [ unocss({ - transformers: [transformerDirectives()], presets: [presetUno(), presetDaisy(), presetIcons()], }), ], diff --git a/index.ts b/index.ts index 0d4f7e1..53f9385 100644 --- a/index.ts +++ b/index.ts @@ -1,36 +1,190 @@ -import type {Preset} from 'unocss' +import postcss, {type Container, type Rule} from 'postcss' +import {parse} from 'postcss-js' +import {tokenize, type ClassToken, type AttributeToken} from 'parsel-js' +import type {Preset, Preflight, DynamicRule} from 'unocss' import camelCase from 'camelcase' + import colors from 'daisyui/src/colors/index.js' +import utilities from 'daisyui/dist/utilities.js' +import base from 'daisyui/dist/base.js' +import unstyled from 'daisyui/dist/unstyled.js' +import unstyledRtl from 'daisyui/dist/unstyled.rtl.js' +import styled from 'daisyui/dist/styled.js' +import styledRtl from 'daisyui/dist/styled.rtl.js' +import utilitiesUnstyled from 'daisyui/dist/utilities-unstyled.js' +import utilitiesStyled from 'daisyui/dist/utilities-styled.js' +import themes from 'daisyui/src/colors/themes.js' +import colorFunctions from 'daisyui/src/colors/functions.js' + +const processor = postcss.default() +const toCss = (css: Container) => processor.process(css).css + +const replacePrefix = (css: string) => css.replace(/--tw-/g, '--un-') +// UnoCSS uses comma syntax +const replaceSlash = (css: string) => css.replace(/\) \/ /g, '), ') +const replaceSpace = (css: string) => css.replace(/ /g, ', ') + +export const presetDaisy = ( + options: { + styled?: boolean + themes?: boolean | string[] + base?: boolean + utils?: boolean + rtl?: boolean + } = {}, +): Preset => { + const rules = new Map() + const keyframes: string[] = [] + const overrides: string[] = [] + + const styles = [ + // Components + options.styled === false + ? options.rtl + ? unstyledRtl + : unstyled + : options.rtl + ? styledRtl + : styled, + ] + if (options.utils !== false) + styles.push(utilities, utilitiesUnstyled, utilitiesStyled) + + for (const node of styles.flatMap((style) => parse(style).nodes)) { + const isAtRule = node.type === 'atrule' + if (isAtRule && node.name === 'keyframes') { + keyframes.push(toCss(node)) + continue + } + + // Unwrap `@media` if necessary + const rule = (isAtRule ? node.nodes[0]! : node) as Rule + + let selector = rule.selectors[0]! + // Skip modifiers + if ( + selector.startsWith('.collapse-open') || + selector.startsWith('.modal-open') + ) + selector = rule.selectors[1]! + + const token = tokenize(selector)[0]! + let base = '' + + if (token.type === 'class') base = token.name + else if (token.type === 'pseudo-class' && token.name === 'where') + base = (tokenize(token.argument!)[0] as ClassToken).name + // `[dir="rtl"] .foo` & `:root .foo` + else base = (tokenize(selector)[2] as ClassToken).name + + rules.set( + base, + (rules.get(base) ?? '') + replaceSlash(replacePrefix(toCss(rule))) + '\n', + ) + } + + for (const rule of [ + // Move after `btn-*` + 'btn-outline', + // Resolve conflicts with @unocss/preset-wind `link:` variant + 'link-primary', + 'link-secondary', + 'link-accent', + 'link-neutral', + 'link-success', + 'link-info', + 'link-warning', + 'link-error', + 'link-hover', + ]) { + overrides.push(rules.get(rule)!) + rules.delete(rule) + } + + const preflights: Preflight[] = [ + { + // eslint-disable-next-line @typescript-eslint/naming-convention + getCSS: () => + toCss( + parse( + Object.fromEntries( + Object.entries(themes) + .filter(([selector]) => { + const theme = (tokenize(selector)[0] as AttributeToken).value! + + if (options.themes === false) return theme === 'light' + if (Array.isArray(options.themes)) + return options.themes.includes(theme) + + return true + }) + .map(([selector, colors], index) => { + const theme = (tokenize(selector)[0] as AttributeToken).value! + const isDefault = Array.isArray(options.themes) + ? index === 0 + : theme === 'light' + + return [ + isDefault ? `:root, ${selector}` : selector, + Object.fromEntries( + Object.entries(colorFunctions.convertToHsl(colors)).map( + ([prop, value]) => [prop, replaceSpace(value)], + ), + ), + ] + }), + ), + ), + ), + }, + { + // eslint-disable-next-line @typescript-eslint/naming-convention + getCSS: () => keyframes.join('\n'), + }, + { + layer: 'default', + // eslint-disable-next-line @typescript-eslint/naming-convention + getCSS: () => overrides.join('\n'), + }, + ] + + if (options.base !== false) + preflights.push({ + // eslint-disable-next-line @typescript-eslint/naming-convention + getCSS: () => replaceSlash(replacePrefix(toCss(parse(base)))), + }) -const colorEntries = Object.entries(colors) - -export const presetDaisy = (): Preset => ({ - name: 'daisy', - theme: { - colors: { - ...Object.fromEntries( - colorEntries - .filter( - ([color]) => - // Already in preset-mini - // https://github.com/unocss/unocss/blob/0f7efcba592e71d81fbb295332b27e6894a0b4fa/packages/preset-mini/src/_theme/colors.ts#L11-L12 - !['transparent', 'current'].includes(color) && - // Added below - !color.startsWith('base'), - ) - .map(([color, value]) => [camelCase(color), value({})]), - ), - base: Object.fromEntries( - colorEntries - .filter(([color]) => color.startsWith('base')) - .map(([color, value]) => [color.replace('base-', ''), value({})]), - ), + return { + name: 'unocss-preset-daisy', + preflights, + theme: { + colors: { + ...Object.fromEntries( + Object.entries(colors) + .filter( + ([color]) => + // Already in preset-mini + // https://github.com/unocss/unocss/blob/0f7efcba592e71d81fbb295332b27e6894a0b4fa/packages/preset-mini/src/_theme/colors.ts#L11-L12 + !['transparent', 'current'].includes(color) && + // Added below + !color.startsWith('base'), + ) + .map(([color, value]) => [camelCase(color), value({})]), + ), + base: Object.fromEntries( + Object.entries(colors) + .filter(([color]) => color.startsWith('base')) + .map(([color, value]) => [color.replace('base-', ''), value({})]), + ), + }, }, - }, - rules: [ - [ - /^rounded-(?:box|btn|badge)$/, - ([name]) => ({'border-radius': `var(--${name})`}), - ], - ], -}) + rules: [...rules].map( + (rule) => + [ + new RegExp(`^${rule[0]}$`), + () => rule[1], + {layer: 'components'}, + ] as DynamicRule, + ), + } +} diff --git a/package.json b/package.json index 4c622ac..16f9f81 100644 --- a/package.json +++ b/package.json @@ -27,15 +27,18 @@ ], "dependencies": { "camelcase": "^7.0.1", - "daisyui": "^2.51.3" + "parsel-js": "^1.1.0", + "postcss": "^8.4.21", + "postcss-js": "^4.0.1" }, "peerDependencies": { - "@kidonng/daisyui": "^2.51.3", + "daisyui": "^2.51.3", "unocss": "^0.50.3" }, "devDependencies": { "@iconify-json/octicon": "^1.1.34", "@sindresorhus/tsconfig": "^3.0.1", + "@types/postcss-js": "^4.0.0", "@unocss/reset": "^0.50.3", "typescript": "^4.9.5", "unbuild": "^1.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 59a501c..4528857 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,11 +2,14 @@ lockfileVersion: 5.4 specifiers: '@iconify-json/octicon': ^1.1.34 - '@kidonng/daisyui': ^2.51.3 '@sindresorhus/tsconfig': ^3.0.1 + '@types/postcss-js': ^4.0.0 '@unocss/reset': ^0.50.3 camelcase: ^7.0.1 daisyui: ^2.51.3 + parsel-js: ^1.1.0 + postcss: ^8.4.21 + postcss-js: ^4.0.1 typescript: ^4.9.5 unbuild: ^1.1.2 unocss: ^0.50.3 @@ -14,14 +17,17 @@ specifiers: xo: ^0.53.1 dependencies: - '@kidonng/daisyui': 2.51.3 camelcase: 7.0.1 daisyui: 2.51.3_gbtt6ss3tbiz4yjtvdr6fbrj44 - unocss: 0.50.3_postcss@8.4.21+vite@4.1.4 + parsel-js: 1.1.0 + postcss: 8.4.21 + postcss-js: 4.0.1_postcss@8.4.21 + unocss: 0.50.3_qhs3skccxyc5vhpfwyf2knsc54 devDependencies: '@iconify-json/octicon': 1.1.34 '@sindresorhus/tsconfig': 3.0.1 + '@types/postcss-js': 4.0.0 '@unocss/reset': 0.50.3 typescript: 4.9.5 unbuild: 1.1.2 @@ -725,10 +731,6 @@ packages: '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.4.14 - /@kidonng/daisyui/2.51.3: - resolution: {integrity: sha512-1f8/goiYAqW9DxMBSrI8pwDU9YpOkjuCtu5RLW+tZook5YQqoLuG4NV/DgBnP+4AUXNAquZGciMf2Cem5IrKsw==} - dev: false - /@nodelib/fs.scandir/2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -827,20 +829,6 @@ packages: rollup: 3.18.0 dev: true - /@rollup/pluginutils/5.0.2: - resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0 - peerDependenciesMeta: - rollup: - optional: true - dependencies: - '@types/estree': 1.0.0 - estree-walker: 2.0.2 - picomatch: 2.3.1 - dev: false - /@rollup/pluginutils/5.0.2_rollup@3.18.0: resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==} engines: {node: '>=14.0.0'} @@ -854,7 +842,6 @@ packages: estree-walker: 2.0.2 picomatch: 2.3.1 rollup: 3.18.0 - dev: true /@sindresorhus/tsconfig/3.0.1: resolution: {integrity: sha512-0/gtPNTY3++0J2BZM5nHHULg0BIMw886gqdn8vWN+Av6bgF5ZU2qIcHubAn+Z9KNvJhO8WFE+9kDOU3n6OcKtA==} @@ -913,6 +900,12 @@ packages: resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} dev: true + /@types/postcss-js/4.0.0: + resolution: {integrity: sha512-ieu0u7T7jnUCFmXcf8JzoJ3/1U5NI3ZYpfX5zW2td0x/BqVQrpCQ6uOY9Ef/0WJuIjc9OCtdfYF/JE7MGax2TA==} + dependencies: + postcss: 8.4.21 + dev: true + /@types/resolve/1.20.2: resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} dev: true @@ -1051,24 +1044,24 @@ packages: eslint-visitor-keys: 3.3.0 dev: true - /@unocss/astro/0.50.3_vite@4.1.4: + /@unocss/astro/0.50.3_rollup@3.18.0+vite@4.1.4: resolution: {integrity: sha512-XVYkTusLUplgzWW5KMk3UGASQH39SMTU8yH8pszyCzR75zTPNlbMzkk1dI8epqikx839mOBPL6nLL9kWwlIfcg==} dependencies: '@unocss/core': 0.50.3 '@unocss/reset': 0.50.3 - '@unocss/vite': 0.50.3_vite@4.1.4 + '@unocss/vite': 0.50.3_rollup@3.18.0+vite@4.1.4 transitivePeerDependencies: - rollup - vite dev: false - /@unocss/cli/0.50.3: + /@unocss/cli/0.50.3_rollup@3.18.0: resolution: {integrity: sha512-eIDFy2VZtTsfZ5ZpVf8YckS27UjdLuO7A6eJDv0ZZCKF6ITXTtfSkB9+N6bNNVkY/gAbb1ctvzytvCn6aUhyGg==} engines: {node: '>=14'} hasBin: true dependencies: '@ampproject/remapping': 2.2.0 - '@rollup/pluginutils': 5.0.2 + '@rollup/pluginutils': 5.0.2_rollup@3.18.0 '@unocss/config': 0.50.3 '@unocss/core': 0.50.3 '@unocss/preset-uno': 0.50.3 @@ -1206,13 +1199,13 @@ packages: '@unocss/core': 0.50.3 dev: false - /@unocss/vite/0.50.3_vite@4.1.4: + /@unocss/vite/0.50.3_rollup@3.18.0+vite@4.1.4: resolution: {integrity: sha512-phahYMPWSXIc2ArxD8qpeu2ctgjHyOViaIld4t6exTUPlgJzgHn2Ru+FNwCVBXm2Gc9ZvslGx7mVh5IzD9kafQ==} peerDependencies: vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 dependencies: '@ampproject/remapping': 2.2.0 - '@rollup/pluginutils': 5.0.2 + '@rollup/pluginutils': 5.0.2_rollup@3.18.0 '@unocss/config': 0.50.3 '@unocss/core': 0.50.3 '@unocss/inspector': 0.50.3 @@ -3738,6 +3731,10 @@ packages: lines-and-columns: 1.2.4 dev: true + /parsel-js/1.1.0: + resolution: {integrity: sha512-+CAY5A3p8b6he3OzlY/naXpeeiLMjEqFUyMwiPrwnemG5yh0/sgygYMKRhtn6/YSriyy4KZwQLnpBfs36GnXUg==} + dev: false + /path-exists/4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -4558,7 +4555,7 @@ packages: engines: {node: '>= 10.0.0'} dev: true - /unocss/0.50.3_postcss@8.4.21+vite@4.1.4: + /unocss/0.50.3_qhs3skccxyc5vhpfwyf2knsc54: resolution: {integrity: sha512-6quL19+WPJeuQmVdDIbSSXARHUhIvJSyJQYzHVlZRX4tv9fb+ZeZ2o8upRWodlIT2ulmDRnhXcUQyVdSRnRDDA==} engines: {node: '>=14'} peerDependencies: @@ -4567,8 +4564,8 @@ packages: '@unocss/webpack': optional: true dependencies: - '@unocss/astro': 0.50.3_vite@4.1.4 - '@unocss/cli': 0.50.3 + '@unocss/astro': 0.50.3_rollup@3.18.0+vite@4.1.4 + '@unocss/cli': 0.50.3_rollup@3.18.0 '@unocss/core': 0.50.3 '@unocss/postcss': 0.50.3_postcss@8.4.21 '@unocss/preset-attributify': 0.50.3 @@ -4584,7 +4581,7 @@ packages: '@unocss/transformer-compile-class': 0.50.3 '@unocss/transformer-directives': 0.50.3 '@unocss/transformer-variant-group': 0.50.3 - '@unocss/vite': 0.50.3_vite@4.1.4 + '@unocss/vite': 0.50.3_rollup@3.18.0+vite@4.1.4 transitivePeerDependencies: - postcss - rollup