Improve candidate extraction when candidates contain . characters (#17113)
This PR fixes an issue where some classes weren't properly extracted due
to some incorrect assumptions in the pre processors.
Templating languages such as Haml, Slim and Pug allow you to write
classes in a shorter way that are not properly contained inside of
strings. E.g.:
```slim
p.flex.px-2
```
These candidates are not properly extracted because there are no
bounding characters like quotes. To solve this, we pre-process it and
replace `.` with ` ` characters. This results in something like:
```
p flex px-2
```
However, this has some challenges on its own. Candidates like `px-2.5`
cannot be written in this shorthand form, instead they need to be in
strings. Now we _cannot_ replace the `.` because otherwise we would
change `px-2.5` to `px-2 5` which is wrong.
The next problem is that we need to know when they are in a "string".
This has another set of problems because these templating languages
allow you to write normal text that will eventually be the contents of
the HTML tags.
```haml
.text-red-500.text-3xl
| This text can't should be red
^ Wait, is this the start of a string now???
```
In this example, if we consider the `'` the start of a string, when it's
clearly not, how would we know it's for _sure_ not a string?
This ended up as a bit of a rabbit hole, but we came up with another
approach entirely if we think about the original problem we want to
solve which is when do we change `.` to ` ` characters.
One of the rules in our current extractor is that a `.` has to be
between 2 numbers. Which works great in a scenario like: `px-2.5`.
However, if you look at Haml or Slim syntax, this is also allowed:
```slim
p.bg-red-500.2xl:flex
^^^ Uh oh...
```
In this scenario, a `.` is surrounded by numbers so we shouldn't replace
it with a space. But as you can see, we clearly do... so we need another
heuristic in this case.
Luckily, one of the rules in Tailwind CSS is that a utility cannot start
with a number, but a variant _can_. This means that if we see a scenario
like `<digit>.<digit>` then we can just check if the value after the `.`
is a valid variant or not.
In this case it is a valid variant so we _do_ want to replace the `.`
with a ` ` even though we do have the `<digit>.<digit>` format.
🥴
# Test plan
1. Added additional tests.
2. Existing tests still pass
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
This commit is contained in:
parent
22746e6a6b
commit
37062928f9
6 changed files with 557 additions and 93 deletions
|
|
@ -23,7 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Ensure classes between `>` and `<` are properly extracted ([#17094](https://github.com/tailwindlabs/tailwindcss/pull/17094))
|
||||
- Treat starting single quote as verbatim text in Slim ([#17085](https://github.com/tailwindlabs/tailwindcss/pull/17085))
|
||||
- Ensure `.node` and `.wasm` files are not scanned for utilities ([#17123](https://github.com/tailwindlabs/tailwindcss/pull/17123))
|
||||
- Improve performance when scanning `JSON` files ([#17125](https://github.com/tailwindlabs/tailwindcss/pull/17125))
|
||||
- Improve performance when scanning JSON files ([#17125](https://github.com/tailwindlabs/tailwindcss/pull/17125))
|
||||
- Fix extracting candidates containing dots in Haml, Pug, and Slim pre processors ([#17113](https://github.com/tailwindlabs/tailwindcss/pull/17113))
|
||||
- Don't create invalid CSS when encountering a link wrapped in square brackets ([#17129](https://github.com/tailwindlabs/tailwindcss/pull/17129))
|
||||
|
||||
## [4.0.12] - 2025-03-07
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
use crate::cursor;
|
||||
use crate::extractor::bracket_stack::BracketStack;
|
||||
use crate::extractor::machine::{Machine, MachineState};
|
||||
use crate::extractor::pre_processors::pre_processor::PreProcessor;
|
||||
use crate::extractor::variant_machine::VariantMachine;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Haml;
|
||||
|
|
@ -14,29 +16,48 @@ impl PreProcessor for Haml {
|
|||
|
||||
while cursor.pos < len {
|
||||
match cursor.curr {
|
||||
// Consume strings as-is
|
||||
b'\'' | b'"' => {
|
||||
let len = cursor.input.len();
|
||||
let end_char = cursor.curr;
|
||||
// Only replace `.` with a space if it's not surrounded by numbers. E.g.:
|
||||
//
|
||||
// ```diff
|
||||
// - .flex.items-center
|
||||
// + flex items-center
|
||||
// ```
|
||||
//
|
||||
// But with numbers, it's allowed:
|
||||
//
|
||||
// ```diff
|
||||
// - px-2.5
|
||||
// + px-2.5
|
||||
// ```
|
||||
b'.' => {
|
||||
// Don't replace dots with spaces when inside of any type of brackets, because
|
||||
// this could be part of arbitrary values. E.g.: `bg-[url(https://example.com)]`
|
||||
// ^
|
||||
if !bracket_stack.is_empty() {
|
||||
cursor.advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
cursor.advance();
|
||||
// If the dot is surrounded by digits, we want to keep it. E.g.: `px-2.5`
|
||||
// EXCEPT if it's followed by a valid variant that happens to start with a
|
||||
// digit.
|
||||
// E.g.: `bg-red-500.2xl:flex`
|
||||
// ^^^
|
||||
if cursor.prev.is_ascii_digit() && cursor.next.is_ascii_digit() {
|
||||
let mut next_cursor = cursor.clone();
|
||||
next_cursor.advance();
|
||||
|
||||
while cursor.pos < len {
|
||||
match cursor.curr {
|
||||
// Escaped character, skip ahead to the next character
|
||||
b'\\' => cursor.advance_twice(),
|
||||
|
||||
// End of the string
|
||||
b'\'' | b'"' if cursor.curr == end_char => break,
|
||||
|
||||
// Everything else is valid
|
||||
_ => cursor.advance(),
|
||||
};
|
||||
let mut variant_machine = VariantMachine::default();
|
||||
if let MachineState::Done(_) = variant_machine.next(&mut next_cursor) {
|
||||
result[cursor.pos] = b' ';
|
||||
}
|
||||
} else {
|
||||
result[cursor.pos] = b' ';
|
||||
}
|
||||
}
|
||||
|
||||
// Replace following characters with spaces if they are not inside of brackets
|
||||
b'.' | b'#' | b'=' if bracket_stack.is_empty() => {
|
||||
b'#' | b'=' if bracket_stack.is_empty() => {
|
||||
result[cursor.pos] = b' ';
|
||||
}
|
||||
|
||||
|
|
@ -48,7 +69,7 @@ impl PreProcessor for Haml {
|
|||
bracket_stack.push(cursor.curr);
|
||||
}
|
||||
|
||||
b')' | b']' | b'}' => {
|
||||
b')' | b']' | b'}' if !bracket_stack.is_empty() => {
|
||||
bracket_stack.pop(cursor.curr);
|
||||
|
||||
// Replace closing bracket with a space
|
||||
|
|
@ -116,8 +137,56 @@ mod tests {
|
|||
".text-lime-500.xl:text-emerald-500#root",
|
||||
" text-lime-500 xl:text-emerald-500 root",
|
||||
),
|
||||
// Dots in strings in HTML attributes stay as-is
|
||||
(r#"<div id="px-2.5"></div>"#, r#"<div id "px-2.5"></div>"#),
|
||||
] {
|
||||
Haml::test(input, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strings_only_occur_when_nested() {
|
||||
let input = r#"
|
||||
%p.mt-2.text-xl
|
||||
The quote in the next word, can't be the start of a string
|
||||
|
||||
%h3.mt-24.text-center.text-4xl.font-bold.italic
|
||||
The classes above should be extracted
|
||||
"#;
|
||||
|
||||
Haml::test_extract_contains(
|
||||
input,
|
||||
vec![
|
||||
// First paragraph
|
||||
"mt-2",
|
||||
"text-xl",
|
||||
// second paragraph
|
||||
"mt-24",
|
||||
"text-center",
|
||||
"text-4xl",
|
||||
"font-bold",
|
||||
"italic",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// https://github.com/tailwindlabs/tailwindcss/pull/17051#issuecomment-2711181352
|
||||
#[test]
|
||||
fn test_haml_full_file() {
|
||||
let processed = Haml.process(include_bytes!("./test-fixtures/haml/src-1.haml"));
|
||||
let actual = std::str::from_utf8(&processed).unwrap();
|
||||
let expected = include_str!("./test-fixtures/haml/dst-1.haml");
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arbitrary_code_followed_by_classes() {
|
||||
let input = r#"
|
||||
%p
|
||||
= i < 3
|
||||
.flex.items-center
|
||||
"#;
|
||||
Haml::test_extract_contains(input, vec!["flex", "items-center"]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
use crate::cursor;
|
||||
use crate::extractor::bracket_stack::BracketStack;
|
||||
use crate::extractor::machine::{Machine, MachineState};
|
||||
use crate::extractor::pre_processors::pre_processor::PreProcessor;
|
||||
use crate::extractor::variant_machine::VariantMachine;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Pug;
|
||||
|
|
@ -14,30 +16,44 @@ impl PreProcessor for Pug {
|
|||
|
||||
while cursor.pos < len {
|
||||
match cursor.curr {
|
||||
// Consume strings as-is
|
||||
b'\'' | b'"' => {
|
||||
let len = cursor.input.len();
|
||||
let end_char = cursor.curr;
|
||||
|
||||
cursor.advance();
|
||||
|
||||
while cursor.pos < len {
|
||||
match cursor.curr {
|
||||
// Escaped character, skip ahead to the next character
|
||||
b'\\' => cursor.advance_twice(),
|
||||
|
||||
// End of the string
|
||||
b'\'' | b'"' if cursor.curr == end_char => break,
|
||||
|
||||
// Everything else is valid
|
||||
_ => cursor.advance(),
|
||||
};
|
||||
// Only replace `.` with a space if it's not surrounded by numbers. E.g.:
|
||||
//
|
||||
// ```diff
|
||||
// - .flex.items-center
|
||||
// + flex items-center
|
||||
// ```
|
||||
//
|
||||
// But with numbers, it's allowed:
|
||||
//
|
||||
// ```diff
|
||||
// - px-2.5
|
||||
// + px-2.5
|
||||
// ```
|
||||
b'.' => {
|
||||
// Don't replace dots with spaces when inside of any type of brackets, because
|
||||
// this could be part of arbitrary values. E.g.: `bg-[url(https://example.com)]`
|
||||
// ^
|
||||
if !bracket_stack.is_empty() {
|
||||
cursor.advance();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Replace dots with spaces
|
||||
b'.' if bracket_stack.is_empty() => {
|
||||
result[cursor.pos] = b' ';
|
||||
// If the dot is surrounded by digits, we want to keep it. E.g.: `px-2.5`
|
||||
// EXCEPT if it's followed by a valid variant that happens to start with a
|
||||
// digit.
|
||||
// E.g.: `bg-red-500.2xl:flex`
|
||||
// ^^^
|
||||
if cursor.prev.is_ascii_digit() && cursor.next.is_ascii_digit() {
|
||||
let mut next_cursor = cursor.clone();
|
||||
next_cursor.advance();
|
||||
|
||||
let mut variant_machine = VariantMachine::default();
|
||||
if let MachineState::Done(_) = variant_machine.next(&mut next_cursor) {
|
||||
result[cursor.pos] = b' ';
|
||||
}
|
||||
} else {
|
||||
result[cursor.pos] = b' ';
|
||||
}
|
||||
}
|
||||
|
||||
b'(' | b'[' | b'{' => {
|
||||
|
|
@ -77,8 +93,45 @@ mod tests {
|
|||
"bg-[url(https://example.com/?q=[1,2])]",
|
||||
"bg-[url(https://example.com/?q=[1,2])]",
|
||||
),
|
||||
// Classes in HTML attributes
|
||||
(r#"<div id="px-2.5"></div>"#, r#"<div id="px-2.5"></div>"#),
|
||||
] {
|
||||
Pug::test(input, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strings_only_occur_when_nested() {
|
||||
let input = r#"
|
||||
p.mt-2.text-xl
|
||||
div The quote in the next word, can't be the start of a string
|
||||
|
||||
h3.mt-24.text-center.text-4xl.font-bold.italic
|
||||
div The classes above should be extracted
|
||||
"#;
|
||||
|
||||
Pug::test_extract_contains(
|
||||
input,
|
||||
vec![
|
||||
// First paragraph
|
||||
"mt-2",
|
||||
"text-xl",
|
||||
// second paragraph
|
||||
"mt-24",
|
||||
"text-center",
|
||||
"text-4xl",
|
||||
"font-bold",
|
||||
"italic",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arbitrary_code_followed_by_classes() {
|
||||
let input = r#"
|
||||
- i < 3
|
||||
.flex.items-center
|
||||
"#;
|
||||
Pug::test_extract_contains(input, vec!["flex", "items-center"]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
use crate::cursor;
|
||||
use crate::extractor::bracket_stack::BracketStack;
|
||||
use crate::extractor::machine::{Machine, MachineState};
|
||||
use crate::extractor::pre_processors::pre_processor::PreProcessor;
|
||||
use crate::extractor::variant_machine::VariantMachine;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Slim;
|
||||
|
|
@ -11,50 +13,47 @@ impl PreProcessor for Slim {
|
|||
let mut result = content.to_vec();
|
||||
let mut cursor = cursor::Cursor::new(content);
|
||||
let mut bracket_stack = BracketStack::default();
|
||||
let mut line_start_pos = 0x00;
|
||||
|
||||
while cursor.pos < len {
|
||||
match cursor.curr {
|
||||
b'\n' => {
|
||||
line_start_pos = cursor.pos + 1;
|
||||
}
|
||||
|
||||
// Line indicators:
|
||||
// Only replace `.` with a space if it's not surrounded by numbers. E.g.:
|
||||
//
|
||||
// > Verbatim text with trailing white space '
|
||||
// > See: https://github.com/slim-template/slim?tab=readme-ov-file#verbatim-text-with-trailing-white-space-
|
||||
b'\''
|
||||
if cursor.input[line_start_pos..cursor.pos]
|
||||
.iter()
|
||||
.all(|x| x.is_ascii_whitespace()) =>
|
||||
{
|
||||
// Do not treat the `'` as a string
|
||||
}
|
||||
|
||||
// Consume strings as-is
|
||||
b'\'' | b'"' => {
|
||||
let len = cursor.input.len();
|
||||
let end_char = cursor.curr;
|
||||
|
||||
cursor.advance();
|
||||
|
||||
while cursor.pos < len {
|
||||
match cursor.curr {
|
||||
// Escaped character, skip ahead to the next character
|
||||
b'\\' => cursor.advance_twice(),
|
||||
|
||||
// End of the string
|
||||
b'\'' | b'"' if cursor.curr == end_char => break,
|
||||
|
||||
// Everything else is valid
|
||||
_ => cursor.advance(),
|
||||
};
|
||||
// ```diff
|
||||
// - .flex.items-center
|
||||
// + flex items-center
|
||||
// ```
|
||||
//
|
||||
// But with numbers, it's allowed:
|
||||
//
|
||||
// ```diff
|
||||
// - px-2.5
|
||||
// + px-2.5
|
||||
// ```
|
||||
b'.' => {
|
||||
// Don't replace dots with spaces when inside of any type of brackets, because
|
||||
// this could be part of arbitrary values. E.g.: `bg-[url(https://example.com)]`
|
||||
// ^
|
||||
if !bracket_stack.is_empty() {
|
||||
cursor.advance();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Replace dots with spaces
|
||||
b'.' if bracket_stack.is_empty() => {
|
||||
result[cursor.pos] = b' ';
|
||||
// If the dot is surrounded by digits, we want to keep it. E.g.: `px-2.5`
|
||||
// EXCEPT if it's followed by a valid variant that happens to start with a
|
||||
// digit.
|
||||
// E.g.: `bg-red-500.2xl:flex`
|
||||
// ^^^
|
||||
if cursor.prev.is_ascii_digit() && cursor.next.is_ascii_digit() {
|
||||
let mut next_cursor = cursor.clone();
|
||||
next_cursor.advance();
|
||||
|
||||
let mut variant_machine = VariantMachine::default();
|
||||
if let MachineState::Done(_) = variant_machine.next(&mut next_cursor) {
|
||||
result[cursor.pos] = b' ';
|
||||
}
|
||||
} else {
|
||||
result[cursor.pos] = b' ';
|
||||
}
|
||||
}
|
||||
|
||||
// Any `[` preceded by an alphanumeric value will not be part of a candidate.
|
||||
|
|
@ -111,6 +110,11 @@ mod tests {
|
|||
// Convert dots to spaces
|
||||
("div.flex.bg-red-500", "div flex bg-red-500"),
|
||||
(".flex.bg-red-500", " flex bg-red-500"),
|
||||
(".bg-red-500.2xl:flex", " bg-red-500 2xl:flex"),
|
||||
(
|
||||
".bg-red-500.2xl:flex.bg-green-200.3xl:flex",
|
||||
" bg-red-500 2xl:flex bg-green-200 3xl:flex",
|
||||
),
|
||||
// Keep dots in strings
|
||||
(r#"div(class="px-2.5")"#, r#"div(class="px-2.5")"#),
|
||||
// Replace top-level `(a-z0-9)[` with `$1 `. E.g.: `.flex[x]` -> `.flex x]`
|
||||
|
|
@ -135,6 +139,12 @@ mod tests {
|
|||
("content-['50[]']", "content-['50[]']"),
|
||||
// Escaped string
|
||||
("content-['a\'b\'c\'']", "content-['a\'b\'c\'']"),
|
||||
// Classes in HTML attributes
|
||||
("<div id=\"px-2.5\"></div>", "<div id=\"px-2.5\"></div>"),
|
||||
(
|
||||
"<div id=\"px-2.5 bg-red-500 2xl:flex bg-green-200 3xl:flex\"></div>",
|
||||
"<div id=\"px-2.5 bg-red-500 2xl:flex bg-green-200 3xl:flex\"></div>",
|
||||
),
|
||||
] {
|
||||
Slim::test(input, expected);
|
||||
}
|
||||
|
|
@ -155,20 +165,6 @@ mod tests {
|
|||
| Should be BLACK on RED - FAIL
|
||||
"#;
|
||||
|
||||
let expected = r#"
|
||||
text-black
|
||||
data-controller= ['foo', ('bar' if rand.positive?)].join(' ')
|
||||
]
|
||||
bg-green-300
|
||||
| BLACK on GREEN - OK
|
||||
|
||||
bg-red-300
|
||||
data-foo= 42
|
||||
]
|
||||
| Should be BLACK on RED - FAIL
|
||||
"#;
|
||||
|
||||
Slim::test(input, expected);
|
||||
Slim::test_extract_contains(input, vec!["text-black", "bg-green-300", "bg-red-300"]);
|
||||
}
|
||||
|
||||
|
|
@ -197,4 +193,39 @@ mod tests {
|
|||
Slim::test(input, expected);
|
||||
Slim::test_extract_contains(input, vec!["text-red-500", "text-3xl"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strings_only_occur_when_nested() {
|
||||
let input = r#"
|
||||
p.mt-2.text-xl
|
||||
| The quote in the next word, can't be the start of a string
|
||||
|
||||
h3.mt-24.text-center.text-4xl.font-bold.italic
|
||||
| The classes above should be extracted
|
||||
"#;
|
||||
|
||||
Slim::test_extract_contains(
|
||||
input,
|
||||
vec![
|
||||
// First paragraph
|
||||
"mt-2",
|
||||
"text-xl",
|
||||
// second paragraph
|
||||
"mt-24",
|
||||
"text-center",
|
||||
"text-4xl",
|
||||
"font-bold",
|
||||
"italic",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arbitrary_code_followed_by_classes() {
|
||||
let input = r#"
|
||||
- i < 3
|
||||
.flex.items-center
|
||||
"#;
|
||||
Slim::test_extract_contains(input, vec!["flex", "items-center"]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,155 @@
|
|||
/ https://github com/tailwindlabs/tailwindcss/pull/17051 issuecomment-2711181352
|
||||
- star_styles "size- 800px mask mask-star bg-gradient-to-r from-secondary via-cyan-400 to-lime-400"
|
||||
- crazy_text_styles "italic font-black bg-gradient-to-r via-60% to-90% from-orange-500 via-secondary to-primary text-transparent bg-clip-text inline-block py-2"
|
||||
|
||||
relative
|
||||
- Blurred background star
|
||||
absolute left-0 z-0 class: "-top-[400px] -right-[400px]"
|
||||
flex justify-end blur-3xl
|
||||
%div class: star_styles
|
||||
|
||||
relative z-10
|
||||
%h1 mt-8 text-center text-5xl font-black tracking-wide drop-shadow-lg
|
||||
%div
|
||||
Components, Guides, and Paradigms
|
||||
md:inline-flex md:items-center justify-center
|
||||
%span -mr-2
|
||||
for
|
||||
%span relative -rotate-3 hover:-rotate-6 hover:scale-125 transition-transform
|
||||
%span class: crazy_text_styles + " absolute blur-xl"
|
||||
CRAZY
|
||||
%span class: crazy_text_styles + " drop-shadow-[0_0_1px_#fff]"
|
||||
CRAZY
|
||||
%span -ml-3 md:-ml-1
|
||||
\-fast Development
|
||||
%div
|
||||
in Ruby on Rails!
|
||||
|
||||
mt-16 text-center
|
||||
daisy_button "😃 Get Started", css: "btn-primary text-xl",
|
||||
right_icon: "arrow-right", target: "_blank",
|
||||
href: "https://github com/profoundry-us/loco_motion locomotion-components"
|
||||
|
||||
mt-32 lg:flex lg:space-x-8
|
||||
%div class: "md:basis-2/5"
|
||||
%h3 text-4xl font-bold
|
||||
Easy, Flexible Components
|
||||
%p mt-2 text-xl
|
||||
Powered by the fabulous
|
||||
succeed ',' do
|
||||
daisy_link "ViewComponent", "https://viewcomponent.org", target: "_blank"
|
||||
succeed ', and' do
|
||||
daisy_link "DaisyUI", "https://daisyui com/", target: "_blank"
|
||||
daisy_link "TailwindCSS", "https://tailwindcss com/", target: "_blank"
|
||||
libraries, our components are designed to be fast, flexible, and easy to
|
||||
use <i>directy</i> in Ruby on Rails!
|
||||
|
||||
doc_example css: "mt-8 lg:mt-0 lg:basis-3/5 h-44" do
|
||||
flex flex-col sm:flex-row items-center gap-4
|
||||
daisy_button "Accent Button", css: "btn-accent"
|
||||
daisy_tip "Click to Swap" do
|
||||
daisy_swap off: "🌚", on: "🌞", css: "swap-rotate text-4xl"
|
||||
daisy_badge "Large Badge", css: "badge-secondary badge-lg"
|
||||
|
||||
|
||||
mt-32 xl:flex xl:flex-row-reverse xl:items-center
|
||||
text-xl xl:ml-8
|
||||
%h3 text-4xl font-bold
|
||||
Simple, Concise Views
|
||||
|
||||
%p mt-2
|
||||
Utlize
|
||||
daisy_link "HAML", "https://haml.info/", target: "_blank"
|
||||
so your views are simple, concise, and easy to understand
|
||||
|
||||
%p mt-2
|
||||
No more messy ERB files with all of their closing tags and Ruby wrappers
|
||||
HAML feels more natural to write and reduces file sizes, making your
|
||||
views easier to read and maintain
|
||||
|
||||
%p mt-2
|
||||
:markdown
|
||||
**PLUS!** You can utlize filters like _Markdown_, CoffeeScript,
|
||||
Textile, and many more!
|
||||
|
||||
mt-8 xl:mt-0
|
||||
flex flex-col xl:flex xl:flex-row w-full
|
||||
doc_code css: "grow", code_css: "xl:pl-2 xl:pr-0 xl:rounded-r-none", language: "erb" do
|
||||
:escaped
|
||||
<% Ruby %>
|
||||
|
||||
<% 5 times do |i| %>
|
||||
<% if i even? %>
|
||||
<p class "odd">Number <% i %></p>
|
||||
<% else %>
|
||||
<p class "even">Numero <% i %></p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
doc_code css: "grow mt-8 xl:mt-0", pre_css: "xl:h-full", code_css: "xl:pl-0 xl:pr-2 xl:h-full xl:rounded-l-none" do
|
||||
:escaped
|
||||
- HAML
|
||||
|
||||
- 5 times do |i|
|
||||
- if i even?
|
||||
%p odd Number \ i
|
||||
- else
|
||||
%p even Numero \ i
|
||||
|
||||
|
||||
%h3 mt-32 text-4xl font-bold
|
||||
Build Your <i>OWN</i> Components
|
||||
|
||||
%p mt-2 text-xl
|
||||
Can't find exactly what you need? No problem! Build your own components
|
||||
with ease using our simple, flexible, and powerful DSL
|
||||
|
||||
mt-8 xl:flex xl:space-x-8
|
||||
%div
|
||||
doc_code language: "ruby" do
|
||||
:escaped
|
||||
app/components/application_component rb
|
||||
class ApplicationComponent < LocoMotion::BaseComponent
|
||||
Add your custom / shared component logic here!
|
||||
end
|
||||
|
||||
doc_code language: "haml", css: "mt-8" do
|
||||
:escaped
|
||||
- app/components/character_component html haml
|
||||
part :component do
|
||||
part :head
|
||||
part :body do
|
||||
content
|
||||
part :legs
|
||||
|
||||
%div mt-8 xl:mt-0
|
||||
doc_code language: "ruby" do
|
||||
:escaped
|
||||
app/components/character_component rb
|
||||
class CharacterComponent < ApplicationComponent
|
||||
define_parts :head, :body, :legs
|
||||
|
||||
def before_render
|
||||
set_tag_name :head, :h1
|
||||
add_css :head, "text-3xl font-bold"
|
||||
|
||||
set_tag_name :body, :p
|
||||
add_stimulus_controller :body, "character-body"
|
||||
add_css :body, "text-lg"
|
||||
|
||||
set_tag_name :legs, :footer
|
||||
add_css :legs, "text-sm"
|
||||
end
|
||||
end
|
||||
|
||||
%h3 mt-24 text-center text-4xl font-bold italic
|
||||
More Coming Soon!
|
||||
%p mt-2 text-xl text-center
|
||||
Keen an eye out as we'll be adding more components, guides, and
|
||||
%br max-sm:hidden
|
||||
suggested gems for you to build amazing Rails apps!
|
||||
mt-4 text-center
|
||||
daisy_button "😉 Get Started", css: "btn-primary text-xl",
|
||||
right_icon: "arrow-right", target: "_blank",
|
||||
class: "px-2.5"
|
||||
href: "https://github com/profoundry-us/loco_motion locomotion-components"
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
/ https://github.com/tailwindlabs/tailwindcss/pull/17051#issuecomment-2711181352
|
||||
- star_styles = "size-[800px] mask mask-star bg-gradient-to-r from-secondary via-cyan-400 to-lime-400"
|
||||
- crazy_text_styles = "italic font-black bg-gradient-to-r via-60% to-90% from-orange-500 via-secondary to-primary text-transparent bg-clip-text inline-block py-2"
|
||||
|
||||
.relative
|
||||
- # Blurred background star
|
||||
.absolute.left-0.z-0{ class: "-top-[400px] -right-[400px]" }
|
||||
.flex.justify-end.blur-3xl
|
||||
%div{ class: star_styles }
|
||||
|
||||
.relative.z-10
|
||||
%h1.mt-8.text-center.text-5xl.font-black.tracking-wide.drop-shadow-lg
|
||||
%div
|
||||
Components, Guides, and Paradigms
|
||||
.md:inline-flex.md:items-center.justify-center
|
||||
%span.-mr-2
|
||||
for
|
||||
%span.relative.-rotate-3.hover:-rotate-6.hover:scale-125.transition-transform
|
||||
%span{ class: crazy_text_styles + " absolute blur-xl" }
|
||||
CRAZY
|
||||
%span{ class: crazy_text_styles + " drop-shadow-[0_0_1px_#fff]" }
|
||||
CRAZY
|
||||
%span.-ml-3.md:-ml-1
|
||||
\-fast Development
|
||||
%div
|
||||
in Ruby on Rails!
|
||||
|
||||
.mt-16.text-center
|
||||
= daisy_button "😃 Get Started", css: "btn-primary text-xl",
|
||||
right_icon: "arrow-right", target: "_blank",
|
||||
href: "https://github.com/profoundry-us/loco_motion#locomotion-components"
|
||||
|
||||
.mt-32.lg:flex.lg:space-x-8
|
||||
%div{ class: "md:basis-2/5" }
|
||||
%h3.text-4xl.font-bold
|
||||
Easy, Flexible Components
|
||||
%p.mt-2.text-xl
|
||||
Powered by the fabulous
|
||||
= succeed ',' do
|
||||
= daisy_link("ViewComponent", "https://viewcomponent.org", target: "_blank")
|
||||
= succeed ', and' do
|
||||
= daisy_link "DaisyUI", "https://daisyui.com/", target: "_blank"
|
||||
= daisy_link "TailwindCSS", "https://tailwindcss.com/", target: "_blank"
|
||||
libraries, our components are designed to be fast, flexible, and easy to
|
||||
use <i>directy</i> in Ruby on Rails!
|
||||
|
||||
= doc_example(css: "mt-8 lg:mt-0 lg:basis-3/5 h-44") do
|
||||
.flex.flex-col.sm:flex-row.items-center.gap-4
|
||||
= daisy_button "Accent Button", css: "btn-accent"
|
||||
= daisy_tip("Click to Swap") do
|
||||
= daisy_swap off: "🌚", on: "🌞", css: "swap-rotate text-4xl"
|
||||
= daisy_badge "Large Badge", css: "badge-secondary badge-lg"
|
||||
|
||||
|
||||
.mt-32.xl:flex.xl:flex-row-reverse.xl:items-center
|
||||
.text-xl.xl:ml-8
|
||||
%h3.text-4xl.font-bold
|
||||
Simple, Concise Views
|
||||
|
||||
%p.mt-2
|
||||
Utlize
|
||||
= daisy_link("HAML", "https://haml.info/", target: "_blank")
|
||||
so your views are simple, concise, and easy to understand.
|
||||
|
||||
%p.mt-2
|
||||
No more messy ERB files with all of their closing tags and Ruby wrappers.
|
||||
HAML feels more natural to write and reduces file sizes, making your
|
||||
views easier to read and maintain.
|
||||
|
||||
%p.mt-2
|
||||
:markdown
|
||||
**PLUS!** You can utlize filters like _Markdown_, CoffeeScript,
|
||||
Textile, and many more!
|
||||
|
||||
.mt-8.xl:mt-0
|
||||
.flex.flex-col.xl:flex.xl:flex-row.w-full
|
||||
= doc_code(css: "grow", code_css: "xl:pl-2 xl:pr-0 xl:rounded-r-none", language: "erb") do
|
||||
:escaped
|
||||
<% # Ruby %>
|
||||
|
||||
<% 5.times do |i| %>
|
||||
<% if i.even? %>
|
||||
<p class="odd">Number <%= i %></p>
|
||||
<% else %>
|
||||
<p class="even">Numero <%= i %></p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
= doc_code(css: "grow mt-8 xl:mt-0", pre_css: "xl:h-full", code_css: "xl:pl-0 xl:pr-2 xl:h-full xl:rounded-l-none") do
|
||||
:escaped
|
||||
- # HAML
|
||||
|
||||
- 5.times do |i|
|
||||
- if i.even?
|
||||
%p.odd Number \#{i}
|
||||
- else
|
||||
%p.even Numero \#{i}
|
||||
|
||||
|
||||
%h3.mt-32.text-4xl.font-bold
|
||||
Build Your <i>OWN</i> Components
|
||||
|
||||
%p.mt-2.text-xl
|
||||
Can't find exactly what you need? No problem! Build your own components
|
||||
with ease using our simple, flexible, and powerful DSL.
|
||||
|
||||
.mt-8.xl:flex.xl:space-x-8
|
||||
%div
|
||||
= doc_code(language: "ruby") do
|
||||
:escaped
|
||||
# app/components/application_component.rb
|
||||
class ApplicationComponent < LocoMotion::BaseComponent
|
||||
# Add your custom / shared component logic here!
|
||||
end
|
||||
|
||||
= doc_code(language: "haml", css: "mt-8") do
|
||||
:escaped
|
||||
- # app/components/character_component.html.haml
|
||||
= part(:component) do
|
||||
= part(:head)
|
||||
= part(:body) do
|
||||
= content
|
||||
= part(:legs)
|
||||
|
||||
%div.mt-8.xl:mt-0
|
||||
= doc_code(language: "ruby") do
|
||||
:escaped
|
||||
# app/components/character_component.rb
|
||||
class CharacterComponent < ApplicationComponent
|
||||
define_parts :head, :body, :legs
|
||||
|
||||
def before_render
|
||||
set_tag_name(:head, :h1)
|
||||
add_css(:head, "text-3xl font-bold")
|
||||
|
||||
set_tag_name(:body, :p)
|
||||
add_stimulus_controller(:body, "character-body")
|
||||
add_css(:body, "text-lg")
|
||||
|
||||
set_tag_name(:legs, :footer)
|
||||
add_css(:legs, "text-sm")
|
||||
end
|
||||
end
|
||||
|
||||
%h3.mt-24.text-center.text-4xl.font-bold.italic
|
||||
More Coming Soon!
|
||||
%p.mt-2.text-xl.text-center
|
||||
Keen an eye out as we'll be adding more components, guides, and
|
||||
%br.max-sm:hidden
|
||||
suggested gems for you to build amazing Rails apps!
|
||||
.mt-4.text-center
|
||||
= daisy_button "😉 Get Started", css: "btn-primary text-xl",
|
||||
right_icon: "arrow-right", target: "_blank",
|
||||
class: "px-2.5"
|
||||
href: "https://github.com/profoundry-us/loco_motion#locomotion-components"
|
||||
Loading…
Add table
Add a link
Reference in a new issue