diff --git a/guides/mix_tasks.md b/guides/mix_tasks.md index 8abe3594ae..62900d7374 100644 --- a/guides/mix_tasks.md +++ b/guides/mix_tasks.md @@ -419,10 +419,10 @@ And then `assets/` which should look similar to this: ```console ├── css -│   └── app.css +│   ├── app.css +│   └── tailwind_heroicons.js ├── js │   └── app.js -├── tailwind.config.js └── vendor └── topbar.js ``` diff --git a/installer/lib/phx_new/single.ex b/installer/lib/phx_new/single.ex index 96d389117f..e4761b294d 100644 --- a/installer/lib/phx_new/single.ex +++ b/installer/lib/phx_new/single.ex @@ -70,7 +70,7 @@ defmodule Phx.New.Single do template(:css, [ {:eex, :web, "phx_assets/app.css": "assets/css/app.css", - "phx_assets/tailwind.config.js": "assets/tailwind.config.js"} + "phx_assets/tailwind_heroicons.js": "assets/css/tailwind_heroicons.js"} ]) template(:js, [ diff --git a/installer/templates/phx_assets/app.css b/installer/templates/phx_assets/app.css index 5b7776c97c..e9a997efb7 100644 --- a/installer/templates/phx_assets/app.css +++ b/installer/templates/phx_assets/app.css @@ -1,6 +1,15 @@ -@import "tailwindcss/base"; -@import "tailwindcss/components"; -@import "tailwindcss/utilities"; +/* See the Tailwind configuration guide for advanced usage + https://tailwindcss.com/docs/configuration */ + +@import "tailwindcss"; +@plugin "@tailwindcss/forms"; +@plugin "./tailwind_heroicons.js"; +@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 &"]); +@theme { + --color-brand: "#FD4F00", +}; /* * Make LiveView wrapper divs transparent for layout. diff --git a/installer/templates/phx_assets/tailwind.config.js b/installer/templates/phx_assets/tailwind.config.js deleted file mode 100644 index 5c65c09145..0000000000 --- a/installer/templates/phx_assets/tailwind.config.js +++ /dev/null @@ -1,75 +0,0 @@ -// See the Tailwind configuration guide for advanced usage -// https://tailwindcss.com/docs/configuration - -const plugin = require("tailwindcss/plugin") -const fs = require("fs") -const path = require("path") - -module.exports = { - content: [ - "./js/**/*.js", - "../lib/<%= @lib_web_name || @app_name %>.ex", - "../lib/<%= @lib_web_name || @app_name %>/**/*.*ex" - ], - theme: { - extend: { - colors: { - brand: "#FD4F00", - } - }, - }, - 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, "..<%= if @in_umbrella, do: "/../.." %>/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/installer/templates/phx_assets/tailwind_heroicons.js b/installer/templates/phx_assets/tailwind_heroicons.js new file mode 100644 index 0000000000..98b4e7e4fe --- /dev/null +++ b/installer/templates/phx_assets/tailwind_heroicons.js @@ -0,0 +1,43 @@ +const plugin = require("tailwindcss/plugin") +const fs = require("fs") +const path = require("path") + +module.exports = plugin(function({matchComponents, theme}) { + let iconsDir = path.join(__dirname, "../..<%= if @in_umbrella, do: "/../.." %>/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/installer/templates/phx_single/config/config.exs b/installer/templates/phx_single/config/config.exs index 97c1bbf53b..8422bbb6a9 100644 --- a/installer/templates/phx_single/config/config.exs +++ b/installer/templates/phx_single/config/config.exs @@ -45,14 +45,13 @@ config :esbuild, # Configure tailwind (the version is required) config :tailwind, - version: "3.4.3", + version: "4.0.3", <%= @app_name %>: [ 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("..<%= if @in_umbrella, do: "/apps/#{@app_name}" %>/assets", __DIR__), + cd: Path.expand("..<%= if @in_umbrella, do: "/apps/#{@app_name}" %>", __DIR__), ]<% end %> # Configures Elixir's Logger diff --git a/installer/templates/phx_single/mix.exs b/installer/templates/phx_single/mix.exs index 0e990e8d0c..2464c7be0b 100644 --- a/installer/templates/phx_single/mix.exs +++ b/installer/templates/phx_single/mix.exs @@ -47,7 +47,7 @@ defmodule <%= @app_module %>.MixProject do {:floki, ">= 0.30.0", only: :test},<% end %><%= if @dashboard do %> {:phoenix_live_dashboard, "~> 0.8.3"},<% end %><%= if @javascript do %> {:esbuild, "~> 0.8", runtime: Mix.env() == :dev},<% end %><%= if @css do %> - {:tailwind, "~> 0.2", runtime: Mix.env() == :dev}, + {:tailwind, "~> 0.3", runtime: Mix.env() == :dev}, {:heroicons, github: "tailwindlabs/heroicons", tag: "v2.1.1", diff --git a/installer/templates/phx_umbrella/apps/app_name_web/config/config.exs b/installer/templates/phx_umbrella/apps/app_name_web/config/config.exs index 2b2f6300d0..a12cc9bc29 100644 --- a/installer/templates/phx_umbrella/apps/app_name_web/config/config.exs +++ b/installer/templates/phx_umbrella/apps/app_name_web/config/config.exs @@ -29,12 +29,11 @@ config :esbuild, # Configure tailwind (the version is required) config :tailwind, - version: "3.4.3", + version: "4.0.3", <%= @web_app_name %>: [ 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("../apps/<%= @web_app_name %>/assets", __DIR__) + cd: Path.expand("../apps/<%= @web_app_name %>", __DIR__) ]<% end %> diff --git a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs index d2ce376fcd..cfeede37bf 100644 --- a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs +++ b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs @@ -45,7 +45,7 @@ defmodule <%= @web_namespace %>.MixProject do {:floki, ">= 0.30.0", only: :test},<% end %><%= if @dashboard do %> {:phoenix_live_dashboard, "~> 0.8.3"},<% end %><%= if @javascript do %> {:esbuild, "~> 0.8", runtime: Mix.env() == :dev},<% end %><%= if @css do %> - {:tailwind, "~> 0.2", runtime: Mix.env() == :dev}, + {:tailwind, "~> 0.3", runtime: Mix.env() == :dev}, {:heroicons, github: "tailwindlabs/heroicons", tag: "v2.1.1", diff --git a/installer/templates/phx_web/components/core_components.ex b/installer/templates/phx_web/components/core_components.ex index f5ba025411..2708586066 100644 --- a/installer/templates/phx_web/components/core_components.ex +++ b/installer/templates/phx_web/components/core_components.ex @@ -520,7 +520,7 @@ defmodule <%= @web_namespace %>.CoreComponents do width, height, and background color classes. Icons are extracted from the `deps/heroicons` directory and bundled within - your compiled app.css by the plugin in your `assets/tailwind.config.js`. + your compiled app.css by the plugin in `assets/css/tailwind_heroicons.js`. ## Examples diff --git a/installer/test/phx_new_test.exs b/installer/test/phx_new_test.exs index 9690c7b66b..0605944a45 100644 --- a/installer/test/phx_new_test.exs +++ b/installer/test/phx_new_test.exs @@ -154,11 +154,6 @@ defmodule Mix.Tasks.Phx.NewTest do # tailwind assert_file("phx_blog/assets/css/app.css") - assert_file("phx_blog/assets/tailwind.config.js", fn file -> - assert file =~ "phx_blog_web.ex" - assert file =~ "phx_blog_web/**/*.*ex" - end) - refute File.exists?("phx_blog/priv/static/assets/app.css") refute File.exists?("phx_blog/priv/static/assets/app.js") assert File.exists?("phx_blog/assets/vendor") diff --git a/installer/test/phx_new_umbrella_test.exs b/installer/test/phx_new_umbrella_test.exs index 31a95ed3a4..a9315308af 100644 --- a/installer/test/phx_new_umbrella_test.exs +++ b/installer/test/phx_new_umbrella_test.exs @@ -183,11 +183,6 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do assert_file(web_path(@app, ".gitignore"), ~r/\n$/) assert_file(web_path(@app, "assets/css/app.css")) - assert_file(web_path(@app, "assets/tailwind.config.js"), fn file -> - assert file =~ "phx_umb_web.ex" - assert file =~ "phx_umb_web/**/*.*ex" - end) - assert_file(web_path(@app, "priv/static/favicon.ico")) refute File.exists?(web_path(@app, "priv/static/assets/app.css")) diff --git a/installer/test/phx_new_web_test.exs b/installer/test/phx_new_web_test.exs index 9911ce22c0..2f13f4cda9 100644 --- a/installer/test/phx_new_web_test.exs +++ b/installer/test/phx_new_web_test.exs @@ -63,15 +63,4 @@ defmodule Mix.Tasks.Phx.New.WebTest do assert_received {:mix_shell, :info, ["Start your Phoenix app" <> _]} end end - - test "app_name is included in tailwind config" do - in_tmp_umbrella_project "new with defaults", fn -> - Mix.Tasks.Phx.New.Web.run(["testweb"]) - - assert_file "testweb/assets/tailwind.config.js", fn file -> - assert file =~ "testweb.ex" - assert file =~ "testweb/**/*.*ex" - end - end - end end diff --git a/integration_test/config/config.exs b/integration_test/config/config.exs index aa9faf5f65..382e357788 100644 --- a/integration_test/config/config.exs +++ b/integration_test/config/config.exs @@ -4,6 +4,6 @@ config :phoenix, :json_library, Jason config :swoosh, api_client: false -config :tailwind, :version, "3.4.3" +config :tailwind, :version, "4.0.3" -config :phoenix_live_view, enable_expensive_runtime_checks: true \ No newline at end of file +config :phoenix_live_view, enable_expensive_runtime_checks: true diff --git a/integration_test/mix.exs b/integration_test/mix.exs index a63f329ba0..4fc63558b0 100644 --- a/integration_test/mix.exs +++ b/integration_test/mix.exs @@ -55,7 +55,7 @@ defmodule Phoenix.Integration.MixProject do {:bcrypt_elixir, "~> 3.0"}, {:argon2_elixir, "~> 4.0"}, {:pbkdf2_elixir, "~> 2.0"}, - {:tailwind, "~> 0.2"}, + {:tailwind, "~> 0.3"}, {:heroicons, github: "tailwindlabs/heroicons", tag: "v2.1.1", diff --git a/priv/templates/phx.gen.live/core_components.ex b/priv/templates/phx.gen.live/core_components.ex index f5ba025411..2708586066 100644 --- a/priv/templates/phx.gen.live/core_components.ex +++ b/priv/templates/phx.gen.live/core_components.ex @@ -520,7 +520,7 @@ defmodule <%= @web_namespace %>.CoreComponents do width, height, and background color classes. Icons are extracted from the `deps/heroicons` directory and bundled within - your compiled app.css by the plugin in your `assets/tailwind.config.js`. + your compiled app.css by the plugin in `assets/css/tailwind_heroicons.js`. ## Examples