Module: Shadcn::Rails

Defined in:
lib/shadcn/rails.rb,
lib/shadcn/rails/engine.rb,
lib/shadcn/rails/version.rb,
lib/shadcn/rails/class_merger.rb,
lib/shadcn/rails/configuration.rb,
lib/shadcn/rails/helpers/component_helper.rb,
lib/shadcn/rails/helpers/class_name_helper.rb,
lib/shadcn/rails/helpers/pagination_helper.rb

Defined Under Namespace

Modules: Helpers Classes: ClassMerger, Configuration, Engine, Error

Constant Summary collapse

VERSION =
"0.2.1"

Class Method Summary collapse

Class Method Details

.available_componentsObject

List all available components



69
70
71
72
73
# File 'lib/shadcn/rails.rb', line 69

def available_components
  @available_components ||= Dir[File.join(__dir__, "../../app/components/shadcn/*_component.rb")].map do |file|
    File.basename(file, "_component.rb").to_sym
  end
end

.cn(*args) ⇒ String

Shorthand for the cn() class merger Uses tailwind_merge gem if available, falls back to custom ClassMerger

Parameters:

  • args (Array)

    Classes to merge (strings, hashes, arrays, or nil)

Returns:

  • (String)

    Merged class string with conflicts resolved



141
142
143
144
145
146
147
148
149
150
151
# File 'lib/shadcn/rails.rb', line 141

def cn(*args)
  # Flatten and filter the arguments first
  classes = flatten_class_args(args)
  class_string = classes.join(" ")

  if tailwind_merger
    tailwind_merger.merge(class_string)
  else
    ClassMerger.merge(*args)
  end
end

.component_for(name) ⇒ Class

Get a component class by name Supports aliases defined in configuration

Parameters:

  • name (Symbol, String)

    Component name

Returns:

  • (Class)

    Component class



53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/shadcn/rails.rb', line 53

def component_for(name)
  name = name.to_sym
  return configuration.component_aliases[name] if configuration.component_aliases.key?(name)

  # Validate name contains only lowercase letters and underscores (security hardening)
  unless name.to_s.match?(/\A[a-z_]+\z/)
    raise Error, "Invalid component name format: #{name}. Names must contain only lowercase letters and underscores."
  end

  component_name = "Shadcn::#{name.to_s.camelize}Component"
  component_name.constantize
rescue NameError
  raise Error, "Unknown component: #{name}. Available components: #{available_components.join(', ')}"
end

.configurationObject

Access the configuration



28
29
30
# File 'lib/shadcn/rails.rb', line 28

def configuration
  @configuration ||= Configuration.new
end

.configure {|Configuration| ... } ⇒ Object

Configure the gem

Yields:



39
40
41
# File 'lib/shadcn/rails.rb', line 39

def configure
  yield(configuration)
end

.css_variables(theme: :light) ⇒ Object

Generate CSS variables for the current theme



76
77
78
79
80
81
82
83
84
# File 'lib/shadcn/rails.rb', line 76

def css_variables(theme: :light)
  vars = if theme == :dark
    Configuration::DARK_THEME_VARIABLES[configuration.base_color.to_sym] || {}
  else
    configuration.base_variables
  end

  vars.map { |key, value| "--#{key.to_s.tr('_', '-')}: #{value};" }.join("\n  ")
end

.reset_configuration!Object

Reset configuration to defaults



44
45
46
# File 'lib/shadcn/rails.rb', line 44

def reset_configuration!
  @configuration = Configuration.new
end

.tailwind_mergerObject

TailwindMerge instance (singleton)



33
34
35
# File 'lib/shadcn/rails.rb', line 33

def tailwind_merger
  @tailwind_merger ||= TailwindMerge::Merger.new if TAILWIND_MERGE_AVAILABLE
end

.theme_cssObject

Generate complete CSS for themes Supports three dark mode strategies:

  • :media - uses @media (prefers-color-scheme: dark) for automatic detection
  • :class - uses .dark class selector (default for manual toggling)
  • :both - includes both media query AND .dark class for maximum flexibility


91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/shadcn/rails.rb', line 91

def theme_css
  light_vars = css_variables(theme: :light)
  dark_vars = css_variables(theme: :dark)
  mode = configuration.dark_mode

  css = <<~CSS
    :root {
      #{light_vars}
    }
  CSS

  case mode
  when :media
    css += <<~CSS

      @media (prefers-color-scheme: dark) {
        :root {
          #{dark_vars}
        }
      }
    CSS
  when :both
    css += <<~CSS

      @media (prefers-color-scheme: dark) {
        :root {
          #{dark_vars}
        }
      }

      .dark {
        #{dark_vars}
      }
    CSS
  else # :class (default)
    css += <<~CSS

      .dark {
        #{dark_vars}
      }
    CSS
  end

  css
end