Fix HAML extraction with embedded Ruby (#17846)
This PR fixes and improves the HAML extractor by ensuring that whenever we detect lines or blocks of Ruby code (`-`, `=` and `~`) characters at the beginning of the line, we treat them as Ruby code. Fixes: #17813 ## Test Plan 1. Existing tests pass 2. Changed 1 existing test which embedded Ruby syntax 3. Added a dedicated test to ensure the HAML file in the linked issue is parsed correctly Running this in the internal extractor tool you can see that the `w-[12px]`, `w-[16px]`, `h-[12px]`, and `h-[16px]` are properly extracted. Note: the `mr-12px` is also extracted, but not highlighted because this is not a valid Tailwind CSS class. <img width="1816" alt="image" src="https://github.com/user-attachments/assets/fc5929ca-bc71-47d2-b21b-7abeec86f54d" />
This commit is contained in:
parent
473f0241bf
commit
d38554d110
10 changed files with 682 additions and 159 deletions
|
|
@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Fixed
|
||||
|
||||
- Ensure negative arbitrary `scale` values generate negative values ([#17831](https://github.com/tailwindlabs/tailwindcss/pull/17831))
|
||||
- Fix HAML extraction with embedded Ruby ([#17846](https://github.com/tailwindlabs/tailwindcss/pull/17846))
|
||||
|
||||
## [4.1.5] - 2025-04-30
|
||||
|
||||
|
|
|
|||
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -582,6 +582,7 @@ dependencies = [
|
|||
"tempfile",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"unicode-width",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
|
|
@ -681,6 +682,12 @@ version = "1.10.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
|
|
|
|||
|
|
@ -23,3 +23,4 @@ regex = "1.11.1"
|
|||
[dev-dependencies]
|
||||
tempfile = "3.13.0"
|
||||
pretty_assertions = "1.4.1"
|
||||
unicode-width = "0.2.0"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ 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;
|
||||
use crate::scanner::pre_process_input;
|
||||
use bstr::ByteVec;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Haml;
|
||||
|
|
@ -14,8 +16,153 @@ impl PreProcessor for Haml {
|
|||
let mut cursor = cursor::Cursor::new(content);
|
||||
let mut bracket_stack = BracketStack::default();
|
||||
|
||||
// Haml Comments: -#
|
||||
// https://haml.info/docs/yardoc/file.REFERENCE.html#ruby-evaluation
|
||||
//
|
||||
// > The hyphen followed immediately by the pound sign signifies a silent comment. Any text
|
||||
// > following this isn’t rendered in the resulting document at all.
|
||||
//
|
||||
// ```haml
|
||||
// %p foo
|
||||
// -# This is a comment
|
||||
// %p bar
|
||||
// ```
|
||||
//
|
||||
// > You can also nest text beneath a silent comment. None of this text will be rendered.
|
||||
//
|
||||
// ```haml
|
||||
// %p foo
|
||||
// -#
|
||||
// This won't be displayed
|
||||
// Nor will this
|
||||
// Nor will this.
|
||||
// %p bar
|
||||
// ```
|
||||
//
|
||||
// Ruby Evaluation
|
||||
// https://haml.info/docs/yardoc/file.REFERENCE.html#ruby-evaluation
|
||||
//
|
||||
// When any of the following characters are the first non-whitespace character on the line,
|
||||
// then the line is treated as Ruby code:
|
||||
//
|
||||
// - Inserting Ruby: =
|
||||
// https://haml.info/docs/yardoc/file.REFERENCE.html#inserting_ruby
|
||||
//
|
||||
// ```haml
|
||||
// %p
|
||||
// = ['hi', 'there', 'reader!'].join " "
|
||||
// = "yo"
|
||||
// ```
|
||||
//
|
||||
// - Running Ruby: -
|
||||
// https://haml.info/docs/yardoc/file.REFERENCE.html#running-ruby--
|
||||
//
|
||||
// ```haml
|
||||
// - foo = "hello"
|
||||
// - foo << " there"
|
||||
// - foo << " you!"
|
||||
// %p= foo
|
||||
// ```
|
||||
//
|
||||
// - Whitespace Preservation: ~
|
||||
// https://haml.info/docs/yardoc/file.REFERENCE.html#tilde
|
||||
//
|
||||
// > ~ works just like =, except that it runs Haml::Helpers.preserve on its input.
|
||||
//
|
||||
// ```haml
|
||||
// ~ "Foo\n<pre>Bar\nBaz</pre>"
|
||||
// ```
|
||||
//
|
||||
// Important note:
|
||||
//
|
||||
// > A line of Ruby code can be stretched over multiple lines as long as each line but the
|
||||
// > last ends with a comma.
|
||||
//
|
||||
// ```haml
|
||||
// - links = {:home => "/",
|
||||
// :docs => "/docs",
|
||||
// :about => "/about"}
|
||||
// ```
|
||||
//
|
||||
// Ruby Blocks:
|
||||
// https://haml.info/docs/yardoc/file.REFERENCE.html#ruby-blocks
|
||||
//
|
||||
// > Ruby blocks, like XHTML tags, don’t need to be explicitly closed in Haml. Rather,
|
||||
// > they’re automatically closed, based on indentation. A block begins whenever the
|
||||
// > indentation is increased after a Ruby evaluation command. It ends when the indentation
|
||||
// > decreases (as long as it’s not an else clause or something similar).
|
||||
//
|
||||
// ```haml
|
||||
// - (42...47).each do |i|
|
||||
// %p= i
|
||||
// %p See, I can count!
|
||||
// ```
|
||||
//
|
||||
let mut last_newline_position = 0;
|
||||
|
||||
while cursor.pos < len {
|
||||
match cursor.curr {
|
||||
// Escape the next character
|
||||
b'\\' => {
|
||||
cursor.advance_twice();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Track the last newline position
|
||||
b'\n' => {
|
||||
last_newline_position = cursor.pos;
|
||||
cursor.advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip HAML comments. `-#`
|
||||
b'-' if cursor.input[last_newline_position..cursor.pos]
|
||||
.iter()
|
||||
.all(u8::is_ascii_whitespace)
|
||||
&& matches!(cursor.next, b'#') =>
|
||||
{
|
||||
// Just consume the comment
|
||||
let updated_last_newline_position =
|
||||
self.skip_indented_block(&mut cursor, last_newline_position);
|
||||
|
||||
// Override the last known newline position
|
||||
last_newline_position = updated_last_newline_position;
|
||||
}
|
||||
|
||||
// Skip HTML comments. `/`
|
||||
b'/' if cursor.input[last_newline_position..cursor.pos]
|
||||
.iter()
|
||||
.all(u8::is_ascii_whitespace) =>
|
||||
{
|
||||
// Just consume the comment
|
||||
let updated_last_newline_position =
|
||||
self.skip_indented_block(&mut cursor, last_newline_position);
|
||||
|
||||
// Override the last known newline position
|
||||
last_newline_position = updated_last_newline_position;
|
||||
}
|
||||
|
||||
// Ruby evaluation
|
||||
b'-' | b'=' | b'~'
|
||||
if cursor.input[last_newline_position..cursor.pos]
|
||||
.iter()
|
||||
.all(u8::is_ascii_whitespace) =>
|
||||
{
|
||||
let mut start = cursor.pos;
|
||||
let end = self.skip_indented_block(&mut cursor, last_newline_position);
|
||||
|
||||
// Increment start with 1 character to skip the `=` or `-` character
|
||||
start += 1;
|
||||
|
||||
let ruby_code = &cursor.input[start..end];
|
||||
|
||||
// Override the last known newline position
|
||||
last_newline_position = end;
|
||||
|
||||
let replaced = pre_process_input(ruby_code, "rb");
|
||||
result.replace_range(start..end, replaced);
|
||||
}
|
||||
|
||||
// Only replace `.` with a space if it's not surrounded by numbers. E.g.:
|
||||
//
|
||||
// ```diff
|
||||
|
|
@ -89,6 +236,107 @@ impl PreProcessor for Haml {
|
|||
}
|
||||
}
|
||||
|
||||
impl Haml {
|
||||
fn skip_indented_block(
|
||||
&self,
|
||||
cursor: &mut cursor::Cursor,
|
||||
last_known_newline_position: usize,
|
||||
) -> usize {
|
||||
let len = cursor.input.len();
|
||||
|
||||
// Special case: if the first character of the block is `=`, then newlines are only allowed
|
||||
// _if_ the last character of the previous line is a comma `,`.
|
||||
//
|
||||
// https://haml.info/docs/yardoc/file.REFERENCE.html#inserting_ruby
|
||||
//
|
||||
// > A line of Ruby code can be stretched over multiple lines as long as each line but the
|
||||
// > last ends with a comma. For example:
|
||||
//
|
||||
// ```haml
|
||||
// = link_to_remote "Add to cart",
|
||||
// :url => { :action => "add", :id => product.id },
|
||||
// :update => { :success => "cart", :failure => "error" }
|
||||
// ```
|
||||
let evaluation_type = cursor.curr;
|
||||
|
||||
let block_indentation_level = cursor
|
||||
.pos
|
||||
.saturating_sub(last_known_newline_position)
|
||||
.saturating_sub(1); /* The newline itself */
|
||||
|
||||
let mut last_newline_position = last_known_newline_position;
|
||||
|
||||
// Consume until the end of the line first
|
||||
while cursor.pos < len && cursor.curr != b'\n' {
|
||||
cursor.advance();
|
||||
}
|
||||
|
||||
// Block is already done, aka just a line
|
||||
if evaluation_type == b'=' && cursor.prev != b',' {
|
||||
return cursor.pos;
|
||||
}
|
||||
|
||||
'outer: while cursor.pos < len {
|
||||
match cursor.curr {
|
||||
// Escape the next character
|
||||
b'\\' => {
|
||||
cursor.advance_twice();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Track the last newline position
|
||||
b'\n' => {
|
||||
last_newline_position = cursor.pos;
|
||||
|
||||
// We are done with this block
|
||||
if evaluation_type == b'=' && cursor.prev != b',' {
|
||||
break;
|
||||
}
|
||||
|
||||
cursor.advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip whitespace and compute the indentation level
|
||||
x if x.is_ascii_whitespace() => {
|
||||
// Find first non-whitespace character
|
||||
while cursor.pos < len && cursor.curr.is_ascii_whitespace() {
|
||||
if cursor.curr == b'\n' {
|
||||
last_newline_position = cursor.pos;
|
||||
|
||||
if evaluation_type == b'=' && cursor.prev != b',' {
|
||||
// We are done with this block
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
|
||||
cursor.advance();
|
||||
}
|
||||
|
||||
let indentation = cursor
|
||||
.pos
|
||||
.saturating_sub(last_newline_position)
|
||||
.saturating_sub(1); /* The newline itself */
|
||||
if indentation < block_indentation_level {
|
||||
// We are done with this block
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Not whitespace, end of block
|
||||
_ => break,
|
||||
};
|
||||
|
||||
cursor.advance();
|
||||
}
|
||||
|
||||
// Move the cursor to the last newline position
|
||||
cursor.move_to(last_newline_position);
|
||||
|
||||
last_newline_position
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Haml;
|
||||
|
|
@ -173,10 +421,18 @@ mod tests {
|
|||
|
||||
// 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");
|
||||
fn test_haml_full_file_17051() {
|
||||
let actual = Haml::extract_annotated(include_bytes!("./test-fixtures/haml/src-17051.haml"));
|
||||
let expected = include_str!("./test-fixtures/haml/dst-17051.haml");
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
// https://github.com/tailwindlabs/tailwindcss/issues/17813
|
||||
#[test]
|
||||
fn test_haml_full_file_17813() {
|
||||
let actual = Haml::extract_annotated(include_bytes!("./test-fixtures/haml/src-17813.haml"));
|
||||
let expected = include_str!("./test-fixtures/haml/dst-17813.haml");
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,4 +57,104 @@ pub trait PreProcessor: Sized + Default {
|
|||
panic!("Missing some items");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn extract_annotated(input: &[u8]) -> String {
|
||||
use crate::extractor::{Extracted, Extractor};
|
||||
use std::collections::BTreeMap;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
let processor = Self::default();
|
||||
let transformed = processor.process(input);
|
||||
|
||||
let extracted = Extractor::new(&transformed).extract();
|
||||
|
||||
// Extract only candidate positions
|
||||
let byte_ranges = extracted
|
||||
.iter()
|
||||
.filter_map(|x| match x {
|
||||
Extracted::Candidate(bytes) => {
|
||||
let start = bytes.as_ptr() as usize - transformed.as_ptr() as usize;
|
||||
let end = start + bytes.len();
|
||||
Some((start, end))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Convert byte ranges to (line, start_col, end_col)
|
||||
let mut annotations = byte_ranges
|
||||
.into_iter()
|
||||
.map(|(start, end)| {
|
||||
let (line, start_col) = byte_offset_to_line_and_column(input, start);
|
||||
let (_, end_col) = byte_offset_to_line_and_column(input, end);
|
||||
(line, start_col, end_col)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Sort for safe insertion
|
||||
annotations.sort_by(|a, b| b.0.cmp(&a.0).then(b.1.cmp(&a.1)));
|
||||
|
||||
// Convert input to lines
|
||||
let mut lines = std::str::from_utf8(input)
|
||||
.expect("Input must be valid UTF-8")
|
||||
.lines()
|
||||
.map(|line| line.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Group annotations per line
|
||||
let mut grouped = BTreeMap::<usize, Vec<(usize, usize)>>::new();
|
||||
for (line, start_char, end_char) in annotations {
|
||||
grouped
|
||||
.entry(line)
|
||||
.or_default()
|
||||
.push((start_char, end_char));
|
||||
}
|
||||
|
||||
// Inject annotation lines
|
||||
for (line_idx, spans) in grouped.into_iter().rev() {
|
||||
let display_line = &lines[line_idx];
|
||||
let width = UnicodeWidthStr::width(display_line.as_str());
|
||||
let mut annotation = vec![' '; width];
|
||||
|
||||
for (start, end) in spans {
|
||||
for i in start..end.min(annotation.len()) {
|
||||
annotation[i] = '^';
|
||||
}
|
||||
}
|
||||
|
||||
let annotation_line: String = annotation
|
||||
.into_iter()
|
||||
.collect::<String>()
|
||||
.trim_end()
|
||||
.to_owned();
|
||||
lines.insert(line_idx + 1, annotation_line);
|
||||
}
|
||||
|
||||
lines.join("\n").trim_end().to_string() + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn byte_offset_to_line_and_column(input: &[u8], offset: usize) -> (usize, usize) {
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
let mut line_start = 0;
|
||||
let mut line = 0;
|
||||
|
||||
for (i, &b) in input.iter().enumerate() {
|
||||
if i >= offset {
|
||||
break;
|
||||
}
|
||||
if b == b'\n' {
|
||||
line += 1;
|
||||
line_start = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
let slice = &input[line_start..offset];
|
||||
let column = std::str::from_utf8(slice).expect("Valid UTF-8");
|
||||
let column = UnicodeWidthStr::width(column);
|
||||
|
||||
(line, column)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,155 +0,0 @@
|
|||
/ 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,264 @@
|
|||
/ 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,30 @@
|
|||
:ruby
|
||||
- devices_classes = 'size-5 mr-2px'
|
||||
^^^^^^^^^^^^^^^ ^^^^^^ ^^^^^^
|
||||
- icon_classes = 'w-[12px] h-[12px]'
|
||||
^^^^^^^^^^^^ ^^^^^^^^ ^^^^^^^^
|
||||
!!!
|
||||
%html{ lang: 'en' }
|
||||
^^^^ ^^
|
||||
%head
|
||||
%title Tailwind v4.1.4 + HAML bug
|
||||
^^^^^^ ^^^
|
||||
|
||||
-# This is a comment
|
||||
^^ ^ ^^^^^^^
|
||||
|
||||
A multi-line comment
|
||||
^^^^^^^^^^ ^^^^^^^
|
||||
With more indentation
|
||||
^^^^ ^^^^^^^^^^^
|
||||
|
||||
Which can dedent again just fine
|
||||
^^^ ^^^^^^ ^^^^^ ^^^^ ^^^^
|
||||
|
||||
%body
|
||||
- icon_classes = 'self-center w-[16px] h-[16px]'
|
||||
^^^^^^^^^^^^ ^^^^^^^^^^^ ^^^^^^^^ ^^^^^^^^
|
||||
.flex{ class: icon_classes }
|
||||
^^^^ ^^^^^ ^^^^^^^^^^^^
|
||||
.flex{ class: devices_classes }
|
||||
^^^^ ^^^^^ ^^^^^^^^^^^^^^^
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
:ruby
|
||||
- devices_classes = 'size-5 mr-2px'
|
||||
- icon_classes = 'w-[12px] h-[12px]'
|
||||
!!!
|
||||
%html{ lang: 'en' }
|
||||
%head
|
||||
%title Tailwind v4.1.4 + HAML bug
|
||||
|
||||
-# This is a comment
|
||||
|
||||
A multi-line comment
|
||||
With more indentation
|
||||
|
||||
Which can dedent again just fine
|
||||
|
||||
%body
|
||||
- icon_classes = 'self-center w-[16px] h-[16px]'
|
||||
.flex{ class: icon_classes }
|
||||
.flex{ class: devices_classes }
|
||||
Loading…
Add table
Add a link
Reference in a new issue