-
-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replace Tails by turboprop #84
Merged
+1,960
−251
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -74,17 +74,7 @@ npm i -D tailwindcss-animate | |
yarn add -D tailwindcss-animate | ||
``` | ||
|
||
3. Configure `tails` | ||
SaladUI use `tails` to properly merge Tailwindcss classes | ||
|
||
```elixir | ||
# config/config.exs | ||
|
||
config :tails, colors_file: Path.join(File.cwd!(), "assets/tailwind.colors.json") | ||
``` | ||
|
||
|
||
4. **Add javascript to handle event from server** | ||
3. **Add javascript to handle event from server** | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (documentation): Step numbering needs to be updated after removal of tails section The steps go from 2 to 4, skipping 3. Please update the numbering to be sequential. |
||
This add ability to execute client action from server. It's similar to `JS.exec/2`. Thanks to [this post](https://fly.io/phoenix-files/server-triggered-js/) from fly.io. | ||
|
||
Add this code snippet to the end of `app.js` | ||
|
@@ -106,7 +96,7 @@ Then from server side, you can close an opening sheet like this. | |
end | ||
``` | ||
|
||
5. Some tweaks | ||
4. Some tweaks | ||
Thanks to @ahacking | ||
|
||
- To make dark and light mode work correctly, add following to your `app.css` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
defmodule SaladUI.Cache do | ||
@moduledoc false | ||
use GenServer | ||
|
||
alias SaladUI.Merge.ClassTree | ||
|
||
@default_table_name :turboprop_cache | ||
|
||
@doc false | ||
def default_table_name, do: @default_table_name | ||
|
||
def start_link(default) when is_list(default) do | ||
GenServer.start_link(__MODULE__, default) | ||
end | ||
|
||
@impl true | ||
def init(opts \\ []) do | ||
table_name = Keyword.get(opts, :cache_table_name, @default_table_name) | ||
create_table(table_name) | ||
|
||
insert(:class_tree, ClassTree.generate()) | ||
|
||
{:ok, []} | ||
end | ||
|
||
def create_table(table_name \\ @default_table_name) do | ||
:ets.new(table_name, [:set, :public, :named_table, read_concurrency: true]) | ||
end | ||
|
||
def insert(key, value, table_name \\ @default_table_name) do | ||
:ets.insert(table_name, {key, value}) | ||
end | ||
|
||
def retrieve(key, table_name \\ @default_table_name) do | ||
case :ets.lookup(table_name, key) do | ||
[{^key, value}] -> value | ||
[] -> nil | ||
end | ||
end | ||
|
||
def purge(table_name \\ @default_table_name) do | ||
:ets.delete_all_objects(table_name) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
defmodule SaladUI.Merge do | ||
@moduledoc """ | ||
SaladUI Merge adds efficient class joining and merging of TailwindCSS classes to Elixir. | ||
|
||
TailwindCSS class names are composable and allow specifying an infinite amount of different styles. Most components allow overriding class | ||
names, like as passing `class` attribute that then gets merged with the existing styles. This can result in class lists such as | ||
`text-white bg-red-500 bg-blue-300` where `text-white bg-red-500` is the preset style, and `bg-blue-300` is the override for that one | ||
specific button that needs to look slightly different. | ||
Styles based on class are applied according to the order _they are defined at in the stylesheet_. In this example, because TailwindCSS | ||
orders color definitions alphabetically, the override does not work. `blue` is defined before `red`, so the `bg-red-500` class takes | ||
precedence since it was defined later. | ||
|
||
In order to still allow overriding styles, SaladUI Merge traverses the entire class list, creates a list of all classes and which | ||
conflicting groups of styles exist in them and gives precedence to the ones that were defined last _in the class list_, which, unlike the | ||
stylesheet, is in control of the user. | ||
|
||
|
||
## Example | ||
|
||
```elixir | ||
iex> merge("text-white bg-red-500 bg-blue-300") | ||
"text-white bg-blue-300" | ||
|
||
iex> merge(["px-2 py-1 bg-red hover:bg-dark-red", "p-3 bg-[#B91C1C]"]) | ||
"hover:bg-dark-red p-3 bg-[#B91C1C]" | ||
``` | ||
|
||
## Configuration | ||
|
||
SaladUI Merge does not currently support full theme configuration - that's on the roadmap! | ||
|
||
The limited configuration at the moment is adding Tailwind's `prefix` option. | ||
|
||
```elixir | ||
config :turboprop, | ||
prefix: "tw-" | ||
``` | ||
""" | ||
|
||
alias SaladUI.Cache | ||
alias SaladUI.Merge.Class | ||
alias SaladUI.Merge.Config | ||
|
||
@doc """ | ||
Joins and merges a list of classes. | ||
|
||
Passes the input to `join/1` before merging. | ||
""" | ||
@spec merge(list(), term()) :: binary() | ||
def merge(input, config \\ Config.config()) do | ||
input | ||
|> join() | ||
|> retrieve_from_cache_or_merge(config) | ||
end | ||
|
||
@doc """ | ||
Joins a list of classes. | ||
""" | ||
@spec merge(binary() | list()) :: binary() | ||
def join(input) when is_binary(input), do: input | ||
def join(input) when is_list(input), do: do_join(input, "") | ||
def join(_), do: "" | ||
|
||
defp do_join("", result), do: result | ||
defp do_join(nil, result), do: result | ||
defp do_join([], result), do: result | ||
|
||
defp do_join(string, result) when is_binary(string), do: do_join([string], result) | ||
|
||
defp do_join([head | tail], result) do | ||
case to_value(head) do | ||
"" -> do_join(tail, result) | ||
value when result == "" -> do_join(tail, value) | ||
value -> do_join(tail, result <> " " <> value) | ||
end | ||
end | ||
|
||
defp to_value(value) when is_binary(value), do: value | ||
|
||
defp to_value(values) when is_list(values) do | ||
Enum.reduce(values, "", fn v, acc -> | ||
case to_value(v) do | ||
"" -> acc | ||
resolved_value when acc == "" -> resolved_value | ||
resolved_value -> acc <> " " <> resolved_value | ||
end | ||
end) | ||
end | ||
|
||
defp to_value(_), do: "" | ||
|
||
defp retrieve_from_cache_or_merge(classes, config) do | ||
case Cache.retrieve("merge:#{classes}") do | ||
nil -> | ||
merged_classes = do_merge(classes, config) | ||
Cache.insert("merge:#{classes}", merged_classes) | ||
merged_classes | ||
|
||
merged_classes -> | ||
merged_classes | ||
end | ||
end | ||
|
||
defp do_merge(classes, config) do | ||
classes | ||
|> String.trim() | ||
|> String.split(~r/\s+/) | ||
|> Enum.map(&Class.parse/1) | ||
|> Enum.reverse() | ||
|> Enum.reduce(%{classes: [], groups: []}, fn class, acc -> | ||
handle_class(class, acc, config) | ||
end) | ||
|> Map.get(:classes) | ||
|> Enum.join(" ") | ||
end | ||
|
||
defp handle_class(%{raw: raw, tailwind?: false}, acc, _config), do: Map.update!(acc, :classes, fn classes -> [raw | classes] end) | ||
|
||
defp handle_class(%{conflict_id: conflict_id} = class, acc, config) do | ||
if Enum.member?(acc.groups, conflict_id), do: acc, else: add_class(acc, class, config) | ||
end | ||
|
||
defp add_class(acc, %{raw: raw, group: group, conflict_id: conflict_id, modifier_id: modifier_id}, config) do | ||
conflicting_groups = | ||
group | ||
|> conflicting_groups(config) | ||
|> Enum.map(&"#{modifier_id}:#{&1}") | ||
|> then(&[conflict_id | &1]) | ||
|
||
acc | ||
|> Map.update!(:classes, fn classes -> [raw | classes] end) | ||
|> Map.update!(:groups, fn groups -> groups ++ conflicting_groups end) | ||
end | ||
|
||
defp conflicting_groups(group, config) do | ||
conflicts = Map.get(config.conflicting_groups, group, []) | ||
modifier_conflicts = Map.get(config.conflicting_group_modifiers, group, []) | ||
|
||
conflicts ++ modifier_conflicts | ||
end | ||
end |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (documentation): Installation methods could be more clearly distinguished
Consider adding a brief comparison of when to use each installation method at the start of the installation section to help users choose the appropriate approach.