From d9e87d6c8024c8b5fa1896ed6c2be8748f1d338e Mon Sep 17 00:00:00 2001 From: Claudio Ortolina Date: Fri, 28 Feb 2025 20:06:39 +0000 Subject: [PATCH] Migrate to Tailwind v4 --- assets/css/app.css | 39 +++++++++- assets/css/heroicons.tailwind.plugin.js | 47 ++++++++++++ assets/tailwind.config.js | 98 ------------------------- config/config.exs | 9 +-- mix.exs | 2 +- mix.lock | 2 +- 6 files changed, 88 insertions(+), 109 deletions(-) create mode 100644 assets/css/heroicons.tailwind.plugin.js delete mode 100644 assets/tailwind.config.js diff --git a/assets/css/app.css b/assets/css/app.css index 378c8f90..38922297 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -1,5 +1,36 @@ -@import "tailwindcss/base"; -@import "tailwindcss/components"; -@import "tailwindcss/utilities"; +@variant phx-click-loading (.phx-click-loading&, .phx-click-loading &); +@variant phx-submit-loading (.phx-submit-loading&, .phx-submit-loading &); +@variant phx-change-loading (.phx-change-loading&, .phx-change-loading &); -/* This file is for your main application CSS */ +@plugin "./heroicons.tailwind.plugin.js"; +@plugin "@tailwindcss/forms"; + +@import "tailwindcss"; + +@theme { + --color-brand: #d4af37; + --font-sans: + "Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --animate-shake: shake 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; + + @keyframes shake { + 10%, + 90% { + transform: translate3d(-1px, 0, 0); + } + 20%, + 80% { + transform: translate3d(2px, 0, 0); + } + 30%, + 50%, + 70% { + transform: translate3d(-4px, 0, 0); + } + 40%, + 60% { + transform: translate3d(4px, 0, 0); + } + } +} diff --git a/assets/css/heroicons.tailwind.plugin.js b/assets/css/heroicons.tailwind.plugin.js new file mode 100644 index 00000000..95b2cbed --- /dev/null +++ b/assets/css/heroicons.tailwind.plugin.js @@ -0,0 +1,47 @@ +const fs = require("fs"); +const path = require("path"); + +module.exports = function ({ matchComponents, theme }) { + let iconsDir = path.join(process.cwd(), "deps/heroicons/optimized"); + let values = {}; + let icons = [ + ["", "/24/outline"], + ["-solid", "/24/solid"], + ["-mini", "/20/solid"], + ["-micro", "/16/solid"], + ]; + icons.forEach(([suffix, dir]) => { + fs.readdirSync(path.join(iconsDir, dir)).forEach((file) => { + let name = path.basename(file, ".svg") + suffix; + values[name] = { name, fullPath: path.join(iconsDir, dir, file) }; + }); + }); + matchComponents( + { + hero: ({ name, fullPath }) => { + let content = fs + .readFileSync(fullPath) + .toString() + .replace(/\r?\n|\r/g, ""); + let size = theme("spacing.6"); + if (name.endsWith("-mini")) { + size = theme("spacing.5"); + } else if (name.endsWith("-micro")) { + size = theme("spacing.4"); + } + return { + [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`, + "-webkit-mask": `var(--hero-${name})`, + mask: `var(--hero-${name})`, + "mask-repeat": "no-repeat", + "background-color": "currentColor", + "vertical-align": "middle", + display: "inline-block", + width: size, + height: size, + }; + }, + }, + { values }, + ); +}; diff --git a/assets/tailwind.config.js b/assets/tailwind.config.js deleted file mode 100644 index 880645a3..00000000 --- a/assets/tailwind.config.js +++ /dev/null @@ -1,98 +0,0 @@ -// See the Tailwind configuration guide for advanced usage -// https://tailwindcss.com/docs/configuration - -const plugin = require("tailwindcss/plugin") -const defaultTheme = require('tailwindcss/defaultTheme') -const fs = require("fs") -const path = require("path") - -module.exports = { - content: [ - "./js/**/*.js", - "../lib/music_library_web.ex", - "../lib/music_library_web/**/*.*ex" - ], - theme: { - extend: { - colors: { - brand: "#D4AF37", - }, - fontFamily: { - sans: ['Inter', ...defaultTheme.fontFamily.sans], - }, - animation: { - 'shake': 'shake 0.82s cubic-bezier(.36,.07,.19,.97) both', - }, - keyframes: { - 'shake': { - '10%, 90%': { - transform: 'translate3d(-1px, 0, 0)' - }, - '20%, 80%': { - transform: 'translate3d(2px, 0, 0)' - }, - '30%, 50%, 70%': { - transform: 'translate3d(-4px, 0, 0)' - }, - '40%, 60%': { - transform: 'translate3d(4px, 0, 0)' - } - } - } - }, - }, - plugins: [ - require("@tailwindcss/forms"), - // Allows prefixing tailwind classes with LiveView classes to add rules - // only when LiveView classes are applied, for example: - // - //
- // - plugin(({ addVariant }) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])), - plugin(({ addVariant }) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])), - plugin(({ addVariant }) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])), - - // Embeds Heroicons (https://heroicons.com) into your app.css bundle - // See your `CoreComponents.icon/1` for more information. - // - plugin(function ({ matchComponents, theme }) { - let iconsDir = path.join(__dirname, "../deps/heroicons/optimized") - let values = {} - let icons = [ - ["", "/24/outline"], - ["-solid", "/24/solid"], - ["-mini", "/20/solid"], - ["-micro", "/16/solid"] - ] - icons.forEach(([suffix, dir]) => { - fs.readdirSync(path.join(iconsDir, dir)).forEach(file => { - let name = path.basename(file, ".svg") + suffix - values[name] = { name, fullPath: path.join(iconsDir, dir, file) } - }) - }) - matchComponents({ - "hero": ({ name, fullPath }) => { - let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "") - content = encodeURIComponent(content) - let size = theme("spacing.6") - if (name.endsWith("-mini")) { - size = theme("spacing.5") - } else if (name.endsWith("-micro")) { - size = theme("spacing.4") - } - return { - [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`, - "-webkit-mask": `var(--hero-${name})`, - "mask": `var(--hero-${name})`, - "mask-repeat": "no-repeat", - "background-color": "currentColor", - "vertical-align": "middle", - "display": "inline-block", - "width": size, - "height": size - } - } - }, { values }) - }) - ] -} diff --git a/config/config.exs b/config/config.exs index 1e439c62..56c6782f 100644 --- a/config/config.exs +++ b/config/config.exs @@ -47,14 +47,13 @@ config :esbuild, # Configure tailwind (the version is required) config :tailwind, - version: "3.4.17", + version: "4.0.9", music_library: [ args: ~w( - --config=tailwind.config.js - --input=css/app.css - --output=../priv/static/assets/app.css + --input=assets/css/app.css + --output=priv/static/assets/app.css ), - cd: Path.expand("../assets", __DIR__) + cd: Path.expand("..", __DIR__) ] # Configures Elixir's Logger diff --git a/mix.exs b/mix.exs index 5de5bf50..b4c3f4f3 100644 --- a/mix.exs +++ b/mix.exs @@ -65,7 +65,7 @@ defmodule MusicLibrary.MixProject do # Dev tooling {:phoenix_live_reload, "~> 1.2", only: :dev}, {:esbuild, "~> 0.8", runtime: Mix.env() == :dev}, - {:tailwind, "~> 0.2", runtime: Mix.env() == :dev}, + {:tailwind, "~> 0.3", runtime: Mix.env() == :dev}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, # HTTP Clients diff --git a/mix.lock b/mix.lock index b26cb3ec..490104a2 100644 --- a/mix.lock +++ b/mix.lock @@ -44,7 +44,7 @@ "recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"}, "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"}, "table_rex": {:hex, :table_rex, "4.1.0", "fbaa8b1ce154c9772012bf445bfb86b587430fb96f3b12022d3f35ee4a68c918", [:mix], [], "hexpm", "95932701df195d43bc2d1c6531178fc8338aa8f38c80f098504d529c43bc2601"}, - "tailwind": {:hex, :tailwind, "0.2.4", "5706ec47182d4e7045901302bf3a333e80f3d1af65c442ba9a9eed152fb26c2e", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "c6e4a82b8727bab593700c998a4d98cf3d8025678bfde059aed71d0000c3e463"}, + "tailwind": {:hex, :tailwind, "0.3.1", "a89d2835c580748c7a975ad7dd3f2ea5e63216dc16d44f9df492fbd12c094bed", [:mix], [], "hexpm", "98a45febdf4a87bc26682e1171acdedd6317d0919953c353fcd1b4f9f4b676a2"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, "telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"},