├── .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 |
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 |
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 |
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 |
25 |
26 |
29 |
30 | awesome-site.com
33 |
34 |
35 |
36 |
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 |
11 |
18 |
19 |
24 |
28 |
29 |
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 |
26 |
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 `