ruby-ui / ruby_ui

Ruby gem for RBUI Components
https://rbui.dev/
MIT License
247 stars 30 forks source link

More flexible CSS classes management/customization proposal #102

Open qd3v opened 2 months ago

qd3v commented 2 months ago

Because TCSS is all about adding lots of new CSS classes even for prebuilt components (thanks for your work, BTW:) having own subclasses is unavoidable, especially if two themes required to exist. In my code I found subclassing PhlexUI components is the only way to avoid passing class: "xxxx" each time I'm using them. Not a big deal, that's ok. But I found this approach more flexible:

module PhlexUI
  class Base < Phlex::HTML
    attr_reader :attrs

    def initialize(**user_attrs)
      @attrs         = PhlexUI::AttributeMerger.new(default_attrs, user_attrs).call
      @attrs[:class] = ::TailwindMerge::Merger.new.merge(@attrs[:class]) if @attrs[:class]
    end

    if defined?(Rails) && Rails.env.development?
      def before_template
        comment { "Before #{self.class.name}" }
        super
      end
    end

    private

    def default_attrs
      {class: default_classes}
    end

    # Always use array for class list
    # Lets us just push new items w/o messing with string concatenation and space preservation
    # Additionally we split attrs and classes. Attrs rarely customized on class-level, but classes are constantly
    def default_classes
      []
    end
  end
end

This semi-draft small code change lets me have components with no further customization needed (though still supported) like so:

module UI
  module Components
    class TypographyH1 < PhlexUI::TypographyH1
      CLASSES = %w[dark:text-slate-200].freeze
      private def default_classes = super + CLASSES
    end
  end
end

Existing PhlexUI components can be mostly updated with regexp

(class:)\s+"([^"]+)"
$1 %w[$2]

NB: as you probably noticed, I personally prefer to have classes in array for readability and ease of management. Compare these two snippets (TabsStrigger classes):

"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow"

and

%w[
 inline-flex
 items-center
 justify-center
 whitespace-nowrap
 rounded-md
 px-3
 py-1
 text-sm
 font-medium
 ring-offset-background
 transition-all
 focus-visible:outline-none
 focus-visible:ring-2
 focus-visible:ring-ring
 focus-visible:ring-offset-2
 disabled:pointer-events-none
 disabled:opacity-50
 data-[state=active]:bg-background
 data-[state=active]:text-foreground
 data-[state=active]:shadow]

Thank you again for your work! :)

cirdes commented 1 month ago

@qd3v, we now have a better way to do this. You can eject a component from the library and customize it. Take a look here