# Upgrade Guide
## v3.x to v4.0.0
Petal Components v4 **removes Alpine.js**. Every component is now Phoenix.LiveView.JS only, and the library ships its own JS hooks. Markdown rendering (used by the new Chat components) is an optional dependency.
### 1. Register the bundled JS hooks
Petal Components now ships JS (for password toggles, copyable/clearable inputs, the accordion, and the Chat components). Import and register it in your `assets/js/app.js`:
```diff
+ import PetalComponents from "../../deps/petal_components/assets/js/petal_components"
const liveSocket = new LiveSocket("/live", Socket, {
params: { _csrf_token: csrfToken },
- hooks: MyHooks,
+ hooks: { ...MyHooks, ...PetalComponents },
})
```
If you don't register it, the password-visibility toggle, copyable/clearable inputs, the accordion, and the Chat family won't be interactive. The other overlay components (dropdown, menu, modal, slide over) use Phoenix.LiveView.JS commands and need nothing extra.
### 2. Remove Alpine.js (if you only had it for Petal Components)
Petal Components no longer needs Alpine. If nothing else in your app uses it, drop `alpinejs` from `assets/package.json` and remove the Alpine setup from `app.js` (the `import Alpine`, `window.Alpine = Alpine`, `Alpine.start()`, and the `dom: { onBeforeElUpdated }` Alpine-clone block in your LiveSocket).
### 3. Remove the `js_lib` attribute
`js_lib` has been removed from `<.dropdown>`, `<.accordion>`, and the vertical menu components. Delete it from your call sites:
```diff
- <.dropdown label="Menu" js_lib="live_view_js">
+ <.dropdown label="Menu">
```
`PetalComponents.default_js_lib/0` is deprecated; it always returns `"live_view_js"`.
### 4. Add MDEx if you use Chat markdown
The new `<.markdown>` / `Chat.to_html/1` need the optional `:mdex` dependency:
```diff
{:petal_components, "~> 4.0"},
+ {:mdex, "~> 0.12"},
```
Calling `markdown/1` without it raises a clear error. The rest of the library has no new required dependencies.
## v2.8.4 to v3.0.0
Petal Components has been upgraded to Tailwind 4. Some utilities have been removed or renamed. See the [Tailwind upgrade guide](https://tailwindcss.com/docs/upgrade-guide) for more information.
To upgrade to Tailwind 4 in your project, make sure you update `mix.exs`:
```diff
- {:tailwind, "~> 0.2", runtime: Mix.env() == :dev}
+ {:tailwind, "~> 0.3", runtime: Mix.env() == :dev}
```
And `config.exs` (note that the paths have changed and that you need 4.0.9 or above):
```diff
config :tailwind,
- version: "3.3.3",
+ version: "4.0.9",
default: [
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__)
]
```
Don't forget to run:
```bash
mix tailwind.install
```
`tailwind.config.js` is now considered legacy and is no longer automatically loaded. However, it is still supported and you can manually load it by adding the following to your `app.css`:
```css
@config "../tailwind.config.js";
```
Now you should be able to follow the [Tailwind upgrade guide](https://tailwindcss.com/docs/upgrade-guide) to update your project.
### Petal Components CSS configuration
Here are some tips on how to integrate Petal Components if you are no longer using the `tailwind.config.js` file.
To reference Petal Components CSS and include it as a source for Tailwind utility classes, use the `@source` and `@import` directives:
```css
@import "tailwindcss";
@source "../../deps/petal_components/**/*.*ex";
@import "../../deps/petal_components/assets/default.css";
```
The CSS file equivalent of `darkMode: 'class'` is:
```css
@custom-variant dark (&:where(.dark, .dark *));
```
In Tailwind 4 buttons now use `cursor: default` instead of `cursor: pointer`. If you would like the old behaviour:
```css
@layer base {
/* Use the pointer for buttons */
button:not(:disabled),
[role="button"]:not(:disabled) {
cursor: pointer;
}
}
```
To configure Petal Component colours add an `@import`:
```css
@import "./colors.css";
```
Then create the `colors.css` file:
```css
@theme inline {
--color-primary-50: var(--color-blue-50);
--color-primary-100: var(--color-blue-100);
--color-primary-200: var(--color-blue-200);
--color-primary-300: var(--color-blue-300);
--color-primary-400: var(--color-blue-400);
--color-primary-500: var(--color-blue-500);
--color-primary-600: var(--color-blue-600);
--color-primary-700: var(--color-blue-700);
--color-primary-800: var(--color-blue-800);
--color-primary-900: var(--color-blue-900);
--color-primary-950: var(--color-blue-950);
--color-secondary-50: var(--color-pink-50);
--color-secondary-100: var(--color-pink-100);
--color-secondary-200: var(--color-pink-200);
--color-secondary-300: var(--color-pink-300);
--color-secondary-400: var(--color-pink-400);
--color-secondary-500: var(--color-pink-500);
--color-secondary-600: var(--color-pink-600);
--color-secondary-700: var(--color-pink-700);
--color-secondary-800: var(--color-pink-800);
--color-secondary-900: var(--color-pink-900);
--color-secondary-950: var(--color-pink-950);
--color-success-50: var(--color-green-50);
--color-success-100: var(--color-green-100);
--color-success-200: var(--color-green-200);
--color-success-300: var(--color-green-300);
--color-success-400: var(--color-green-400);
--color-success-500: var(--color-green-500);
--color-success-600: var(--color-green-600);
--color-success-700: var(--color-green-700);
--color-success-800: var(--color-green-800);
--color-success-900: var(--color-green-900);
--color-success-950: var(--color-green-950);
--color-danger-50: var(--color-red-50);
--color-danger-100: var(--color-red-100);
--color-danger-200: var(--color-red-200);
--color-danger-300: var(--color-red-300);
--color-danger-400: var(--color-red-400);
--color-danger-500: var(--color-red-500);
--color-danger-600: var(--color-red-600);
--color-danger-700: var(--color-red-700);
--color-danger-800: var(--color-red-800);
--color-danger-900: var(--color-red-900);
--color-danger-950: var(--color-red-950);
--color-warning-50: var(--color-yellow-50);
--color-warning-100: var(--color-yellow-100);
--color-warning-200: var(--color-yellow-200);
--color-warning-300: var(--color-yellow-300);
--color-warning-400: var(--color-yellow-400);
--color-warning-500: var(--color-yellow-500);
--color-warning-600: var(--color-yellow-600);
--color-warning-700: var(--color-yellow-700);
--color-warning-800: var(--color-yellow-800);
--color-warning-900: var(--color-yellow-900);
--color-warning-950: var(--color-yellow-950);
--color-info-50: var(--color-sky-50);
--color-info-100: var(--color-sky-100);
--color-info-200: var(--color-sky-200);
--color-info-300: var(--color-sky-300);
--color-info-400: var(--color-sky-400);
--color-info-500: var(--color-sky-500);
--color-info-600: var(--color-sky-600);
--color-info-700: var(--color-sky-700);
--color-info-800: var(--color-sky-800);
--color-info-900: var(--color-sky-900);
--color-info-950: var(--color-sky-950);
--color-gray-50: var(--color-slate-50);
--color-gray-100: var(--color-slate-100);
--color-gray-200: var(--color-slate-200);
--color-gray-300: var(--color-slate-300);
--color-gray-400: var(--color-slate-400);
--color-gray-500: var(--color-slate-500);
--color-gray-600: var(--color-slate-600);
--color-gray-700: var(--color-slate-700);
--color-gray-800: var(--color-slate-800);
--color-gray-900: var(--color-slate-900);
--color-gray-950: var(--color-slate-950);
}
```
To add the "typography" and "form" plug-ins:
```css
@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms";
```
To re-create the heroicons JavaScript:
```css
@plugin "./tailwind_heroicons.js";
```
Then create the `tailwind_heroicons.js` file:
```JavaScript
const plugin = require("tailwindcss/plugin");
const fs = require("fs");
const path = require("path");
// Embeds Heroicons (https://heroicons.com) into your app.css bundle
// See your `CoreComponents.icon/1` for more information.
module.exports = 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, "");
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: "1lh",
};
},
},
{ values },
);
});
```
## v1.4 to v1.5
v1.5 requires Tailwind v3.3.3. Update the version in `config.exs`:
```elixir
config :tailwind,
version: "3.3.3",
```
Then run `mix tailwind.install`.
## v0.19 to v1.0.0
In `tailwind.config.js` you need to add more colors:
```js
theme: {
extend: {
colors: {
primary: colors.blue,
secondary: colors.pink,
// ADD THESE COLORS (can pick different ones from here: https://tailwindcss.com/docs/customizing-colors)
success: colors.green,
danger: colors.red,
warning: colors.yellow,
info: colors.sky,
// Options: slate, gray, zinc, neutral, stone
gray: colors.gray,
},
},
},
```
NOTE: If you are supplying your own colours, they will require keys for different shades.
In your `app.css` you need to import the default Petal Components CSS:
```css
@import "tailwindcss/base";
@import "../../deps/petal_components/assets/default.css";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
```
Update tailwind and esbuild dependencies:
```
mix deps.update esbuild
mix deps.update tailwind
```
In your `config.exs`, update tailwind and esbuild to more recent versions:
```elixir
config :esbuild,
version: "0.15.5",
default: [
...
config :tailwind,
version: "3.3.3",
default: [
...
```
Update tailwind and esbuild binaries:
```
mix esbuild.install
mix tailwind.install
```
## v0.18 to v0.19
There should be no breaking changes. This provides declarative assigns for all components so you should get warnings if you don't pass the right attributes.
### Using with Phoenix 1.7
Petal Components work fine with Phoenix 1.7 - there just will be some naming conflicts as the Phoenix generator now creates a file called `core_components.ex`, which has some function components defined in there.
To fix, go to the generated `core_components.ex` file and rename or remove the following functions: `modal`, `button` and `table`.
Unfortunately, this means the generators like `mix phx.gen.live` won't work properly. If you want generators for Petal Components, look into buying [Petal Pro](https://petal.build).
For a full upgrade commit of Phoenix 1.6 to 1.7 you can see [here](https://github.com/petalframework/petal_boilerplate/commit/dfd122902b2f1730f122efafc3d6962c2a6ce91d) how we did it with Petal Boilerplate.
If you want to pick and choose which components to use, you could namespace Petal Components.
```
defmodule PC do
use PetalComponents
end
```
Then there would be no naming conflicts. But you would have to use the module for every component: `<PC.button></PC.button>` etc.
## v0.17 to v0.18
This guide assumes you are also updating `phoenix_live_view` to `0.18.0`.
### Update mix.exs
```elixir
{:phoenix_live_view, "~> 0.18"},
{:petal_components, "~> 0.18"},
```
### Live View 0.18 updates
#### Phoenix.Component
Global replace: `Phoenix.LiveView.Helpers` --> `import Phoenix.Component`.
In some places you'll need to `import Phoenix.Component`. For example with `assign_new/3` calls.
#### Live title tag
`live_title_tag` is to be replaced with a component:
```html
<.live_title>
<%= assigns[:page_title] || "Welcome" %>
</.live_title>
```
#### Renaming <.link>
Live View 0.18 deprecations:
- `live_redirect` - deprecate in favor of new `<.link navigate={..}>` component of `Phoenix.Component`
- `live_patch` - deprecate in favor of new `<.link patch={..}>` component of `Phoenix.Component`
- `push_redirect` - deprecate in favor of new `push_navigate` function on `Phoenix.LiveView`
Petal Components has a `<.link>` component, but now Live View 0.18 has its own `<.link>`:
```
<.link href="https://myapp.com">my app</.link>
<.link navigate={@path}>remount</.link>
<.link patch={@path}>patch</.link>
```
The attributes are a bit different to our link. So we renamed `<.link>` to `<.a>` to make it an easier upgrade.
Perform these global replaces:
```
`<.link` --> `<.a`
`</.link` --> `</.a`
```
This way, your app will still work with our `<a>` tags. However, we will eventually deprecate this in favour of the new Live View `<.link>`.
### Renaming Heroicons
PetalComponents now internally uses https://github.com/mveytsman/heroicons_elixir, which uses Heroicons V2.
Unfortunately Heroicons V2 renames a lot of icons and is kind of like another library. So we have renamed `PetalComponents.Heroicons` --> `PetalComponents.HeroiconsV1` for anyone who would like to continue using V1.
#### To keep using V1:
Do the global replaces:
```
Replace `PetalComponents.Heroicons` --> `PetalComponents.HeroiconsV1`
Replace `Heroicons.Solid` --> `HeroiconsV1.Solid`
Replace `Heroicons.Outline` --> `HeroiconsV1.Outline`
```
#### To use V2:
Delete all references to `PetalComponents.Heroicons`.
For every case you have used a Heroicon in a HEEX template you will have to update to the new syntax defined here: https://github.com/mveytsman/heroicons_elixir
Eg.
```html
<!-- Old way -->
<Heroicons.Solid.home class="" />
<!-- New way -->
<Heroicons.home solid class="" />
```
Note the `solid` attribute. For `outline`, you don't need any attributes.
The most annoying part is that a lot of the icon names have changed. You can see a list of the all the name changes here: https://github.com/tailwindlabs/heroicons/releases/tag/v2.0.0
#### Icon Button
We can't use a dynamic `Heroicons.Solid.render/1` anymore so instead we need to pass in the icon as the default slot.
Old way:
```html
<.icon_button to="/" icon={:trash} />
```
New way:
```html
<.icon_button to="/">
<Heroicons.trash solid />
<./icon_button>
```
### Alpine JS updates
You can't use the bind shortcuts in the latest Live View.
Old:
```html
<div :class="..."></div>
```
New
```html
<div x-bind:class="..."></div>
```
You can do this global replace: ` :class=` --> ` x-bind:class=`.
You'll have to do it for each attribute using this bind syntax, eg: ` :aria-expanded=` --> ` x-bind:aria-expanded=`
There could be many more if you use Alpine a lot.