├── .node-version ├── .prettierignore ├── packages ├── demo │ ├── postcss.config.cjs │ ├── tsconfig.json │ ├── src │ │ ├── env.d.ts │ │ ├── styles │ │ │ ├── padded-box.example.css │ │ │ ├── box-to-circle.example.css │ │ │ ├── fluid-border.example.css │ │ │ ├── growing-square.example.css │ │ │ ├── shrinking-square.example.css │ │ │ ├── fluid-grid-gap.example.css │ │ │ ├── positioned-box.example.css │ │ │ ├── typography.example.css │ │ │ └── global.css │ │ ├── utils │ │ │ └── cn.ts │ │ ├── pages │ │ │ ├── examples │ │ │ │ ├── growing-square.astro │ │ │ │ ├── shrinking-square.astro │ │ │ │ ├── fluid-border.astro │ │ │ │ ├── box-to-circle.astro │ │ │ │ ├── positioned-box.astro │ │ │ │ ├── padded-box.astro │ │ │ │ ├── typography.astro │ │ │ │ └── fluid-grid-gap.astro │ │ │ └── index.astro │ │ ├── layouts │ │ │ ├── ExampleLayout.astro │ │ │ └── MainLayout.astro │ │ └── components │ │ │ ├── ResizableWindowDimensions.astro │ │ │ ├── GitHubLink.astro │ │ │ ├── IconHamburger.astro │ │ │ ├── IconWarning.astro │ │ │ ├── IconLockClosed.astro │ │ │ ├── ScreenWidthWarning.astro │ │ │ ├── IconGitHub.astro │ │ │ ├── DemoSection.astro │ │ │ ├── ResizableWindowHeader.astro │ │ │ ├── GitHubBadge.astro │ │ │ └── ResizableWindow.astro │ ├── .gitignore │ ├── astro.config.mjs │ ├── eslint.config.mjs │ ├── package.json │ └── public │ │ └── favicon.svg ├── playground │ ├── postcss.config.cjs │ ├── src │ │ └── index.css │ └── package.json └── postcss-fluid │ ├── tsconfig.json │ ├── tsconfig.build.json │ ├── tsconfig.base.json │ ├── CHANGELOG.md │ ├── eslint.config.mjs │ ├── lib │ ├── index.ts │ ├── utils.ts │ ├── options.ts │ ├── errors.ts │ └── function.ts │ ├── LICENSE │ ├── package.json │ └── tests │ ├── errors.test.ts │ └── index.test.ts ├── .gitignore ├── .gitattributes ├── pnpm-workspace.yaml ├── .vscode ├── extensions.json ├── settings.json └── tailwind.json ├── .editorconfig ├── package.json ├── .prettierrc.mjs ├── nx.json ├── .github └── workflows │ └── ci.yml └── README.md /.node-version: -------------------------------------------------------------------------------- 1 | 22 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | pnpm-lock.yaml 2 | -------------------------------------------------------------------------------- /packages/demo/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require("@lehoczky/postcss-fluid")], 3 | } 4 | -------------------------------------------------------------------------------- /packages/playground/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require("@lehoczky/postcss-fluid")], 3 | } 4 | -------------------------------------------------------------------------------- /packages/demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "exclude": ["dist", "node_modules"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/playground/src/index.css: -------------------------------------------------------------------------------- 1 | div { 2 | font-size: clamp(16px, 5vw, 28px); 3 | line-height: clamp(26px, 5vw, 38px); 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | yarn-error.log 4 | dist/ 5 | 6 | coverage/ 7 | .idea/ 8 | 9 | .nx/cache 10 | .nx/workspace-data 11 | -------------------------------------------------------------------------------- /packages/postcss-fluid/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": ["lib/**/*.ts", "tests/**/*.ts"], 4 | "exclude": ["dist"] 5 | } 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Enforce Unix newlines 2 | * text=auto eol=lf 3 | 4 | # Fix syntax highlighting on GitHub to allow comments 5 | .vscode/*.json linguist-language=JSON-with-Comments 6 | -------------------------------------------------------------------------------- /packages/demo/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/triple-slash-reference */ 2 | /// 3 | /// 4 | -------------------------------------------------------------------------------- /packages/demo/src/styles/padded-box.example.css: -------------------------------------------------------------------------------- 1 | div { 2 | padding: fluid(4px, 40px, 480px, 720px); 3 | } 4 | 5 | /* Compiled output: 6 | div { 7 | padding: clamp(4px, -68px + 15vw, 40px); 8 | } 9 | */ 10 | -------------------------------------------------------------------------------- /packages/demo/src/utils/cn.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]): string { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /packages/demo/src/styles/box-to-circle.example.css: -------------------------------------------------------------------------------- 1 | div { 2 | border-radius: fluid(0, 50px, 480px, 720px); 3 | } 4 | 5 | /* Compiled output: 6 | div { 7 | border-radius: clamp(0px, -100px + 20.8333vw, 50px); 8 | } 9 | */ 10 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | 4 | catalog: 5 | '@types/node': ^22.18.12 6 | typescript: ^5.9.3 7 | 8 | onlyBuiltDependencies: 9 | - '@tailwindcss/oxide' 10 | - esbuild 11 | - nx 12 | - sharp 13 | -------------------------------------------------------------------------------- /packages/demo/src/styles/fluid-border.example.css: -------------------------------------------------------------------------------- 1 | div { 2 | border: fluid(10px, 22px, 480px, 720px) solid black; 3 | } 4 | 5 | /* Compiled output: 6 | div { 7 | border: clamp(10px, -14px + 5vw, 22px) solid black; 8 | } 9 | */ 10 | -------------------------------------------------------------------------------- /packages/demo/src/styles/growing-square.example.css: -------------------------------------------------------------------------------- 1 | div { 2 | width: fluid(50px, 200px, 480px, 720px); 3 | height: fluid(50px, 200px, 480px, 720px); 4 | } 5 | 6 | /* Compiled output: 7 | div { 8 | width: clamp(50px, -250px + 62.5vw, 200px); 9 | height: clamp(50px, -250px + 62.5vw, 200px); 10 | } 11 | */ 12 | -------------------------------------------------------------------------------- /packages/demo/src/styles/shrinking-square.example.css: -------------------------------------------------------------------------------- 1 | div { 2 | width: fluid(200px, 50px, 480px, 720px); 3 | height: fluid(200px, 50px, 480px, 720px); 4 | } 5 | 6 | /* Compiled output: 7 | div { 8 | width: clamp(50px, 500px + -62.5vw, 200px); 9 | height: clamp(50px, 500px + -62.5vw, 200px); 10 | } 11 | */ 12 | -------------------------------------------------------------------------------- /packages/postcss-fluid/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "pretty": true, 6 | "removeComments": false, 7 | "declaration": true 8 | }, 9 | "include": ["lib/**/*.ts"], 10 | "exclude": ["dist", "**/*.test.*"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/demo/src/styles/fluid-grid-gap.example.css: -------------------------------------------------------------------------------- 1 | div { 2 | display: grid; 3 | grid-template-columns: 1fr 1fr; 4 | gap: fluid(0, 30px, 480px, 720px); 5 | } 6 | 7 | /* Compiled output: 8 | div { 9 | display: grid; 10 | grid-template-columns: 1fr 1fr; 11 | gap: clamp(0px, -60px + 12.5vw, 30px); 12 | } 13 | */ 14 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "editorconfig.editorconfig", 4 | "dbaeumer.vscode-eslint", 5 | "davidanson.vscode-markdownlint", 6 | "esbenp.prettier-vscode", 7 | "streetsidesoftware.code-spell-checker", 8 | "astro-build.astro-vscode", 9 | "vitest.explorer" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | indent_size = 2 11 | 12 | [*.md] 13 | max_line_length = off 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /packages/demo/src/styles/positioned-box.example.css: -------------------------------------------------------------------------------- 1 | div { 2 | position: absolute; 3 | top: fluid(0px, 60px, 480px, 720px); 4 | left: fluid(0px, 60px, 480px, 720px); 5 | } 6 | 7 | /* Compiled output: 8 | div { 9 | position: absolute; 10 | top: clamp(0px, -120px + 25vw, 60px); 11 | left: clamp(0px, -120px + 25vw, 60px); 12 | } 13 | */ 14 | -------------------------------------------------------------------------------- /packages/postcss-fluid/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "strict": true, 5 | "lib": ["es2017", "DOM"], 6 | "module": "commonjs", 7 | "baseUrl": ".", 8 | "esModuleInterop": true, 9 | "moduleResolution": "node", 10 | "target": "es2017", 11 | "types": ["node"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/demo/src/pages/examples/growing-square.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import "../../styles/growing-square.example.css" 3 | 4 | import ExampleLayout from "../../layouts/ExampleLayout.astro" 5 | --- 6 | 7 | 8 |
9 |
10 | 11 | 18 | -------------------------------------------------------------------------------- /packages/demo/src/pages/examples/shrinking-square.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import "../../styles/shrinking-square.example.css" 3 | 4 | import ExampleLayout from "../../layouts/ExampleLayout.astro" 5 | --- 6 | 7 | 8 |
9 |
10 | 11 | 18 | -------------------------------------------------------------------------------- /packages/demo/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # generated types 5 | .astro/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | 23 | # jetbrains setting folder 24 | .idea/ 25 | -------------------------------------------------------------------------------- /packages/demo/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import tailwindcss from "@tailwindcss/vite" 2 | import { defineConfig } from "astro/config" 3 | 4 | // https://astro.build/config 5 | export default defineConfig({ 6 | site: "https://lehoczky.github.io", 7 | base: "postcss-fluid", 8 | 9 | devToolbar: { 10 | enabled: false, 11 | }, 12 | 13 | vite: { 14 | plugins: [tailwindcss()], 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /packages/postcss-fluid/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## 1.0.3 — 21 April 2023 6 | 7 | - Show more detailed error messages 8 | 9 | ## 1.0.2 — 03 June 2022 10 | 11 | - Handle 0 values without unit 12 | 13 | ## 1.0.1 — 25 May 2022 14 | 15 | - Throw better error message 16 | 17 | ## 1.0.0 — 24 May 2022 18 | 19 | - Initial release 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lehoczky/postcss-fluid-monorepo", 3 | "private": true, 4 | "scripts": {}, 5 | "devDependencies": { 6 | "@lehoczky/prettier-config": "^2.0.2", 7 | "nx": "22.0.0", 8 | "prettier": "^3.6.2", 9 | "prettier-plugin-astro": "^0.14.1", 10 | "prettier-plugin-tailwindcss": "^0.7.1", 11 | "typescript": "catalog:" 12 | }, 13 | "packageManager": "pnpm@10.19.0" 14 | } 15 | -------------------------------------------------------------------------------- /packages/demo/src/layouts/ExampleLayout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import "../styles/global.css" 3 | --- 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/demo/src/pages/examples/fluid-border.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import "../../styles/fluid-border.example.css" 3 | 4 | import ExampleLayout from "../../layouts/ExampleLayout.astro" 5 | --- 6 | 7 | 8 |
9 |
10 | 11 | 20 | -------------------------------------------------------------------------------- /packages/demo/src/styles/typography.example.css: -------------------------------------------------------------------------------- 1 | div { 2 | font-size: fluid(16px, 28px, 480px, 720px); 3 | line-height: fluid(26px, 38px, 480px, 720px); 4 | letter-spacing: fluid(0, 0.6px, 480px, 720px); 5 | } 6 | 7 | /* Compiled output: 8 | div { 9 | font-size: clamp(16px, -8px + 5vw, 28px); 10 | line-height: clamp(26px, 2px + 5vw, 38px); 11 | letter-spacing: clamp(0px, -1.2px + 0.25vw, 0.6px); 12 | } 13 | */ 14 | -------------------------------------------------------------------------------- /packages/demo/src/components/ResizableWindowDimensions.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { HTMLAttributes } from "astro/types" 3 | 4 | import { cn } from "../utils/cn" 5 | 6 | export interface Props extends HTMLAttributes<"div"> {} 7 | 8 | const { class: className, ...attrs } = Astro.props 9 | --- 10 | 11 |
16 |
17 | -------------------------------------------------------------------------------- /packages/demo/src/components/GitHubLink.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { HTMLAttributes } from "astro/types" 3 | 4 | import { cn } from "../utils/cn" 5 | 6 | export interface Props extends HTMLAttributes<"a"> {} 7 | 8 | const { class: className, ...attrs } = Astro.props 9 | --- 10 | 11 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "start": "postcss src/ --dir dist", 7 | "start:watch": "postcss src/ --dir dist --watch" 8 | }, 9 | "devDependencies": { 10 | "@lehoczky/postcss-fluid": "workspace:*", 11 | "@types/node": "catalog:", 12 | "postcss": "^8.5.6", 13 | "postcss-cli": "^11.0.1", 14 | "typescript": "catalog:" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/demo/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { configLehoczkyVue } from "@lehoczky/eslint-config-vue" 3 | import eslintPluginAstro from "eslint-plugin-astro" 4 | 5 | /** @type {import("eslint").Linter.Config[]} */ 6 | export default [ 7 | ...configLehoczkyVue({ 8 | parserOptionsForTypeChecking: { 9 | projectService: true, 10 | tsconfigRootDir: import.meta.dirname, 11 | }, 12 | }), 13 | ...eslintPluginAstro.configs["flat/recommended"], 14 | ] 15 | -------------------------------------------------------------------------------- /packages/demo/src/pages/examples/box-to-circle.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import "../../styles/box-to-circle.example.css" 3 | 4 | import ExampleLayout from "../../layouts/ExampleLayout.astro" 5 | --- 6 | 7 | 8 |
9 |
10 |
11 |
12 | 13 | 22 | -------------------------------------------------------------------------------- /packages/demo/src/pages/examples/positioned-box.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import "../../styles/positioned-box.example.css" 3 | 4 | import ExampleLayout from "../../layouts/ExampleLayout.astro" 5 | --- 6 | 7 | 8 |
9 |
10 |
11 |
12 | 13 | 23 | -------------------------------------------------------------------------------- /.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | import config from "@lehoczky/prettier-config" 2 | 3 | /** 4 | * @type {import("prettier").Options & import('prettier-plugin-tailwindcss').PluginOptions} 5 | */ 6 | export default { 7 | ...config, 8 | plugins: ["prettier-plugin-astro", "prettier-plugin-tailwindcss"], 9 | tailwindConfig: "./packages/demo/src/styles/global.css", 10 | tailwindFunctions: ["cn"], 11 | overrides: [ 12 | { 13 | files: "*.astro", 14 | options: { 15 | parser: "astro", 16 | }, 17 | }, 18 | ], 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": "explicit" 5 | }, 6 | "tailwindCSS.experimental.classRegex": [ 7 | ["cn\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"] 8 | ], 9 | "css.customData": [".vscode/tailwind.json"], 10 | "cSpell.words": [ 11 | "astro", 12 | "astrojs", 13 | "clsx", 14 | "octo", 15 | "octocat", 16 | "postcss", 17 | "Shiki", 18 | "shikiji", 19 | "vueuse" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /packages/demo/src/styles/global.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | @theme { 4 | --color-base-300: #cbcac8; 5 | --color-base-400: #3b404d; 6 | --color-base-500: #2e323b; 7 | --color-base-600: #282c34; 8 | --color-base-700: #21252b; 9 | --color-base-800: #1b1f23; 10 | --color-box: #064e3b; 11 | } 12 | 13 | @layer base { 14 | h1 { 15 | @apply text-4xl sm:text-5xl; 16 | } 17 | 18 | h2 { 19 | @apply mb-6 text-2xl sm:text-3xl; 20 | } 21 | } 22 | 23 | .astro-code { 24 | @apply p-4; 25 | } 26 | 27 | :root { 28 | color-scheme: dark; 29 | } 30 | -------------------------------------------------------------------------------- /packages/demo/src/pages/examples/padded-box.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import "../../styles/padded-box.example.css" 3 | 4 | import ExampleLayout from "../../layouts/ExampleLayout.astro" 5 | --- 6 | 7 | 8 |
9 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati dolore 10 | voluptatibus, ex nisi dolorum, veritatis quia sint voluptates distinctio 11 | odio quis, natus qui dicta corporis autem neque harum? Assumenda, 12 | voluptatum. 13 |
14 |
15 | 16 | 23 | -------------------------------------------------------------------------------- /packages/demo/src/pages/examples/typography.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import "../../styles/typography.example.css" 3 | 4 | import ExampleLayout from "../../layouts/ExampleLayout.astro" 5 | --- 6 | 7 | 8 |
9 | Lorem ipsum dolor sit, amet consectetur adipisicing elit. Quas vero eaque 10 | iste sapiente, asperiores non quaerat unde animi pariatur dignissimos sunt 11 | ex culpa eveniet temporibus odit et ducimus placeat soluta. Autem nihil 12 | molestiae esse quibusdam rem! Reiciendis quaerat nobis, dolor corporis 13 | repellat odit ipsam incidunt corrupti provident ducimus sunt ab. 14 |
15 |
16 | -------------------------------------------------------------------------------- /packages/demo/src/components/IconHamburger.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { HTMLAttributes } from "astro/types" 3 | 4 | import { cn } from "../utils/cn" 5 | 6 | export interface Props extends HTMLAttributes<"svg"> {} 7 | 8 | const { class: className, ...attrs } = Astro.props 9 | --- 10 | 11 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /packages/demo/src/components/IconWarning.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { HTMLAttributes } from "astro/types" 3 | 4 | import { cn } from "../utils/cn" 5 | 6 | export interface Props extends HTMLAttributes<"svg"> {} 7 | 8 | const { class: className, ...attrs } = Astro.props 9 | --- 10 | 11 | 17 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /packages/demo/src/pages/examples/fluid-grid-gap.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import "../../styles/fluid-grid-gap.example.css" 3 | 4 | import ExampleLayout from "../../layouts/ExampleLayout.astro" 5 | --- 6 | 7 | 8 |
9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 | 20 | 35 | -------------------------------------------------------------------------------- /packages/demo/src/components/IconLockClosed.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { HTMLAttributes } from "astro/types" 3 | 4 | import { cn } from "../utils/cn" 5 | 6 | export interface Props extends HTMLAttributes<"svg"> {} 7 | 8 | const { class: className, ...attrs } = Astro.props 9 | --- 10 | 11 | 20 | 25 | 26 | -------------------------------------------------------------------------------- /packages/demo/src/layouts/MainLayout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import "../styles/global.css" 3 | --- 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 19 | 20 | 21 | PostCSS Fluid Demo 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/postcss-fluid/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { configLehoczkyTypescript } from "@lehoczky/eslint-config-typescript" 3 | import { configLehoczkyVitest } from "@lehoczky/eslint-config-vitest" 4 | 5 | /** @type {import("eslint").Linter.Config[]} */ 6 | export default [ 7 | ...configLehoczkyTypescript({ 8 | parserOptionsForTypeChecking: { 9 | projectService: true, 10 | tsconfigRootDir: import.meta.dirname, 11 | }, 12 | }), 13 | ...configLehoczkyVitest(), 14 | { 15 | rules: { 16 | "vitest/expect-expect": [ 17 | "warn", 18 | { 19 | assertFunctionNames: [ 20 | "expect", 21 | "run", 22 | "runAndExpectToThrow", 23 | "runAndExpectToThrowWithOptions", 24 | ], 25 | }, 26 | ], 27 | }, 28 | }, 29 | ] 30 | -------------------------------------------------------------------------------- /packages/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "build": "astro check && astro build", 8 | "preview": "astro preview", 9 | "astro": "astro", 10 | "lint": "eslint --max-warnings=0", 11 | "lint:fix": "eslint --max-warnings=0 --fix" 12 | }, 13 | "dependencies": { 14 | "@astrojs/check": "^0.9.5", 15 | "@lehoczky/eslint-config-vue": "^2.0.11", 16 | "@lehoczky/postcss-fluid": "workspace:*", 17 | "@tailwindcss/vite": "^4.1.15", 18 | "@types/node": "catalog:", 19 | "astro": "^5.14.8", 20 | "clsx": "^2.1.1", 21 | "eslint": "^9.38.0", 22 | "eslint-plugin-astro": "^1.3.1", 23 | "perfect-debounce": "^2.0.0", 24 | "tailwind-merge": "^3.3.1", 25 | "tailwindcss": "^4.1.15", 26 | "typescript": "catalog:" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/demo/src/components/ScreenWidthWarning.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { HTMLAttributes } from "astro/types" 3 | 4 | import { cn } from "../utils/cn" 5 | import IconWarning from "./IconWarning.astro" 6 | 7 | export interface Props extends HTMLAttributes<"div"> {} 8 | 9 | const { class: className, ...attrs } = Astro.props 10 | --- 11 | 12 |
19 |
22 | 23 |
24 | This page is best viewed on devices that has a screen wider than 26 | 780px. On smaller devices 27 | you might not be able to resize the examples. 29 |
30 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 3 | "namedInputs": { 4 | "default": ["{projectRoot}/**/*", "sharedGlobals"], 5 | "production": [ 6 | "default", 7 | "!{projectRoot}/eslint.config.mjs", 8 | "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", 9 | "!{projectRoot}/tsconfig.spec.json" 10 | ], 11 | "sharedGlobals": [] 12 | }, 13 | "targetDefaults": { 14 | "build": { 15 | "dependsOn": ["^build"], 16 | "inputs": ["production", "^production"], 17 | "outputs": ["{projectRoot}/dist"], 18 | "cache": true 19 | }, 20 | "dev": { 21 | "dependsOn": ["^build"] 22 | }, 23 | "lint": { 24 | "cache": true 25 | }, 26 | "publint": { 27 | "cache": true, 28 | "dependsOn": ["build"] 29 | }, 30 | "preview": { 31 | "dependsOn": ["build"] 32 | }, 33 | "test": { 34 | "dependsOn": ["^test"], 35 | "cache": true 36 | } 37 | }, 38 | "defaultBase": "master" 39 | } 40 | -------------------------------------------------------------------------------- /packages/demo/src/components/IconGitHub.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { HTMLAttributes } from "astro/types" 3 | 4 | import { cn } from "../utils/cn" 5 | 6 | export interface Props extends HTMLAttributes<"svg"> {} 7 | 8 | const { class: className, ...attrs } = Astro.props 9 | --- 10 | 11 | 17 | 21 | 22 | -------------------------------------------------------------------------------- /packages/demo/src/components/DemoSection.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { HTMLAttributes } from "astro/types" 3 | import { Code } from "astro:components" 4 | 5 | import { cn } from "../utils/cn" 6 | import ResizableWindow from "./ResizableWindow.astro" 7 | 8 | export interface Props extends HTMLAttributes<"section"> { 9 | code: string 10 | } 11 | 12 | const { class: className, code, ...attrs } = Astro.props 13 | --- 14 | 15 |
19 |

20 | 21 |
22 | 23 | 24 | 25 |
26 | 27 |
28 |
29 |
30 |
31 |
32 |
33 | 34 |
35 |
36 | -------------------------------------------------------------------------------- /packages/postcss-fluid/lib/index.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from "postcss" 2 | import valueParser from "postcss-value-parser" 3 | 4 | import { 5 | applyFluidFunction, 6 | hasFluidFunction, 7 | isFluidFunction, 8 | } from "./function" 9 | import type { Options } from "./options" 10 | import { parseOptions } from "./options" 11 | 12 | const pluginCreator = (opts: Options = {}): Plugin => { 13 | const options = parseOptions(opts) 14 | 15 | return { 16 | postcssPlugin: "postcss-fluid", 17 | Declaration(decl) { 18 | const { value } = decl 19 | if (!hasFluidFunction(value)) { 20 | return 21 | } 22 | 23 | const parsedValue = valueParser(value) 24 | const newNodes = parsedValue.nodes.map((node) => { 25 | try { 26 | return isFluidFunction(node) 27 | ? applyFluidFunction(node, options) 28 | : node 29 | } catch (error: any) { 30 | throw decl.error(error.message) 31 | } 32 | }) 33 | 34 | decl.value = valueParser.stringify(newNodes) 35 | }, 36 | } 37 | } 38 | pluginCreator.postcss = true 39 | 40 | export = pluginCreator 41 | -------------------------------------------------------------------------------- /packages/postcss-fluid/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2022 Lehoczky Zoltán 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /packages/demo/src/components/ResizableWindowHeader.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { HTMLAttributes } from "astro/types" 3 | 4 | import { cn } from "../utils/cn" 5 | import IconHamburger from "./IconHamburger.astro" 6 | import IconLockClosed from "./IconLockClosed.astro" 7 | 8 | export interface Props extends HTMLAttributes<"div"> {} 9 | 10 | const { class: className, ...attrs } = Astro.props 11 | --- 12 | 13 |
20 |
21 |
22 |
23 |
24 |
25 | 26 |
29 | 30 | awesome-site.com 33 |
34 | 35 |
37 | -------------------------------------------------------------------------------- /packages/postcss-fluid/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Dimension } from "postcss-value-parser" 2 | 3 | export function isBoolean(value: unknown): value is boolean { 4 | return ( 5 | value === true || 6 | value === false || 7 | toString.call(value) === "[object Boolean]" 8 | ) 9 | } 10 | 11 | export function isNumber(value: unknown): value is number { 12 | return typeof value === "number" || toString.call(value) === "[object Number]" 13 | } 14 | 15 | export function round(number: number, precision: number) { 16 | let pair = `${number}e`.split("e") 17 | const value = Math.round(`${pair[0]}e${Number(pair[1]) + precision}` as any) 18 | 19 | pair = `${value}e`.split("e") 20 | return Number(`${pair[0]}e${Number(pair[1]) - precision}`) 21 | } 22 | 23 | export function toREMWithFixedPrecision(number: number, precision = 3) { 24 | return round(number / 16, precision) 25 | } 26 | 27 | export function toPX(number: number) { 28 | return number * 16 29 | } 30 | 31 | const ALLOWED_UNITS = ["px", "rem"] 32 | 33 | export function checkWhetherUnitIsAllowed({ unit }: Dimension): void { 34 | if (!ALLOWED_UNITS.includes(unit)) { 35 | throw new Error( 36 | `Unsupported unit: "${unit}". Please use "px" or "rem" instead`, 37 | ) 38 | } 39 | } 40 | 41 | export function isZeroWithoutUnit(dimension: Dimension) { 42 | return dimension.number === "0" && !dimension.unit 43 | } 44 | 45 | export function unitsNotMatch(dimension1: Dimension, dimension2: Dimension) { 46 | return dimension1.unit !== dimension2.unit 47 | } 48 | -------------------------------------------------------------------------------- /packages/postcss-fluid/lib/options.ts: -------------------------------------------------------------------------------- 1 | import valueParser, { type Dimension } from "postcss-value-parser" 2 | 3 | import { OptionViewportUnitNotMatchError } from "./errors" 4 | import { checkWhetherUnitIsAllowed, isNumber, unitsNotMatch } from "./utils" 5 | 6 | export type Options = { 7 | min?: number | string 8 | max?: number | string 9 | } 10 | 11 | export type ParsedOptions = { min: Dimension; max: Dimension } | undefined 12 | 13 | export function parseOptions(options: Options): ParsedOptions { 14 | if (!options.min && !options.max) { 15 | return undefined 16 | } 17 | if (!options.min) { 18 | throw new Error( 19 | "You have to define both min and max values for defaults to work, but min is missing.", 20 | ) 21 | } 22 | if (!options.max) { 23 | throw new Error( 24 | "You have to define both min and max values for defaults to work, but max is missing.", 25 | ) 26 | } 27 | 28 | const { min, max } = options as Required 29 | const parsedMin = isNumber(min) ? toPixelDimension(min) : parseDimension(min) 30 | const parsedMax = isNumber(max) ? toPixelDimension(max) : parseDimension(max) 31 | 32 | if (unitsNotMatch(parsedMin, parsedMax)) { 33 | throw new OptionViewportUnitNotMatchError(parsedMin, parsedMax) 34 | } 35 | 36 | return { 37 | min: parsedMin, 38 | max: parsedMax, 39 | } 40 | } 41 | 42 | function parseDimension(value: string) { 43 | const dimension = valueParser.unit(value) 44 | if (dimension === false) { 45 | throw new Error(`Cannot parse the given value as viewport width: ${value}`) 46 | } 47 | checkWhetherUnitIsAllowed(dimension) 48 | return dimension 49 | } 50 | 51 | function toPixelDimension(value: number): Dimension { 52 | return { 53 | number: String(value), 54 | unit: "px", 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/postcss-fluid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lehoczky/postcss-fluid", 3 | "version": "1.0.3", 4 | "type": "commonjs", 5 | "private": false, 6 | "description": "PostCSS plugin that makes it easier to work with fluid css values.", 7 | "scripts": { 8 | "clean-dist": "node -e \"fs.rmSync('dist', { force: true, recursive: true })\"", 9 | "build": "npm run clean-dist && tsc -p tsconfig.build.json", 10 | "test": "vitest run", 11 | "test:coverage": "vitest run --coverage", 12 | "lint": "eslint --max-warnings=0", 13 | "lint:fix": "eslint --max-warnings=0 --fix", 14 | "publint": "publint --strict" 15 | }, 16 | "main": "dist/index.js", 17 | "typings": "dist/index.d.ts", 18 | "files": [ 19 | "dist", 20 | "LICENSE", 21 | "README.md" 22 | ], 23 | "engines": { 24 | "node": ">=12.0.0" 25 | }, 26 | "peerDependencies": { 27 | "postcss": "^8.3.0" 28 | }, 29 | "dependencies": { 30 | "postcss-value-parser": "^4.2.0" 31 | }, 32 | "devDependencies": { 33 | "@lehoczky/eslint-config-typescript": "^2.0.11", 34 | "@lehoczky/eslint-config-vitest": "^1.0.11", 35 | "@types/node": "catalog:", 36 | "@vitest/coverage-v8": "^3.2.4", 37 | "eslint": "^9.38.0", 38 | "postcss": "^8.5.6", 39 | "publint": "^0.3.15", 40 | "typescript": "catalog:", 41 | "vitest": "^3.2.4" 42 | }, 43 | "keywords": [ 44 | "postcss", 45 | "css", 46 | "postcss-plugin", 47 | "postcss-fluid" 48 | ], 49 | "author": { 50 | "name": "Zoltan Lehoczky", 51 | "email": "ifjlehoczkyzoltan@gmail.com", 52 | "url": "https://github.com/Lehoczky" 53 | }, 54 | "license": "MIT", 55 | "repository": { 56 | "url": "git+https://github.com/Lehoczky/postcss-fluid.git", 57 | "type": "git" 58 | }, 59 | "publishConfig": { 60 | "access": "public" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/postcss-fluid/lib/errors.ts: -------------------------------------------------------------------------------- 1 | import type { Dimension } from "postcss-value-parser" 2 | 3 | import { toPX, toREMWithFixedPrecision } from "./utils" 4 | 5 | class BaseError extends Error { 6 | constructor(message: string) { 7 | super(message) 8 | this.name = this.constructor.name 9 | } 10 | } 11 | 12 | export class DimensionUnitsNotMatchError extends BaseError { 13 | constructor(type: string, dimension1: Dimension, dimension2: Dimension) { 14 | const value1 = dimensionToString(dimension1) 15 | const value2 = dimensionToString(dimension2) 16 | const advisedValue1 = getAdvisedValue(dimension1) 17 | const advisedValue2 = getAdvisedValue(dimension2) 18 | const showConversionAdvice = advisedValue1 && advisedValue2 19 | const advice = showConversionAdvice 20 | ? `\nTry changing ${value1} to ${advisedValue1} or ${value2} to ${advisedValue2}` 21 | : "" 22 | const prefix = type ? `${type} units` : "Units" 23 | 24 | const message = `${prefix} do not match for ${value1} and ${value2}${advice}.` 25 | super(message) 26 | } 27 | } 28 | 29 | export class OptionViewportUnitNotMatchError extends BaseError { 30 | constructor(dimension1: Dimension, dimension2: Dimension) { 31 | const unit1 = dimension1.unit 32 | const unit2 = dimension2.unit 33 | const message = `Viewport units do not match in the plugin options. One of them is ${unit1} and the other is ${unit2}` 34 | super(message) 35 | } 36 | } 37 | 38 | function dimensionToString({ number, unit }: Dimension) { 39 | return number + unit 40 | } 41 | 42 | function getAdvisedValue({ number, unit }: Dimension) { 43 | if (unit === "px") { 44 | return dimensionToString({ 45 | number: String(toREMWithFixedPrecision(Number(number))), 46 | unit: "rem", 47 | }) 48 | } 49 | if (unit === "rem") { 50 | return dimensionToString({ 51 | number: String(toPX(Number(number))), 52 | unit: "px", 53 | }) 54 | } 55 | return undefined 56 | } 57 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 13 | cancel-in-progress: true 14 | 15 | env: 16 | NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} 17 | 18 | jobs: 19 | ci: 20 | name: ci 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Check out Git repository ⏬ 24 | uses: actions/checkout@v5 25 | with: 26 | fetch-depth: 0 27 | 28 | - name: Install pnpm 29 | uses: pnpm/action-setup@v4 30 | 31 | - name: Set up Node.js 💿 32 | uses: actions/setup-node@v5 33 | with: 34 | node-version-file: ".node-version" 35 | registry-url: "https://registry.npmjs.org" 36 | cache: pnpm 37 | 38 | - name: Install dependencies 💤 39 | run: pnpm install 40 | 41 | - name: Derive appropriate SHAs for base and head for `nx affected` commands 42 | uses: nrwl/nx-set-shas@v4 43 | with: 44 | main-branch-name: master 45 | 46 | - name: "Get affected projects" 47 | id: affected-projects 48 | run: echo "affected='$(npx nx show projects --affected --sep=' ')'" >> "$GITHUB_OUTPUT" 49 | 50 | - name: Check formatting 🎨 51 | run: npx nx format:check 52 | 53 | - name: Run lint, test, build 👷‍♀️ 54 | run: npx nx affected -t lint test build 55 | 56 | - name: Release new version 🎉 57 | if: github.ref == 'refs/heads/master' 58 | run: pnpm -F @lehoczky/postcss-fluid publish 59 | 60 | - name: Deploy demo page 🚀 61 | if: github.ref == 'refs/heads/master' && contains(steps.affected-projects.outputs.affected, 'demo') 62 | uses: peaceiris/actions-gh-pages@v4 63 | with: 64 | github_token: ${{ secrets.GITHUB_TOKEN }} 65 | publish_dir: ./packages/demo/dist 66 | -------------------------------------------------------------------------------- /packages/demo/src/components/GitHubBadge.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { cn } from "../utils/cn" 3 | import GithubLink, { type Props as GithubLinkProps } from "./GitHubLink.astro" 4 | 5 | export interface Props extends GithubLinkProps {} 6 | 7 | const { class: className, ...attrs } = Astro.props 8 | --- 9 | 10 | 30 | 31 | 63 | -------------------------------------------------------------------------------- /packages/demo/src/components/ResizableWindow.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { HTMLAttributes } from "astro/types" 3 | 4 | import { cn } from "../utils/cn" 5 | import ResizableWindowDimensions from "./ResizableWindowDimensions.astro" 6 | import ResizableWindowHeader from "./ResizableWindowHeader.astro" 7 | 8 | export interface Props extends HTMLAttributes<"div"> {} 9 | 10 | const { class: className, ...attrs } = Astro.props 11 | --- 12 | 13 | 20 | 21 | 22 |
23 | 24 | 25 |
27 |
28 | 29 | 78 | -------------------------------------------------------------------------------- /packages/postcss-fluid/tests/errors.test.ts: -------------------------------------------------------------------------------- 1 | import type { Dimension } from "postcss-value-parser" 2 | import { describe, expect, it } from "vitest" 3 | 4 | import { 5 | DimensionUnitsNotMatchError, 6 | OptionViewportUnitNotMatchError, 7 | } from "../lib/errors" 8 | 9 | describe("dimensionUnitsNotMatchError", () => { 10 | it("works without type", () => { 11 | const pxDimension: Dimension = { number: "32", unit: "px" } 12 | const remDimension: Dimension = { number: "1", unit: "rem" } 13 | const error = new DimensionUnitsNotMatchError("", pxDimension, remDimension) 14 | 15 | expect(error.message).toMatch("Units do not match") 16 | }) 17 | 18 | it("works with type", () => { 19 | const pxDimension: Dimension = { number: "32", unit: "px" } 20 | const remDimension: Dimension = { number: "1", unit: "rem" } 21 | const error = new DimensionUnitsNotMatchError( 22 | "Value", 23 | pxDimension, 24 | remDimension, 25 | ) 26 | 27 | expect(error.message).toMatch("Value units do not match") 28 | }) 29 | 30 | it("can show advice for px and rem dimensions", () => { 31 | const pxDimension: Dimension = { number: "32", unit: "px" } 32 | const remDimension: Dimension = { number: "1", unit: "rem" } 33 | const error = new DimensionUnitsNotMatchError("", pxDimension, remDimension) 34 | 35 | expect(error.message).toBe( 36 | "Units do not match for 32px and 1rem\nTry changing 32px to 2rem or 1rem to 16px.", 37 | ) 38 | }) 39 | 40 | it("doesn't show advice if first dimension isn't px or rem", () => { 41 | const pxDimension: Dimension = { number: "32", unit: "vw" } 42 | const remDimension: Dimension = { number: "1", unit: "rem" } 43 | const error = new DimensionUnitsNotMatchError("", pxDimension, remDimension) 44 | 45 | expect(error.message).toBe("Units do not match for 32vw and 1rem.") 46 | }) 47 | 48 | it("doesn't show advice if second dimension isn't px or rem", () => { 49 | const pxDimension: Dimension = { number: "32", unit: "px" } 50 | const remDimension: Dimension = { number: "1", unit: "vw" } 51 | const error = new DimensionUnitsNotMatchError("", pxDimension, remDimension) 52 | 53 | expect(error.message).toBe("Units do not match for 32px and 1vw.") 54 | }) 55 | }) 56 | 57 | describe("optionViewportUnitNotMatchError", () => { 58 | it("shows the right units in it's message", () => { 59 | const pxDimension: Dimension = { number: "32", unit: "px" } 60 | const remDimension: Dimension = { number: "1", unit: "rem" } 61 | const error = new OptionViewportUnitNotMatchError(pxDimension, remDimension) 62 | 63 | expect(error.message).toBe( 64 | "Viewport units do not match in the plugin options. One of them is px and the other is rem", 65 | ) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /.vscode/tailwind.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1.1, 3 | "atDirectives": [ 4 | { 5 | "name": "@apply", 6 | "description": "Use the `@apply` directive to inline any existing utility classes into your own custom CSS. This is useful when you find a common utility pattern in your HTML that you’d like to extract to a new component.", 7 | "references": [ 8 | { 9 | "name": "Tailwind Documentation", 10 | "url": "https://tailwindcss.com/docs/functions-and-directives#apply-directive" 11 | } 12 | ] 13 | }, 14 | { 15 | "name": "@theme", 16 | "description": "Use the `@theme` directive to define your project's custom design tokens, like fonts, colors, and breakpoints:", 17 | "references": [ 18 | { 19 | "name": "Tailwind Documentation", 20 | "url": "https://tailwindcss.com/docs/functions-and-directives#theme-directive" 21 | } 22 | ] 23 | }, 24 | { 25 | "name": "@source", 26 | "description": "Use the `@source` directive to explicitly specify source files that aren't picked up by Tailwind's automatic content detection:", 27 | "references": [ 28 | { 29 | "name": "Tailwind Documentation", 30 | "url": "https://tailwindcss.com/docs/functions-and-directives#source-directive" 31 | } 32 | ] 33 | }, 34 | { 35 | "name": "@utility", 36 | "description": "Use the `@utility` directive to add custom utilities to your project that work with variants like `hover`, `focus` and `lg:`", 37 | "references": [ 38 | { 39 | "name": "Tailwind Documentation", 40 | "url": "https://tailwindcss.com/docs/functions-and-directives#utility-directive" 41 | } 42 | ] 43 | }, 44 | { 45 | "name": "@reference", 46 | "description": "If you want to use `@apply` or `@variant` in the `