Module: Hibiki::Rails::Generators::CssVariant
- Defined in:
- lib/generators/hibiki/rails/css_variant.rb
Overview
The class strings each --css variant stamps into generated views.
Substitution happens at GENERATION time, not at render time. Under
--css=none the emitted views carry no class attribute at all — not an
empty one — which a runtime class="<%= css(:card) %>" could not
produce. That is why templates guard the whole argument line rather
than interpolating into it.
Transcribed from three hand-built, browser-verified variants in ~/rails_projects/hibiki-crud: 8df15b9 (daisyui), 0bc0319 (tailwind), 51de1e9 (none). 10 of the 11 views differ only in these strings; the page control is the one component whose STRUCTURE differs, and it has per-variant templates instead.
Constant Summary collapse
- SECONDARY_BUTTON =
Plain-Tailwind component strings, named once because they repeat. DaisyUI's own classes are already abstractions, which is why only this side needs the constants.
"inline-flex items-center justify-center rounded-md border " \ "border-gray-300 bg-white px-3 py-2 text-sm font-medium " \ "text-gray-700 shadow-sm hover:bg-gray-50"
- PRIMARY_BUTTON =
"inline-flex items-center justify-center rounded-md border " \ "border-transparent bg-indigo-600 px-3 py-2 text-sm font-semibold " \ "text-white shadow-sm hover:bg-indigo-500"
- WARNING_BUTTON =
"inline-flex items-center justify-center rounded-md border " \ "border-transparent bg-amber-500 px-3 py-2 text-sm font-semibold " \ "text-white shadow-sm hover:bg-amber-400"
- FIELD =
"rounded-md border border-gray-300 px-3 py-2 text-sm shadow-sm " \ "focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
- FIELD_FULL =
"block w-full #{FIELD}".freeze
- CHECKBOX =
"h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"- DAISYUI =
The canonical token set. Every token a template may ask for is a key here, which is what makes a typo in a template raise instead of silently emitting nothing.
{ # Layout and spacing. These are plain Tailwind utilities already, so # the tailwind variant inherits them unchanged — which is the whole # reason TAILWIND is a merge rather than a second literal map. page: "max-w-4xl mx-auto mb-6", heading: "text-2xl font-bold my-8", actions_bar: "my-6 flex justify-end", actions_bar_spaced: "my-6 flex justify-end space-x-2", controls: "flex flex-wrap gap-2 items-end mb-4", list: "space-y-3", load_more_wrap: "pt-2 flex justify-center", row_form: "space-y-2", checkbox_wrap: "my-4", form_actions: "flex gap-2 items-center", form: "max-w-md mx-auto", # Components. card: "card bg-base-100 shadow-sm", card_body: "card-body", card_actions: "card-actions justify-end", toggle: "toggle", toggle_sm: "toggle toggle-sm", btn: "btn", # NOTE: btn-default is not a DaisyUI class — it is inert. Kept # verbatim because the reference app has it and this map's job is to # reproduce what was measured; drop it here if it should not travel # into new apps. btn_default: "btn btn-default", btn_outline: "btn btn-outline", btn_primary: "btn btn-primary", btn_warning: "btn btn-warning", btn_ghost: "btn btn-ghost", btn_wide: "btn btn-outline btn-wide", fieldset: "fieldset border border-gray-300 p-4 mb-4", label: "label", label_text: "label-text", form_control: "form-control", input: "input", input_full: "input w-full", select: "select", select_full: "select w-full", textarea_full: "textarea w-full", badge_warning: "badge badge-warning", # The LOOK of a spinner, never its visibility — showing and hiding # it is an attribute-descendant rule, which no class token can # express, so it lives in the transport stylesheet instead. Absent # under --css=none, where that stylesheet hand-rolls the ring too. spinner: "loading loading-spinner loading-xs", warning_text: "text-warning", error_text: "text-error text-sm mt-1", muted: "opacity-60 italic", muted_inline: "opacity-60", counts: "ml-auto text-sm opacity-70", pagination_nav: "join mt-2 mx-auto flex justify-center w-fit" }.freeze
- TAILWIND =
DaisyUI is a plugin over Tailwind, and the merge says so literally: only the component tokens are overridden, and every layout utility falls through untouched.
DAISYUI.merge( card: "rounded-lg border border-gray-200 bg-white shadow-sm", card_body: "p-6 space-y-2", card_actions: "flex flex-wrap justify-end gap-2 pt-4", toggle: CHECKBOX, toggle_sm: CHECKBOX, btn: SECONDARY_BUTTON, btn_default: SECONDARY_BUTTON, btn_outline: SECONDARY_BUTTON, btn_primary: PRIMARY_BUTTON, btn_warning: WARNING_BUTTON, btn_ghost: SECONDARY_BUTTON, btn_wide: SECONDARY_BUTTON.sub("px-3", "px-12"), fieldset: "rounded-md border border-gray-300 p-4 mb-4", label: "block text-sm font-medium text-gray-700 mt-3", label_text: "text-sm font-medium text-gray-700", form_control: "flex flex-col gap-1", input: FIELD, input_full: FIELD_FULL, select: FIELD, select_full: FIELD_FULL, textarea_full: "#{FIELD_FULL} min-h-20", badge_warning: "inline-flex items-center rounded-full bg-amber-100 px-2 py-1 " \ "text-xs font-medium text-amber-800", # `inline-block` is load-bearing, not decoration: a bare span is # inline, where h-3/w-3 do nothing. It is safe against the # visibility rules because this stylesheet is linked in <head> and # the transport <style> is in the body, so equal specificity goes # hibiki's way. spinner: "inline-block h-3 w-3 animate-spin rounded-full border-2 " \ "border-current border-r-transparent align-[-0.15em]", warning_text: "text-amber-600", error_text: "text-red-600 text-sm mt-1", muted: "text-gray-500 italic", muted_inline: "text-gray-400", counts: "ml-auto text-sm text-gray-500", pagination_nav: "mt-2 mx-auto flex justify-center w-fit -space-x-px rounded-md shadow-sm" ).freeze
- NONE =
Every lookup misses, so every class argument is omitted entirely.
{}.freeze
- VARIANTS =
{ daisyui: DAISYUI, tailwind: TAILWIND, none: NONE }.freeze
- NAMES =
VARIANTS.keys.map(&:to_s).freeze
Class Method Summary collapse
-
.detect(root) ⇒ Object
A grep, not a dependency lookup: DaisyUI is a CSS-only plugin under Tailwind 4, so it appears in package.json and in the entry stylesheet but never in the Gemfile.
-
.token(variant, name) ⇒ Object
Validated against DAISYUI rather than the active variant, so a mistyped token raises under --css=none too — where it would otherwise look exactly like a token that is meant to be absent.
Class Method Details
.detect(root) ⇒ Object
A grep, not a dependency lookup: DaisyUI is a CSS-only plugin under Tailwind 4, so it appears in package.json and in the entry stylesheet but never in the Gemfile.
cssbundling-rails is deliberately NOT a signal — it bundles Bootstrap and Sass just as happily, and guessing tailwind from it would restyle an app that has no Tailwind at all.
161 162 163 164 165 166 |
# File 'lib/generators/hibiki/rails/css_variant.rb', line 161 def detect(root) return :daisyui if daisyui?(root) return :tailwind if tailwind?(root) :none end |
.token(variant, name) ⇒ Object
Validated against DAISYUI rather than the active variant, so a mistyped token raises under --css=none too — where it would otherwise look exactly like a token that is meant to be absent.
148 149 150 151 152 |
# File 'lib/generators/hibiki/rails/css_variant.rb', line 148 def token(variant, name) raise ArgumentError, "unknown css token #{name.inspect}" unless DAISYUI.key?(name) VARIANTS.fetch(variant.to_sym)[name] end |