-n primer_rails_forms https://github.com/primer/rails_forms.git
324 | ```
325 |
326 | **NOTE**: The `SOURCE_DATE_EPOCH=0` environment variable is important. It causes Rubygems to use a value of zero as the current timestamp in the gzip header of the resulting .gem file. If omitted, the .gem file will appear to have changed even though none of the files inside it have changed. This is inconvenient because it can cause unexpected merge conflicts and prevent shipping two branches that depend on the same version of primer_rails_forms.
327 |
328 | If you're using primer/rails_forms outside of dotcom, add it directly to your Gemfile.
329 |
330 | ```ruby
331 | gem "primer_rails_forms", github: "primer/rails_forms"
332 | ```
333 |
334 | ## Running the Lookbook app
335 |
336 | Requirements:
337 |
338 | 1. Ruby v3.0
339 | 1. Node v16
340 | 1. Yarn (`npm install yarn`)
341 |
342 | To run the application:
343 |
344 | 1. Change into the lookbook directory.
345 | 1. Run `bundle install` followed by `yarn install`.
346 | 1. Run bin/dev
347 | 1. Visit http://localhost:3000 in your browser
348 | 1. Profit
349 |
350 | ### Codespaces
351 |
352 | This repo includes the necessary configuration to enable developing locally and running the Lookbook app in a codespace. It also includes a local checkout of [primer/css](https://github.com/primer/css). Changes to the local copy of primer/css are immediately reflected in Lookbook.
353 |
354 | If you're using Visual Studio Code, open .vscode/rails-forms-workspace.code-workspace and click the "Open Workspace" button. You should see two folders in the explorer, rails_forms and @primer/css.
355 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "rake/testtask"
4 |
5 | Rake::TestTask.new(:test) do |t|
6 | t.libs << "test"
7 | t.libs << "lib"
8 | t.test_files = FileList["test/**/*_test.rb"]
9 | end
10 |
--------------------------------------------------------------------------------
/bin/test:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env ruby
2 |
3 | # frozen_string_literal: true
4 |
5 | require "bundler/setup"
6 | require "rails/command"
7 |
8 | ENGINE_ROOT = File.join(File.dirname(__dir__), "test")
9 | $LOAD_PATH.push(ENGINE_ROOT)
10 | Rails::Command.invoke "test", ARGV
11 |
--------------------------------------------------------------------------------
/lib/primer/form_components.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | class FormComponents
5 | def self.from_input(input_klass)
6 | Class.new(ViewComponent::Base) do
7 | @input_klass = input_klass
8 |
9 | class << self
10 | attr_reader :input_klass
11 | end
12 |
13 | def initialize(**kwargs, &block)
14 | @kwargs = kwargs
15 | @block = block
16 | end
17 |
18 | def call
19 | builder = ActionView::Helpers::FormBuilder.new(nil, nil, self, {})
20 |
21 | input = self.class.input_klass.new(
22 | builder: builder,
23 | form: nil,
24 | **@kwargs,
25 | &@block
26 | )
27 |
28 | input.render_in(self) { content }
29 | end
30 | end
31 | end
32 | end
33 |
34 | # These components are designed to be used outside the context of a form object.
35 | # They can be rendered just like any other View Component and accept the same
36 | # arguments as the form input they wrap.
37 | #
38 | # Eg:
39 | #
40 | # render(
41 | # Primer::TextField.new(
42 | # name: "foo",
43 | # label: "Foo",
44 | # caption: "Something about foos"
45 | # )
46 | # )
47 | #
48 | TextField = FormComponents.from_input(Primer::RailsForms::Dsl::TextFieldInput)
49 | end
50 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "primer/rails_forms/version"
4 | require "primer/rails_forms/engine"
5 |
6 | module Primer
7 | module RailsForms
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lib/primer/rails_forms/.DS_Store
--------------------------------------------------------------------------------
/lib/primer/rails_forms/acts_as_component.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | module ActsAsComponent
6 | module InstanceMethods
7 | delegate :render, :content_tag, :output_buffer, :capture, to: :@view_context
8 |
9 | def render_in(view_context, &block)
10 | @view_context = view_context
11 | before_render
12 | perform_render(&block)
13 | end
14 |
15 | def perform_render(&_block)
16 | raise NotImplementedError, "subclasses must implement ##{__method__}."
17 | end
18 |
19 | def before_render; end
20 |
21 | # rubocop:disable Naming/AccessorMethodName
22 | def set_original_view_context(view_context)
23 | @view_context = view_context
24 | end
25 | # rubocop:enable Naming/AccessorMethodName
26 | end
27 |
28 | def self.extended(base)
29 | base.include(InstanceMethods)
30 | end
31 |
32 | TemplateGlob = Struct.new(:glob_pattern, :method_name, :on_compile_callback)
33 | TemplateParams = Struct.new(:source, :identifier, :type, :format)
34 |
35 | attr_accessor :template_root_path
36 |
37 | def renders_templates(glob_pattern, method_name = nil, &block)
38 | template_globs << TemplateGlob.new(glob_pattern, method_name, block)
39 | end
40 | alias renders_template renders_templates
41 |
42 | def compile!
43 | # always recompile in dev
44 | return if defined?(@compiled) && @compiled && !Rails.env.development?
45 |
46 | template_globs.each do |template_glob|
47 | compile_templates_in(template_glob)
48 | end
49 |
50 | @compiled = true
51 | end
52 |
53 | private
54 |
55 | def template_globs
56 | @template_globs ||= []
57 | end
58 |
59 | def compile_templates_in(template_glob)
60 | pattern = if File.absolute_path?(template_glob.glob_pattern)
61 | template_glob.glob_pattern
62 | else
63 | # skip compilation for anonymous form classes, as in tests
64 | return unless template_root_path
65 |
66 | File.join(template_root_path, template_glob.glob_pattern)
67 | end
68 |
69 | template_paths = Dir.glob(pattern)
70 |
71 | raise "Cannot compile multiple templates with the same method name." if template_paths.size > 1 && template_glob.method_name
72 |
73 | template_paths.each do |template_path|
74 | method_name = template_glob.method_name
75 | method_name ||= "render_#{File.basename(template_path).chomp('.html.erb')}"
76 | define_template_method(template_path, method_name)
77 | template_glob&.on_compile_callback&.call(template_path)
78 | end
79 | end
80 |
81 | def define_template_method(template_path, method_name)
82 | # rubocop:disable Style/DocumentDynamicEvalDefinition
83 | # rubocop:disable Style/EvalWithLocation
84 | class_eval <<-RUBY, template_path, 0
85 | private def #{method_name}
86 | capture { #{compile_template(template_path)} }
87 | end
88 | RUBY
89 | # rubocop:enable Style/EvalWithLocation
90 | # rubocop:enable Style/DocumentDynamicEvalDefinition
91 | end
92 |
93 | def compile_template(path)
94 | handler = ActionView::Template.handler_for_extension("erb")
95 | template = File.read(path)
96 | template_params = TemplateParams.new({
97 | source: template,
98 | identifier: __FILE__,
99 | type: "text/html",
100 | format: "text/html"
101 | })
102 |
103 | # change @output_buffer ivar to output_buffer method call
104 | BufferRewriter.rewrite(
105 | handler.call(template_params, template)
106 | )
107 | end
108 | end
109 | end
110 | end
111 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/base.html.erb:
--------------------------------------------------------------------------------
1 | <%= render(SpacingWrapper.new) do %>
2 | <% inputs.each do |input| %>
3 | <%= render(input) %>
4 | <% end %>
5 | <% end %>
6 | <% if after_content? %>
7 | <%= render_after_content %>
8 | <% end %>
9 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/base.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | class Base
6 | extend ActsAsComponent
7 |
8 | renders_template File.join(__dir__, "base.html.erb"), :render_base_form
9 |
10 | class << self
11 | attr_reader :has_after_content, :__vcf_form_block, :__vcf_builder
12 | alias after_content? has_after_content
13 |
14 | def form(&block)
15 | @__vcf_form_block = block
16 | end
17 |
18 | def new(builder, **options)
19 | allocate.tap do |form|
20 | form.instance_variable_set(:@builder, builder)
21 | form.send(:initialize, **options)
22 | end
23 | end
24 |
25 | def inherited(base)
26 | form_path = const_source_location(base.name)
27 | return unless form_path
28 |
29 | base.template_root_path = File.join(File.dirname(form_path), base.name.demodulize.underscore)
30 |
31 | base.renders_template "after_content.html.erb" do
32 | base.instance_variable_set(:@has_after_content, true)
33 | end
34 |
35 | base.renders_templates "*_caption.html.erb" do |path|
36 | base.fields_with_caption_templates << File.basename(path).chomp("_caption.html.erb").to_sym
37 | end
38 | end
39 |
40 | def caption_template?(field_name)
41 | fields_with_caption_templates.include?(field_name)
42 | end
43 |
44 | def fields_with_caption_templates
45 | @fields_with_caption_templates ||= []
46 | end
47 |
48 | private
49 |
50 | # Unfortunately this bug (https://github.com/ruby/ruby/pull/5646) prevents us from using
51 | # Ruby's native Module.const_source_location. Instead we have to fudge it by searching
52 | # for the file in the configured autoload paths. Doing so relies on Rails' autoloading
53 | # conventions, so it should work ok. Zeitwerk also has this information but lacks a
54 | # public API to map constants to source files.
55 | def const_source_location(class_name)
56 | # NOTE: underscore respects namespacing, i.e. will convert Foo::Bar to foo/bar.
57 | class_path = "#{class_name.underscore}.rb"
58 |
59 | ActiveSupport::Dependencies.autoload_paths.each do |autoload_path|
60 | absolute_path = File.join(autoload_path, class_path)
61 | return absolute_path if File.exist?(absolute_path)
62 | end
63 |
64 | nil
65 | end
66 | end
67 |
68 | def inputs
69 | @inputs ||= form_object.inputs.map do |input|
70 | next input unless input.input?
71 |
72 | # wrap inputs in a group (unless they are already groups)
73 | if input.type == :group
74 | input
75 | else
76 | Primer::RailsForms::Dsl::InputGroup.new(builder: @builder, form: self) do |group|
77 | group.send(:add_input, input)
78 | end
79 | end
80 | end
81 | end
82 |
83 | def each_input_in(root_input, &block)
84 | return enum_for(__method__, root_input) unless block
85 |
86 | root_input.inputs.each do |input|
87 | if input.respond_to?(:inputs)
88 | each_input_in(input, &block)
89 | else
90 | yield input
91 | end
92 | end
93 | end
94 |
95 | def before_render
96 | each_input_in(self) do |input|
97 | if input.input? && input.invalid? && input.focusable?
98 | input.autofocus!
99 | break
100 | end
101 | end
102 | end
103 |
104 | def caption_template?(*args)
105 | self.class.caption_template?(*args)
106 | end
107 |
108 | def after_content?(*args)
109 | self.class.after_content?(*args)
110 | end
111 |
112 | def render_caption_template(name)
113 | send(:"render_#{name}_caption")
114 | end
115 |
116 | def perform_render(&_block)
117 | Base.compile!
118 | self.class.compile!
119 |
120 | render_base_form
121 | end
122 |
123 | private
124 |
125 | def form_object
126 | # rubocop:disable Naming/MemoizedInstanceVariableName
127 | @__vcf_form_object ||= Primer::RailsForms::Dsl::FormObject.new(builder: @builder, form: self).tap do |obj|
128 | # compile before adding inputs so caption templates are identified
129 | self.class.compile!
130 | instance_exec(obj, &self.class.__vcf_form_block)
131 | end
132 | # rubocop:enable Naming/MemoizedInstanceVariableName
133 | end
134 | end
135 | end
136 | end
137 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/base_component.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "primer/class_name_helper"
4 |
5 | module Primer
6 | module RailsForms
7 | class BaseComponent
8 | include Primer::ClassNameHelper
9 | extend ActsAsComponent
10 |
11 | def self.inherited(base)
12 | base.renders_template File.join(__dir__, "#{base.name.demodulize.underscore}.html.erb"), :render_template
13 | end
14 |
15 | delegate :required?, :disabled?, :hidden?, to: :@input
16 |
17 | def perform_render(&block)
18 | @__prf_content_block = block
19 | compile_and_render_template
20 | end
21 |
22 | def content
23 | return @__prf_content if defined?(@__prf_content_evaluated) && @__prf_content_evaluated
24 |
25 | @__prf_content_evaluated = true
26 | @__prf_content = capture do
27 | @__prf_content_block.call
28 | end
29 | end
30 |
31 | def type
32 | :component
33 | end
34 |
35 | def input?
36 | false
37 | end
38 |
39 | private
40 |
41 | def compile_and_render_template
42 | self.class.compile! unless self.class.instance_methods(false).include?(:render_template)
43 | render_template
44 | end
45 |
46 | def content_tag_if(condition, tag, **kwargs, &block)
47 | if condition
48 | content_tag(tag, **kwargs, &block)
49 | else
50 | capture(&block)
51 | end
52 | end
53 |
54 | def content_tag_if_args(tag, **kwargs, &block)
55 | if kwargs.empty?
56 | capture(&block)
57 | else
58 | content_tag(tag, **kwargs, &block)
59 | end
60 | end
61 | end
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/buffer_rewriter.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "ripper"
4 |
5 | module Primer
6 | module RailsForms
7 | class BufferRewriter < Ripper
8 | class << self
9 | def rewrite(code)
10 | parser = new(code, "(code)", 0)
11 | parser.parse
12 |
13 | line_offsets = calc_line_offsets(code)
14 |
15 | code.dup.tap do |result|
16 | parser.var_refs.reverse_each do |lineno, stop|
17 | line_offset = line_offsets[lineno]
18 | start = (stop - "@output_buffer".length) + line_offset
19 | stop += line_offset
20 | result[start...stop] = "output_buffer"
21 | end
22 | end
23 | end
24 |
25 | private
26 |
27 | def calc_line_offsets(code)
28 | idx = -1
29 |
30 | [0].tap do |offsets|
31 | while (idx = code.index(/\r?\n/, idx + 1))
32 | offsets << Regexp.last_match.end(0)
33 | end
34 | end
35 | end
36 | end
37 |
38 | def on_var_ref(var)
39 | return unless var == "@output_buffer"
40 |
41 | var_refs << [lineno, column - 1]
42 | end
43 |
44 | def var_refs
45 | @var_refs ||= []
46 | end
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/caption.html.erb:
--------------------------------------------------------------------------------
1 | <% if @input.caption? && !@input.caption.blank? %>
2 | <%= @input.caption %>
3 | <% elsif caption_template? %>
4 | <% caption_template = render_caption_template %>
5 | <% unless caption_template.blank? %>
6 |
7 | <%= caption_template %>
8 |
9 | <% end %>
10 | <% end %>
11 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/caption.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | class Caption < BaseComponent
6 | def initialize(input:)
7 | @input = input
8 | end
9 |
10 | def caption_template?
11 | @input.caption_template?
12 | end
13 |
14 | def render_caption_template
15 | @input.render_caption_template
16 | end
17 |
18 | def before_render
19 | return unless @input.caption? && caption_template?
20 |
21 | raise <<~MESSAGE
22 | Please provide either a caption: argument or caption template for the
23 | '#{@input.name}' input; both were found.
24 | MESSAGE
25 | end
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/check_box.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= builder.check_box(@input.name, **@input.input_arguments) %>
3 |
4 | <%= builder.label(@input.name, **@input.label_arguments) do %>
5 | <%= @input.label %>
6 | <% end %>
7 | <%= render(Caption.new(input: @input)) %>
8 |
9 |
10 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/check_box.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | class CheckBox < BaseComponent
6 | delegate :builder, :form, to: :@input
7 |
8 | def initialize(input:)
9 | @input = input
10 | @input.add_label_classes("FormControl-label")
11 | @input.add_input_classes("FormControl-checkbox")
12 | end
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/check_box_group.html.erb:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/check_box_group.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | class CheckBoxGroup < BaseComponent
6 | delegate :builder, :form, to: :@input
7 |
8 | def initialize(input:)
9 | @input = input
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/dsl/check_box_group_input.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | module Dsl
6 | class CheckBoxGroupInput < Input
7 | attr_reader :label, :check_boxes
8 |
9 | def initialize(label: nil, **system_arguments)
10 | @label = label
11 | @check_boxes = []
12 |
13 | super(**system_arguments)
14 |
15 | add_label_classes("FormControl-label", "mb-2")
16 |
17 | yield(self) if block_given?
18 | end
19 |
20 | def to_component
21 | CheckBoxGroup.new(input: self)
22 | end
23 |
24 | def name
25 | nil
26 | end
27 |
28 | def type
29 | :check_box_group
30 | end
31 |
32 | def check_box(**system_arguments)
33 | @check_boxes << CheckBoxInput.new(
34 | builder: @builder, form: @form, **system_arguments
35 | )
36 | end
37 | end
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/dsl/check_box_input.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | module Dsl
6 | class CheckBoxInput < Input
7 | attr_reader :name, :label
8 |
9 | def initialize(name:, label:, **system_arguments)
10 | @name = name
11 | @label = label
12 |
13 | super(**system_arguments)
14 | end
15 |
16 | def to_component
17 | CheckBox.new(input: self)
18 | end
19 |
20 | def type
21 | :check_box
22 | end
23 | end
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/dsl/form_object.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | module Dsl
6 | class FormObject
7 | include InputMethods
8 |
9 | attr_reader :builder, :form
10 |
11 | def initialize(builder:, form:)
12 | @builder = builder
13 | @form = form
14 |
15 | yield(self) if block_given?
16 | end
17 |
18 | def group(**options, &block)
19 | add_input InputGroup.new(builder: @builder, form: @form, **options, &block)
20 | end
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/dsl/form_reference_input.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | module Dsl
6 | class FormReferenceInput < Input
7 | attr_reader :ref_block, :fields_for_args, :fields_for_kwargs
8 |
9 | def initialize(*fields_for_args, builder:, form:, **fields_for_kwargs, &block)
10 | @fields_for_args = fields_for_args
11 | @fields_for_kwargs = fields_for_kwargs
12 | @ref_block = block
13 |
14 | super(builder: builder, form: form, **fields_for_kwargs)
15 | end
16 |
17 | def to_component
18 | FormReference.new(input: self)
19 | end
20 |
21 | def name
22 | nil
23 | end
24 |
25 | def label
26 | nil
27 | end
28 |
29 | def type
30 | :form
31 | end
32 | end
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/dsl/hidden_input.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | module Dsl
6 | class HiddenInput < Input
7 | attr_reader :name
8 |
9 | def initialize(name:, **system_arguments)
10 | @name = name
11 | super(**system_arguments)
12 | end
13 |
14 | def to_component
15 | HiddenField.new(input: self)
16 | end
17 |
18 | def label
19 | nil
20 | end
21 |
22 | def type
23 | :hidden
24 | end
25 | end
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/dsl/input.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | module Dsl
6 | class Input
7 | SPACE_DELIMITED_ARIA_ATTRIBUTES = %i[describedby].freeze
8 | DEFAULT_SIZE = :medium
9 | SIZE_MAPPINGS = {
10 | :small => "FormControl-small",
11 | DEFAULT_SIZE => "FormControl-medium",
12 | :large => "FormControl-large"
13 | }.freeze
14 | SIZE_OPTIONS = SIZE_MAPPINGS.keys
15 |
16 | include Primer::ClassNameHelper
17 |
18 | attr_reader :builder, :form, :input_arguments, :label_arguments, :caption, :validation_message, :ids
19 |
20 | def initialize(builder:, form:, **system_arguments)
21 | @builder = builder
22 | @form = form
23 |
24 | @input_arguments = system_arguments
25 |
26 | @input_arguments[:class] = class_names(
27 | @input_arguments.delete(:class),
28 | @input_arguments.delete(:classes)
29 | )
30 |
31 | @label_arguments = @input_arguments.delete(:label_arguments) || {}
32 | @label_arguments[:class] = class_names(
33 | @label_arguments.delete(:class),
34 | @label_arguments.delete(:classes),
35 | @input_arguments.fetch(:show_label, true) ? nil : "sr-only"
36 | )
37 |
38 | @input_arguments.delete(:show_label)
39 | @input_arguments.delete(:class) if @input_arguments[:class].blank?
40 | @label_arguments.delete(:class) if @label_arguments[:class].blank?
41 |
42 | @caption = @input_arguments.delete(:caption)
43 | @validation_message = @input_arguments.delete(:validation_message)
44 | @invalid = @input_arguments.delete(:invalid)
45 | @full_width = @input_arguments.delete(:full_width)
46 | @size = @input_arguments.delete(:size)
47 |
48 | @input_arguments[:invalid] = "true" if invalid?
49 |
50 | base_id = SecureRandom.hex[0..5]
51 |
52 | @ids = {}.tap do |id_map|
53 | id_map[:validation] = "validation-#{base_id}" if invalid?
54 | id_map[:caption] = "caption-#{base_id}" if caption? || caption_template?
55 | end
56 |
57 | add_input_aria(:required, true) if required?
58 | add_input_aria(:describedby, ids.values) if ids.any?
59 |
60 | # avoid browser-native validation, which doesn't match Primer's style
61 | input_arguments.delete(:required)
62 | end
63 |
64 | def add_input_classes(*class_names)
65 | input_arguments[:class] = class_names(
66 | input_arguments[:class], *class_names
67 | )
68 | end
69 |
70 | def add_label_classes(*class_names)
71 | label_arguments[:class] = class_names(
72 | label_arguments[:class], *class_names
73 | )
74 | end
75 |
76 | def add_input_aria(key, value)
77 | @input_arguments[:aria] ||= {}
78 |
79 | @input_arguments[:aria][key] = if space_delimited_aria_attribute?(key)
80 | aria_join(@input_arguments[:aria][key], *Array(value))
81 | else
82 | value
83 | end
84 | end
85 |
86 | def add_input_data(key, value)
87 | input_data[key] = value
88 | end
89 |
90 | def remove_input_data(key)
91 | input_data.delete(key)
92 | end
93 |
94 | def merge_input_arguments!(arguments)
95 | arguments.each do |k, v|
96 | case k
97 | when :class, :classes, "class", "classes"
98 | add_input_classes(v)
99 | when :aria, "aria"
100 | v.each do |aria_k, aria_v|
101 | add_input_aria(aria_k, aria_v)
102 | end
103 | when :data, "data"
104 | v.each do |data_k, data_v|
105 | add_input_data(data_k, data_v)
106 | end
107 | else
108 | @input_arguments[k] = v
109 | end
110 | end
111 | end
112 |
113 | def validation_id
114 | ids[:validation]
115 | end
116 |
117 | def caption_id
118 | ids[:caption]
119 | end
120 |
121 | def caption?
122 | caption.present?
123 | end
124 |
125 | def caption_template?
126 | return false unless form
127 |
128 | form.caption_template?(caption_template_name)
129 | end
130 |
131 | def render_caption_template
132 | form.render_caption_template(caption_template_name)
133 | end
134 |
135 | def valid?
136 | validation_messages.empty? && !@invalid
137 | end
138 |
139 | def invalid?
140 | !valid?
141 | end
142 |
143 | def hidden?
144 | !!input_arguments[:hidden]
145 | end
146 |
147 | def required?
148 | input_arguments[:required] ||
149 | input_arguments[:aria_required] ||
150 | input_arguments[:"aria-required"] ||
151 | input_arguments.dig(:aria, :required)
152 | end
153 |
154 | def disabled?
155 | input_arguments.include?(:disabled)
156 | end
157 |
158 | def full_width?
159 | @full_width
160 | end
161 |
162 | def size
163 | @size ||= SIZE_MAPPINGS.include?(@size) ? @size : DEFAULT_SIZE
164 | end
165 |
166 | def validation_messages
167 | @validation_messages ||=
168 | if validation_message
169 | [validation_message]
170 | elsif builder.object.respond_to?(:errors)
171 | name ? builder.object.errors.full_messages_for(name) : []
172 | else
173 | []
174 | end
175 | end
176 |
177 | def autofocus!
178 | input_arguments[:autofocus] = true
179 | end
180 |
181 | def name
182 | raise_for_abstract_method!(__method__)
183 | end
184 |
185 | def label
186 | raise_for_abstract_method!(__method__)
187 | end
188 |
189 | def type
190 | raise_for_abstract_method!(__method__)
191 | end
192 |
193 | def to_component
194 | raise_for_abstract_method!(__method__)
195 | end
196 |
197 | def focusable?
198 | false
199 | end
200 |
201 | def input?
202 | true
203 | end
204 |
205 | # Avoid using Rails delegation here for performance reasons
206 | # rubocop:disable Rails/Delegate
207 | def render_in(view_context)
208 | to_component.render_in(view_context)
209 | end
210 | # rubocop:enable Rails/Delegate
211 |
212 | private
213 |
214 | def input_data
215 | @input_arguments[:data] ||= {}
216 | end
217 |
218 | def caption_template_name
219 | return nil unless name
220 |
221 | @caption_template_name ||= if respond_to?(:value)
222 | :"#{name}_#{value}"
223 | else
224 | name.to_sym
225 | end
226 | end
227 |
228 | def space_delimited_aria_attribute?(attrib)
229 | SPACE_DELIMITED_ARIA_ATTRIBUTES.include?(attrib)
230 | end
231 |
232 | def aria_join(*values)
233 | values = values.flat_map { |v| v.to_s.split }
234 | values.reject!(&:empty?)
235 | values.join(" ")
236 | end
237 |
238 | def raise_for_abstract_method!(method_name)
239 | raise NotImplementedError, "subclasses must implement ##{method_name}."
240 | end
241 | end
242 | end
243 | end
244 | end
245 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/dsl/input_group.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | module Dsl
6 | class InputGroup
7 | include InputMethods
8 |
9 | attr_reader :builder, :form, :system_arguments
10 |
11 | def initialize(builder:, form:, **system_arguments)
12 | @builder = builder
13 | @form = form
14 | @system_arguments = system_arguments
15 |
16 | yield(self) if block_given?
17 | end
18 |
19 | def to_component
20 | Group.new(inputs: inputs, builder: builder, form: form, **@system_arguments)
21 | end
22 |
23 | def type
24 | :group
25 | end
26 |
27 | def input?
28 | true
29 | end
30 |
31 | # Avoid using Rails delegation here for performance reasons
32 | # rubocop:disable Rails/Delegate
33 | def render_in(view_context)
34 | to_component.render_in(view_context)
35 | end
36 | # rubocop:enable Rails/Delegate
37 | end
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/dsl/input_methods.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | module Dsl
6 | module InputMethods
7 | def fields_for(*args, **kwargs, &block)
8 | add_input FormReferenceInput.new(*args, builder: builder, form: form, **kwargs, &block)
9 | end
10 |
11 | def multi(**options, &block)
12 | add_input MultiInput.new(builder: builder, form: form, **options, &block)
13 | end
14 |
15 | def hidden(**options)
16 | add_input HiddenInput.new(builder: builder, form: form, **options)
17 | end
18 |
19 | def check_box(**options)
20 | add_input CheckBoxInput.new(builder: builder, form: form, **options)
21 | end
22 |
23 | def radio_button_group(**options, &block)
24 | add_input RadioButtonGroupInput.new(builder: builder, form: form, **options, &block)
25 | end
26 |
27 | def check_box_group(**options, &block)
28 | add_input CheckBoxGroupInput.new(builder: builder, form: form, **options, &block)
29 | end
30 |
31 | def separator
32 | add_input Separator.new
33 | end
34 |
35 | # START text input methods
36 |
37 | def text_field(**options, &block)
38 | options = decorate_options(**options)
39 | add_input TextFieldInput.new(builder: builder, form: form, **options, &block)
40 | end
41 |
42 | def text_area(**options, &block)
43 | options = decorate_options(**options)
44 | add_input TextAreaInput.new(builder: builder, form: form, **options, &block)
45 | end
46 |
47 | def given_name(**options, &block)
48 | text_field(autocomplete: "given-name", **options, &block)
49 | end
50 |
51 | def family_name(**options, &block)
52 | text_field(autocomplete: "family-name", **options, &block)
53 | end
54 |
55 | def address_line1(**options, &block)
56 | text_field(autocomplete: "address-line1", **options, &block)
57 | end
58 |
59 | def address_line2(**options, &block)
60 | text_field(autocomplete: "address-line2", **options, &block)
61 | end
62 |
63 | def address_level2(**options, &block)
64 | text_field(autocomplete: "address-level2", **options, &block)
65 | end
66 |
67 | alias city address_level2
68 |
69 | def postal_code(**options, &block)
70 | text_field(autocomplete: "postal-code", **options, &block)
71 | end
72 |
73 | # END text input methods
74 |
75 | # START select input methods
76 |
77 | def select_list(**options, &block)
78 | options = decorate_options(**options)
79 | add_input SelectListInput.new(builder: builder, form: form, **options, &block)
80 | end
81 |
82 | def country_name(**options, &block)
83 | select_list(autocomplete: "country-name", **options, &block)
84 | end
85 |
86 | def address_level1(**options, &block)
87 | select_list(autocomplete: "address-level1", **options, &block)
88 | end
89 |
90 | alias region_name address_level1
91 |
92 | # END select input methods
93 |
94 | # START button input methods
95 |
96 | def submit(**options, &block)
97 | options = decorate_options(**options)
98 | add_input SubmitButtonInput.new(builder: builder, form: form, **options, &block)
99 | end
100 |
101 | # END button input methods
102 |
103 | def inputs
104 | @inputs ||= []
105 | end
106 |
107 | private
108 |
109 | def add_input(input)
110 | inputs << input
111 | end
112 |
113 | # Called before the corresponding Input class is instantiated. The return value of this method is passed
114 | # to the Input class's constructor.
115 | def decorate_options(**options)
116 | options
117 | end
118 | end
119 | end
120 | end
121 | end
122 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/dsl/multi_input.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | module Dsl
6 | class MultiInput < Input
7 | include InputMethods
8 |
9 | attr_reader :name, :label
10 |
11 | def initialize(name:, label:, **system_arguments)
12 | @name = name
13 | @label = label
14 |
15 | super(**system_arguments)
16 |
17 | yield(self) if block_given?
18 | end
19 |
20 | def to_component
21 | Multi.new(input: self)
22 | end
23 |
24 | def type
25 | :multi
26 | end
27 |
28 | private
29 |
30 | def add_input(input)
31 | super
32 |
33 | check_one_input_visible!
34 | end
35 |
36 | def decorate_options(name: nil, **options)
37 | check_name!(name) if name
38 | new_options = { name: name || @name, label: nil, **options }
39 | new_options[:id] = nil if options[:hidden]
40 | new_options
41 | end
42 |
43 | def check_name!(name)
44 | return if name == @name
45 |
46 | raise ArgumentError, "Inputs inside a `multi' block must all have the same name. Expected '#{@name}', got '#{name}'."
47 | end
48 |
49 | def check_one_input_visible!
50 | return if inputs.count { |input| !input.hidden? } <= 1
51 |
52 | raise ArgumentError, "Only one input can be visible at a time in a `multi' block."
53 | end
54 | end
55 | end
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/dsl/radio_button_group_input.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | module Dsl
6 | class RadioButtonGroupInput < Input
7 | attr_reader :name, :label, :radio_buttons
8 |
9 | def initialize(name:, label: nil, **system_arguments)
10 | @name = name
11 | @label = label
12 | @radio_buttons = []
13 |
14 | super(**system_arguments)
15 |
16 | add_label_classes("FormControl-label", "mb-2")
17 |
18 | yield(self) if block_given?
19 | end
20 |
21 | def to_component
22 | RadioButtonGroup.new(input: self)
23 | end
24 |
25 | def type
26 | :radio_button_group
27 | end
28 |
29 | def radio_button(**system_arguments, &block)
30 | @radio_buttons << RadioButtonInput.new(
31 | builder: @builder, form: @form, name: @name, **system_arguments, &block
32 | )
33 | end
34 | end
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/dsl/radio_button_input.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | module Dsl
6 | class RadioButtonInput < Input
7 | attr_reader :name, :value, :label, :nested_form_block, :nested_form_arguments
8 |
9 | def initialize(name:, value:, label:, **system_arguments)
10 | @name = name
11 | @value = value
12 | @label = label
13 |
14 | super(**system_arguments)
15 |
16 | yield(self) if block_given?
17 | end
18 |
19 | def to_component
20 | RadioButton.new(input: self)
21 | end
22 |
23 | def nested_form(**system_arguments, &block)
24 | @nested_form_arguments = system_arguments
25 | @nested_form_block = block
26 | end
27 |
28 | def type
29 | :radio_button
30 | end
31 | end
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/dsl/select_list_input.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | module Dsl
6 | class SelectListInput < Input
7 | class Option
8 | attr_reader :label, :value, :system_arguments
9 |
10 | def initialize(label:, value:, **system_arguments)
11 | @label = label
12 | @value = value
13 | @system_arguments = system_arguments
14 | end
15 | end
16 |
17 | attr_reader :name, :label, :options
18 |
19 | def initialize(name:, label:, **system_arguments)
20 | @name = name
21 | @label = label
22 | @options = []
23 |
24 | super(**system_arguments)
25 |
26 | yield(self) if block_given?
27 | end
28 |
29 | def option(**system_arguments)
30 | @options << Option.new(**system_arguments)
31 | end
32 |
33 | def to_component
34 | SelectList.new(input: self)
35 | end
36 |
37 | def type
38 | :select_list
39 | end
40 |
41 | def focusable?
42 | true
43 | end
44 | end
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/dsl/submit_button_input.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | module Dsl
6 | class SubmitButtonInput < Input
7 | attr_reader :name, :label, :block
8 |
9 | def initialize(name:, label:, **system_arguments, &block)
10 | @name = name
11 | @label = label
12 | @block = block
13 |
14 | super(**system_arguments)
15 | end
16 |
17 | def to_component
18 | SubmitButton.new(input: self)
19 | end
20 |
21 | def type
22 | :submit_button
23 | end
24 | end
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/dsl/text_area_input.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | module Dsl
6 | class TextAreaInput < Input
7 | attr_reader :name, :label
8 |
9 | def initialize(name:, label:, **system_arguments)
10 | @name = name
11 | @label = label
12 |
13 | super(**system_arguments)
14 | end
15 |
16 | def to_component
17 | TextArea.new(input: self)
18 | end
19 |
20 | def type
21 | :text_area
22 | end
23 |
24 | def focusable?
25 | true
26 | end
27 | end
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/dsl/text_field_input.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | module Dsl
6 | class TextFieldInput < Input
7 | attr_reader(
8 | *%i[
9 | name label show_clear_button leading_visual trailing_label
10 | clear_button_id visually_hide_label inset monospace field_wrap_classes
11 | ]
12 | )
13 |
14 | def initialize(name:, label:, **system_arguments)
15 | @name = name
16 | @label = label
17 |
18 | @show_clear_button = system_arguments.delete(:show_clear_button)
19 | @leading_visual = system_arguments.delete(:leading_visual)
20 | @trailing_label = system_arguments.delete(:trailing_label)
21 | @clear_button_id = system_arguments.delete(:clear_button_id)
22 | @inset = system_arguments.delete(:inset)
23 | @monospace = system_arguments.delete(:monospace)
24 |
25 | super(**system_arguments)
26 |
27 | add_input_classes(
28 | "FormControl-input",
29 | Primer::RailsForms::Dsl::Input::SIZE_MAPPINGS[size]
30 | )
31 |
32 | add_input_classes("FormControl-inset") if inset?
33 | add_input_classes("FormControl-monospace") if monospace?
34 |
35 | @field_wrap_classes = class_names(
36 | "FormControl-input-wrap",
37 | Primer::RailsForms::Dsl::Input::SIZE_MAPPINGS[size],
38 | "FormControl-input-wrap--trailingAction": show_clear_button?,
39 | "FormControl-input-wrap--leadingVisual": leading_visual?
40 | )
41 | end
42 |
43 | alias show_clear_button? show_clear_button
44 | alias inset? inset
45 | alias monospace? monospace
46 |
47 | def to_component
48 | TextField.new(input: self)
49 | end
50 |
51 | def type
52 | :text_field
53 | end
54 |
55 | def focusable?
56 | true
57 | end
58 |
59 | def leading_visual?
60 | !!@leading_visual
61 | end
62 |
63 | def trailing_label?
64 | !!@trailing_label
65 | end
66 | end
67 | end
68 | end
69 | end
70 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/engine.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "rails/engine"
4 |
5 | module Primer
6 | module RailsForms
7 | class Engine < ::Rails::Engine
8 | isolate_namespace Primer::RailsForms
9 |
10 | config.autoload_paths = %W[
11 | #{root}/lib
12 | ]
13 |
14 | config.eager_load_paths = %W[
15 | #{root}/lib
16 | ]
17 |
18 | initializer "primer_rails_forms.eager_load_actions" do
19 | ActiveSupport.on_load(:after_initialize) do
20 | if Rails.application.config.eager_load
21 | Primer::RailsForms::Base.compile!
22 | Primer::RailsForms::Base.descendants.each(&:compile!)
23 | Primer::RailsForms::BaseComponent.descendants.each(&:compile!)
24 | end
25 | end
26 | end
27 |
28 | config.after_initialize do
29 | require "primer/form_components"
30 | end
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/form_control.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_tag(:div, class: @form_group_classes, style: "flex-grow: 1") do %>
2 | <% if @input.label %>
3 | <%= builder.label(@input.name, **@input.label_arguments) do %>
4 | <%= @input.label %>
5 | <% if @input.required? %>
6 | *
7 | <% end %>
8 | <% end %>
9 | <% end %>
10 | <%= content %>
11 | <% if @input.invalid? && @input.validation_messages.present? %>
12 |
13 | <%= render(Primer::OcticonComponent.new(icon: :"alert-fill", size: :xsmall, aria: { hidden: true })) %>
14 | <%= @input.validation_messages.first %>
15 |
16 | <% end %>
17 | <%= render(Caption.new(input: @input)) %>
18 | <% end %>
19 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/form_control.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | class FormControl < BaseComponent
6 | delegate :builder, :form, to: :@input
7 |
8 | def initialize(input:)
9 | @input = input
10 | @input.add_label_classes("FormControl-label")
11 | @form_group_classes = class_names(
12 | "FormControl",
13 | "FormControl--fullWidth" => @input.full_width?
14 | )
15 | end
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/form_list.html.erb:
--------------------------------------------------------------------------------
1 | <%= render(SpacingWrapper.new) do %>
2 | <% @forms.each do |form| %>
3 | <%= render(form) %>
4 | <% end %>
5 | <% end %>
6 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/form_list.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | class FormList
6 | extend ActsAsComponent
7 |
8 | renders_template File.join(__dir__, "form_list.html.erb")
9 |
10 | def initialize(*forms)
11 | @forms = forms
12 | end
13 |
14 | def perform_render(&_block)
15 | self.class.compile!
16 | render_form_list
17 | end
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/form_reference.html.erb:
--------------------------------------------------------------------------------
1 | <%= builder.fields_for(*@input.fields_for_args, **@input.fields_for_kwargs) do |fields| %>
2 | <%= render(@input.ref_block.call(fields)) %>
3 | <% end %>
4 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/form_reference.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | class FormReference < BaseComponent
6 | delegate :builder, :form, to: :@input
7 |
8 | def initialize(input:)
9 | @input = input
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/group.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_tag_if(horizontal?, :div, class: "d-flex", style: "gap: 15px;") do %>
2 | <% @inputs.each do |input| %>
3 | <%= render(input) %>
4 | <% end %>
5 | <% end %>
6 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/group.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "primer/classify"
4 |
5 | module Primer
6 | module RailsForms
7 | class Group < BaseComponent
8 | VERTICAL = :vertical
9 | HORIZONTAL = :horizontal
10 | DEFAULT_LAYOUT = VERTICAL
11 | LAYOUTS = [VERTICAL, HORIZONTAL].freeze
12 |
13 | def initialize(inputs:, builder:, form:, layout: DEFAULT_LAYOUT, **system_arguments)
14 | @inputs = inputs
15 | @builder = builder
16 | @form = form
17 | @layout = layout
18 | @system_arguments = system_arguments
19 | end
20 |
21 | def horizontal?
22 | @layout == HORIZONTAL
23 | end
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/hidden_field.html.erb:
--------------------------------------------------------------------------------
1 | <%= builder.hidden_field(@input.name, **@input.input_arguments) %>
2 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/hidden_field.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | class HiddenField < BaseComponent
6 | delegate :builder, :form, to: :@input
7 |
8 | def initialize(input:)
9 | @input = input
10 | @input.add_input_classes("FormField-input")
11 | end
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/multi.html.erb:
--------------------------------------------------------------------------------
1 | <% @input.inputs.each do |child_input| %>
2 | <% render(child_input) %>
3 | <% end %>
4 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/multi.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | class Multi < BaseComponent
6 | delegate :builder, :form, to: :@input
7 |
8 | def initialize(input:)
9 | @input = input
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/radio_button.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= builder.radio_button(@input.name, @input.value, **@input.input_arguments) %>
3 |
4 | <%= builder.label(@input.name, value: @input.value, **@input.label_arguments) do %>
5 | <%= @input.label %>
6 | <% end %>
7 | <%= render(Caption.new(input: @input)) %>
8 |
9 |
10 | <% if @input.nested_form_block %>
11 | <%= content_tag(:div, nested_form_arguments) do %>
12 | <%= render(@input.nested_form_block.call(builder)) %>
13 | <% end %>
14 | <% end %>
15 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/radio_button.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | class RadioButton < BaseComponent
6 | delegate :builder, :form, to: :@input
7 |
8 | def initialize(input:)
9 | @input = input
10 | @input.add_label_classes("FormControl-label")
11 | @input.add_input_classes("FormControl-radio")
12 | end
13 |
14 | def nested_form_arguments
15 | return @nested_form_arguments if defined?(@nested_form_arguments)
16 |
17 | @nested_form_arguments = { **@input.nested_form_arguments }
18 | @nested_form_arguments[:class] = class_names(
19 | @nested_form_arguments[:class],
20 | @nested_form_arguments.delete(:classes),
21 | "ml-4"
22 | )
23 |
24 | @nested_form_arguments
25 | end
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/radio_button_group.html.erb:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/radio_button_group.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | class RadioButtonGroup < BaseComponent
6 | delegate :builder, :form, to: :@input
7 |
8 | def initialize(input:)
9 | @input = input
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/select_list.html.erb:
--------------------------------------------------------------------------------
1 | <%= render(FormControl.new(input: @input)) do %>
2 | <%= content_tag(:div, class: @field_wrap_classes) do %>
3 | <%= builder.select(@input.name, options, {}, **@input.input_arguments) %>
4 | <% end %>
5 | <% end %>
6 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/select_list.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | class SelectList < BaseComponent
6 | delegate :builder, :form, to: :@input
7 |
8 | def initialize(input:)
9 | @input = input
10 | @input.add_input_classes(
11 | "FormControl-select",
12 | "FormControl--medium"
13 | )
14 |
15 | @field_wrap_classes = class_names("FormControl-select-wrap")
16 | end
17 |
18 | def options
19 | @options ||= @input.options.map do |option|
20 | [option.label, option.value, option.system_arguments]
21 | end
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/separator.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/separator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | class Separator < BaseComponent
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/spacing_wrapper.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= content %>
3 |
4 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/spacing_wrapper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | class SpacingWrapper < BaseComponent
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/submit_button.html.erb:
--------------------------------------------------------------------------------
1 | <%= render(Primer::ButtonComponent.new(**input_arguments)) do |c| %>
2 | <% @input.block.call(c) if @input.block %>
3 | <%= @input.label %>
4 | <% end %>
5 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/submit_button.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | class SubmitButton < BaseComponent
6 | module SubmitAttributeGenerator
7 | extend ActionView::Helpers::FormTagHelper
8 |
9 | class << self
10 | alias submit_tag_attributes submit_tag
11 |
12 | private
13 |
14 | # FormTagHelper#submit_tag ultimately calls the #tag method. We return the options hash here instead
15 | # of returning a string so it can be merged into the hash of options we pass to the Primer::ButtonComponent.
16 | def tag(_name, options)
17 | options
18 | end
19 | end
20 | end
21 |
22 | delegate :builder, :form, to: :@input
23 |
24 | def initialize(input:)
25 | @input = input
26 | @input.add_input_classes("FormField-input flex-self-start")
27 | @input.merge_input_arguments!(
28 | SubmitAttributeGenerator.submit_tag_attributes(input.label, name: input.name).deep_symbolize_keys
29 | )
30 |
31 | # rails uses a string for this, but PVC wants a symbol
32 | @input.merge_input_arguments!(type: :submit)
33 |
34 | # Never disable submit buttons. This overrides the global
35 | # ActionView::Base.automatically_disable_submit_tag setting.
36 | # Disabling the submit button is not accessible.
37 | @input.remove_input_data(:disable_with)
38 | end
39 |
40 | def input_arguments
41 | @input_arguments ||= @input.input_arguments.deep_dup.tap do |args|
42 | # rails uses :class but PVC wants :classes
43 | args[:classes] = args.delete(:class)
44 | end
45 | end
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/text_area.html.erb:
--------------------------------------------------------------------------------
1 | <%= render(FormControl.new(input: @input)) do %>
2 | <%= content_tag(:div, class: @field_wrap_classes) do %>
3 | <%= builder.text_area(@input.name, **@input.input_arguments) %>
4 | <% end %>
5 | <% end %>
6 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/text_area.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | class TextArea < BaseComponent
6 | delegate :builder, :form, to: :@input
7 |
8 | def initialize(input:)
9 | @input = input
10 | @input.add_input_classes("FormControl-input", "FormControl--medium")
11 | @field_wrap_classes = class_names("FormControl-input-wrap")
12 | end
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/text_field.html.erb:
--------------------------------------------------------------------------------
1 | <%= render(FormControl.new(input: @input)) do %>
2 | <%= content_tag_if(@input.trailing_label?, :div, class: "d-flex flex-items-center") do %>
3 | <% if @input.leading_visual || @input.show_clear_button? %>
4 | <%= content_tag(:div, class: @input.field_wrap_classes) do %>
5 | <% if @input.leading_visual %>
6 |
7 | <%= render(Primer::OcticonComponent.new(**@input.leading_visual)) %>
8 |
9 | <% end %>
10 | <%= builder.text_field(@input.name, **@input.input_arguments) %>
11 | <% if @input.show_clear_button? %>
12 |
15 | <% end %>
16 | <% end %>
17 | <% else %>
18 | <%= builder.text_field(@input.name, **@input.input_arguments) %>
19 | <% end %>
20 | <% if @input.trailing_label %>
21 | <%= @input.trailing_label %>
22 | <% end %>
23 | <% end %>
24 | <% end %>
25 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/text_field.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | class TextField < BaseComponent
6 | delegate :builder, :form, to: :@input
7 |
8 | def initialize(input:)
9 | @input = input
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/primer/rails_forms/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Primer
4 | module RailsForms
5 | module Version
6 | MAJOR = 0
7 | MINOR = 1
8 | PATCH = 0
9 |
10 | STRING = [MAJOR, MINOR, PATCH].join(".")
11 | end
12 |
13 | VERSION = Version
14 | end
15 | end
16 |
17 | # rubocop:disable Rails/Output
18 | puts Primer::ViewComponents::VERSION::STRING if __FILE__ == $PROGRAM_NAME
19 | # rubocop:enable Rails/Output
20 |
--------------------------------------------------------------------------------
/lookbook/.gitattributes:
--------------------------------------------------------------------------------
1 | # See https://git-scm.com/docs/gitattributes for more about git attribute files.
2 |
3 | # Mark the database schema as having been generated.
4 | db/schema.rb linguist-generated
5 |
6 | # Mark any vendored files as having been vendored.
7 | vendor/* linguist-vendored
8 |
--------------------------------------------------------------------------------
/lookbook/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2 | #
3 | # If you find yourself ignoring temporary files generated by your text editor
4 | # or operating system, you probably want to add a global ignore instead:
5 | # git config --global core.excludesfile '~/.gitignore_global'
6 |
7 | # Ignore bundler config.
8 | /.bundle
9 |
10 | # Ignore the default SQLite database.
11 | /db/*.sqlite3
12 | /db/*.sqlite3-*
13 |
14 | # Ignore all logfiles and tempfiles.
15 | /log/*
16 | /tmp/*
17 | !/log/.keep
18 | !/tmp/.keep
19 |
20 | # Ignore pidfiles, but keep the directory.
21 | /tmp/pids/*
22 | !/tmp/pids/
23 | !/tmp/pids/.keep
24 |
25 | # Ignore uploaded files in development.
26 | /storage/*
27 | !/storage/.keep
28 | /tmp/storage/*
29 | !/tmp/storage/
30 | !/tmp/storage/.keep
31 |
32 | /public/assets
33 |
34 | # Ignore master key for decrypting credentials and more.
35 | /config/master.key
36 |
37 | /node_modules
38 |
39 | /app/assets/builds/*
40 | !/app/assets/builds/.keep
41 |
42 | # this is built into /workspaces/@primer/css/dist and copied into app/assets
43 | app/assets/stylesheets/primer.css
44 |
--------------------------------------------------------------------------------
/lookbook/.ruby-version:
--------------------------------------------------------------------------------
1 | ruby-3.0.2
2 |
--------------------------------------------------------------------------------
/lookbook/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3 |
4 | ruby "~> 3.0"
5 |
6 | # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
7 | gem "rails", "~> 7.0.3"
8 |
9 | # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
10 | gem "sprockets-rails"
11 |
12 | # Use sqlite3 as the database for Active Record
13 | gem "sqlite3", "~> 1.4"
14 |
15 | # Use the Puma web server [https://github.com/puma/puma]
16 | gem "puma", "~> 5.0"
17 |
18 | # Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
19 | gem "importmap-rails"
20 |
21 | # Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
22 | gem "turbo-rails"
23 |
24 | # Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
25 | gem "stimulus-rails"
26 |
27 | # Build JSON APIs with ease [https://github.com/rails/jbuilder]
28 | gem "jbuilder"
29 |
30 | # Use Redis adapter to run Action Cable in production
31 | gem "redis", "~> 4.0"
32 |
33 | # Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
34 | # gem "kredis"
35 |
36 | # Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
37 | # gem "bcrypt", "~> 3.1.7"
38 |
39 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
40 | gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ]
41 |
42 | # Reduces boot times through caching; required in config/boot.rb
43 | gem "bootsnap", require: false
44 |
45 | # Use Sass to process CSS
46 | # gem "sassc-rails"
47 |
48 | # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
49 | # gem "image_processing", "~> 1.2"
50 |
51 | gem "view_component", "~> 2.0"
52 | gem "lookbook", "~> 0.8"
53 | gem "primer_rails_forms", path: "../", require: "primer/rails_forms"
54 | gem "cssbundling-rails", "~> 1.1"
55 |
56 | gem "pry-byebug"
57 |
58 | group :development, :test do
59 | # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
60 | gem "debug", platforms: %i[ mri mingw x64_mingw ]
61 | end
62 |
63 | group :development do
64 | # Use console on exceptions pages [https://github.com/rails/web-console]
65 | gem "web-console"
66 |
67 | # Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler]
68 | # gem "rack-mini-profiler"
69 |
70 | # Speed up commands on slow machines / big apps [https://github.com/rails/spring]
71 | # gem "spring"
72 | end
73 |
74 | group :test do
75 | # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
76 | gem "capybara"
77 | gem "selenium-webdriver"
78 | gem "webdrivers"
79 | end
80 |
--------------------------------------------------------------------------------
/lookbook/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: ..
3 | specs:
4 | primer_rails_forms (0.1.0)
5 | primer_view_components (~> 0.0.81)
6 | railties (>= 7)
7 |
8 | GEM
9 | remote: https://rubygems.org/
10 | specs:
11 | actioncable (7.0.3)
12 | actionpack (= 7.0.3)
13 | activesupport (= 7.0.3)
14 | nio4r (~> 2.0)
15 | websocket-driver (>= 0.6.1)
16 | actionmailbox (7.0.3)
17 | actionpack (= 7.0.3)
18 | activejob (= 7.0.3)
19 | activerecord (= 7.0.3)
20 | activestorage (= 7.0.3)
21 | activesupport (= 7.0.3)
22 | mail (>= 2.7.1)
23 | net-imap
24 | net-pop
25 | net-smtp
26 | actionmailer (7.0.3)
27 | actionpack (= 7.0.3)
28 | actionview (= 7.0.3)
29 | activejob (= 7.0.3)
30 | activesupport (= 7.0.3)
31 | mail (~> 2.5, >= 2.5.4)
32 | net-imap
33 | net-pop
34 | net-smtp
35 | rails-dom-testing (~> 2.0)
36 | actionpack (7.0.3)
37 | actionview (= 7.0.3)
38 | activesupport (= 7.0.3)
39 | rack (~> 2.0, >= 2.2.0)
40 | rack-test (>= 0.6.3)
41 | rails-dom-testing (~> 2.0)
42 | rails-html-sanitizer (~> 1.0, >= 1.2.0)
43 | actiontext (7.0.3)
44 | actionpack (= 7.0.3)
45 | activerecord (= 7.0.3)
46 | activestorage (= 7.0.3)
47 | activesupport (= 7.0.3)
48 | globalid (>= 0.6.0)
49 | nokogiri (>= 1.8.5)
50 | actionview (7.0.3)
51 | activesupport (= 7.0.3)
52 | builder (~> 3.1)
53 | erubi (~> 1.4)
54 | rails-dom-testing (~> 2.0)
55 | rails-html-sanitizer (~> 1.1, >= 1.2.0)
56 | activejob (7.0.3)
57 | activesupport (= 7.0.3)
58 | globalid (>= 0.3.6)
59 | activemodel (7.0.3)
60 | activesupport (= 7.0.3)
61 | activerecord (7.0.3)
62 | activemodel (= 7.0.3)
63 | activesupport (= 7.0.3)
64 | activestorage (7.0.3)
65 | actionpack (= 7.0.3)
66 | activejob (= 7.0.3)
67 | activerecord (= 7.0.3)
68 | activesupport (= 7.0.3)
69 | marcel (~> 1.0)
70 | mini_mime (>= 1.1.0)
71 | activesupport (7.0.3)
72 | concurrent-ruby (~> 1.0, >= 1.0.2)
73 | i18n (>= 1.6, < 2)
74 | minitest (>= 5.1)
75 | tzinfo (~> 2.0)
76 | addressable (2.8.0)
77 | public_suffix (>= 2.0.2, < 5.0)
78 | bindex (0.8.1)
79 | bootsnap (1.11.1)
80 | msgpack (~> 1.2)
81 | builder (3.2.4)
82 | byebug (11.1.3)
83 | capybara (3.37.1)
84 | addressable
85 | matrix
86 | mini_mime (>= 0.1.3)
87 | nokogiri (~> 1.8)
88 | rack (>= 1.6.0)
89 | rack-test (>= 0.6.3)
90 | regexp_parser (>= 1.5, < 3.0)
91 | xpath (~> 3.2)
92 | childprocess (4.1.0)
93 | coderay (1.1.3)
94 | concurrent-ruby (1.1.10)
95 | crass (1.0.6)
96 | cssbundling-rails (1.1.0)
97 | railties (>= 6.0.0)
98 | debug (1.5.0)
99 | irb (>= 1.3.6)
100 | reline (>= 0.2.7)
101 | digest (3.1.0)
102 | erubi (1.10.0)
103 | ffi (1.15.5)
104 | globalid (1.0.0)
105 | activesupport (>= 5.0)
106 | htmlbeautifier (1.4.2)
107 | i18n (1.10.0)
108 | concurrent-ruby (~> 1.0)
109 | importmap-rails (1.1.0)
110 | actionpack (>= 6.0.0)
111 | railties (>= 6.0.0)
112 | io-console (0.5.11)
113 | irb (1.4.1)
114 | reline (>= 0.3.0)
115 | jbuilder (2.11.5)
116 | actionview (>= 5.0.0)
117 | activesupport (>= 5.0.0)
118 | listen (3.7.1)
119 | rb-fsevent (~> 0.10, >= 0.10.3)
120 | rb-inotify (~> 0.9, >= 0.9.10)
121 | loofah (2.18.0)
122 | crass (~> 1.0.2)
123 | nokogiri (>= 1.5.9)
124 | lookbook (0.8.1)
125 | actioncable
126 | htmlbeautifier (~> 1.3)
127 | listen (~> 3.0)
128 | railties (>= 5.0)
129 | redcarpet (~> 3.5)
130 | rouge (~> 3.26)
131 | view_component (~> 2.0)
132 | yard (~> 0.9.25)
133 | mail (2.7.1)
134 | mini_mime (>= 0.1.1)
135 | marcel (1.0.2)
136 | matrix (0.4.2)
137 | method_source (1.0.0)
138 | mini_mime (1.1.2)
139 | minitest (5.16.1)
140 | msgpack (1.5.1)
141 | net-imap (0.2.3)
142 | digest
143 | net-protocol
144 | strscan
145 | net-pop (0.1.1)
146 | digest
147 | net-protocol
148 | timeout
149 | net-protocol (0.1.3)
150 | timeout
151 | net-smtp (0.3.1)
152 | digest
153 | net-protocol
154 | timeout
155 | nio4r (2.5.8)
156 | nokogiri (1.13.6-x86_64-darwin)
157 | racc (~> 1.4)
158 | nokogiri (1.13.6-x86_64-linux)
159 | racc (~> 1.4)
160 | octicons (17.3.0)
161 | primer_view_components (0.0.81)
162 | actionview (>= 5.0.0)
163 | activesupport (>= 5.0.0)
164 | octicons (>= 17.0.0)
165 | view_component (>= 2.0.0, < 3.0)
166 | pry (0.13.1)
167 | coderay (~> 1.1)
168 | method_source (~> 1.0)
169 | pry-byebug (3.9.0)
170 | byebug (~> 11.0)
171 | pry (~> 0.13.0)
172 | public_suffix (4.0.7)
173 | puma (5.6.4)
174 | nio4r (~> 2.0)
175 | racc (1.6.0)
176 | rack (2.2.3.1)
177 | rack-test (1.1.0)
178 | rack (>= 1.0, < 3)
179 | rails (7.0.3)
180 | actioncable (= 7.0.3)
181 | actionmailbox (= 7.0.3)
182 | actionmailer (= 7.0.3)
183 | actionpack (= 7.0.3)
184 | actiontext (= 7.0.3)
185 | actionview (= 7.0.3)
186 | activejob (= 7.0.3)
187 | activemodel (= 7.0.3)
188 | activerecord (= 7.0.3)
189 | activestorage (= 7.0.3)
190 | activesupport (= 7.0.3)
191 | bundler (>= 1.15.0)
192 | railties (= 7.0.3)
193 | rails-dom-testing (2.0.3)
194 | activesupport (>= 4.2.0)
195 | nokogiri (>= 1.6)
196 | rails-html-sanitizer (1.4.3)
197 | loofah (~> 2.3)
198 | railties (7.0.3)
199 | actionpack (= 7.0.3)
200 | activesupport (= 7.0.3)
201 | method_source
202 | rake (>= 12.2)
203 | thor (~> 1.0)
204 | zeitwerk (~> 2.5)
205 | rake (13.0.6)
206 | rb-fsevent (0.11.1)
207 | rb-inotify (0.10.1)
208 | ffi (~> 1.0)
209 | redcarpet (3.5.1)
210 | redis (4.6.0)
211 | regexp_parser (2.4.0)
212 | reline (0.3.1)
213 | io-console (~> 0.5)
214 | rexml (3.2.5)
215 | rouge (3.28.0)
216 | rubyzip (2.3.2)
217 | selenium-webdriver (4.1.0)
218 | childprocess (>= 0.5, < 5.0)
219 | rexml (~> 3.2, >= 3.2.5)
220 | rubyzip (>= 1.2.2)
221 | sprockets (4.0.3)
222 | concurrent-ruby (~> 1.0)
223 | rack (> 1, < 3)
224 | sprockets-rails (3.4.2)
225 | actionpack (>= 5.2)
226 | activesupport (>= 5.2)
227 | sprockets (>= 3.0.0)
228 | sqlite3 (1.4.2)
229 | stimulus-rails (1.0.4)
230 | railties (>= 6.0.0)
231 | strscan (3.0.3)
232 | thor (1.2.1)
233 | timeout (0.2.0)
234 | turbo-rails (1.1.1)
235 | actionpack (>= 6.0.0)
236 | activejob (>= 6.0.0)
237 | railties (>= 6.0.0)
238 | tzinfo (2.0.4)
239 | concurrent-ruby (~> 1.0)
240 | view_component (2.57.1)
241 | activesupport (>= 5.0.0, < 8.0)
242 | method_source (~> 1.0)
243 | web-console (4.2.0)
244 | actionview (>= 6.0.0)
245 | activemodel (>= 6.0.0)
246 | bindex (>= 0.4.0)
247 | railties (>= 6.0.0)
248 | webdrivers (5.0.0)
249 | nokogiri (~> 1.6)
250 | rubyzip (>= 1.3.0)
251 | selenium-webdriver (~> 4.0)
252 | webrick (1.7.0)
253 | websocket-driver (0.7.5)
254 | websocket-extensions (>= 0.1.0)
255 | websocket-extensions (0.1.5)
256 | xpath (3.2.0)
257 | nokogiri (~> 1.8)
258 | yard (0.9.27)
259 | webrick (~> 1.7.0)
260 | zeitwerk (2.5.4)
261 |
262 | PLATFORMS
263 | x86_64-darwin-20
264 | x86_64-linux
265 |
266 | DEPENDENCIES
267 | bootsnap
268 | capybara
269 | cssbundling-rails (~> 1.1)
270 | debug
271 | importmap-rails
272 | jbuilder
273 | lookbook (~> 0.8)
274 | primer_rails_forms!
275 | pry-byebug
276 | puma (~> 5.0)
277 | rails (~> 7.0.3)
278 | redis (~> 4.0)
279 | selenium-webdriver
280 | sprockets-rails
281 | sqlite3 (~> 1.4)
282 | stimulus-rails
283 | turbo-rails
284 | tzinfo-data
285 | view_component (~> 2.0)
286 | web-console
287 | webdrivers
288 |
289 | RUBY VERSION
290 | ruby 3.0.2p107
291 |
292 | BUNDLED WITH
293 | 2.3.7
294 |
--------------------------------------------------------------------------------
/lookbook/Procfile.dev:
--------------------------------------------------------------------------------
1 | web: bin/rails server -p 3000
2 | css: yarn build:css --watch
3 |
--------------------------------------------------------------------------------
/lookbook/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative "config/application"
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/lookbook/app/assets/builds/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/app/assets/builds/.keep
--------------------------------------------------------------------------------
/lookbook/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_tree ../../javascript .js
3 | //= link_tree ../../../vendor/javascript .js
4 | //= link_tree ../stylesheets .css
5 |
--------------------------------------------------------------------------------
/lookbook/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/app/assets/images/.keep
--------------------------------------------------------------------------------
/lookbook/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/lookbook/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/lookbook/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | end
3 |
--------------------------------------------------------------------------------
/lookbook/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/lookbook/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/lookbook/app/javascript/application.js:
--------------------------------------------------------------------------------
1 | // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
2 | import "@hotwired/turbo-rails"
3 | import "controllers"
4 |
--------------------------------------------------------------------------------
/lookbook/app/javascript/controllers/application.js:
--------------------------------------------------------------------------------
1 | import { Application } from "@hotwired/stimulus"
2 |
3 | const application = Application.start()
4 |
5 | // Configure Stimulus development experience
6 | application.debug = false
7 | window.Stimulus = application
8 |
9 | export { application }
10 |
--------------------------------------------------------------------------------
/lookbook/app/javascript/controllers/hello_controller.js:
--------------------------------------------------------------------------------
1 | import { Controller } from "@hotwired/stimulus"
2 |
3 | export default class extends Controller {
4 | connect() {
5 | this.element.textContent = "Hello World!"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/lookbook/app/javascript/controllers/index.js:
--------------------------------------------------------------------------------
1 | // Import and register all your controllers from the importmap under controllers/*
2 |
3 | import { application } from "controllers/application"
4 |
5 | // Eager load all controllers defined in the import map under controllers/**/*_controller
6 | import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
7 | eagerLoadControllersFrom("controllers", application)
8 |
9 | // Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
10 | // import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
11 | // lazyLoadControllersFrom("controllers", application)
12 |
--------------------------------------------------------------------------------
/lookbook/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | # Automatically retry jobs that encountered a deadlock
3 | # retry_on ActiveRecord::Deadlocked
4 |
5 | # Most jobs are safe to ignore if the underlying records are no longer available
6 | # discard_on ActiveJob::DeserializationError
7 | end
8 |
--------------------------------------------------------------------------------
/lookbook/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: "from@example.com"
3 | layout "mailer"
4 | end
5 |
--------------------------------------------------------------------------------
/lookbook/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | primary_abstract_class
3 | end
4 |
--------------------------------------------------------------------------------
/lookbook/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/app/models/concerns/.keep
--------------------------------------------------------------------------------
/lookbook/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Lookbook
5 |
6 | <%= csrf_meta_tags %>
7 | <%= csp_meta_tag %>
8 |
9 | <%= stylesheet_link_tag "primer", "data-turbo-track": "reload" %>
10 | <%= javascript_importmap_tags %>
11 |
12 |
13 |
14 | <%= yield %>
15 |
16 |
17 |
--------------------------------------------------------------------------------
/lookbook/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/lookbook/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/lookbook/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'bundle' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "rubygems"
12 |
13 | m = Module.new do
14 | module_function
15 |
16 | def invoked_as_script?
17 | File.expand_path($0) == File.expand_path(__FILE__)
18 | end
19 |
20 | def env_var_version
21 | ENV["BUNDLER_VERSION"]
22 | end
23 |
24 | def cli_arg_version
25 | return unless invoked_as_script? # don't want to hijack other binstubs
26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
27 | bundler_version = nil
28 | update_index = nil
29 | ARGV.each_with_index do |a, i|
30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
31 | bundler_version = a
32 | end
33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
34 | bundler_version = $1
35 | update_index = i
36 | end
37 | bundler_version
38 | end
39 |
40 | def gemfile
41 | gemfile = ENV["BUNDLE_GEMFILE"]
42 | return gemfile if gemfile && !gemfile.empty?
43 |
44 | File.expand_path("../../Gemfile", __FILE__)
45 | end
46 |
47 | def lockfile
48 | lockfile =
49 | case File.basename(gemfile)
50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
51 | else "#{gemfile}.lock"
52 | end
53 | File.expand_path(lockfile)
54 | end
55 |
56 | def lockfile_version
57 | return unless File.file?(lockfile)
58 | lockfile_contents = File.read(lockfile)
59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
60 | Regexp.last_match(1)
61 | end
62 |
63 | def bundler_requirement
64 | @bundler_requirement ||=
65 | env_var_version || cli_arg_version ||
66 | bundler_requirement_for(lockfile_version)
67 | end
68 |
69 | def bundler_requirement_for(version)
70 | return "#{Gem::Requirement.default}.a" unless version
71 |
72 | bundler_gem_version = Gem::Version.new(version)
73 |
74 | requirement = bundler_gem_version.approximate_recommendation
75 |
76 | return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0")
77 |
78 | requirement += ".a" if bundler_gem_version.prerelease?
79 |
80 | requirement
81 | end
82 |
83 | def load_bundler!
84 | ENV["BUNDLE_GEMFILE"] ||= gemfile
85 |
86 | activate_bundler
87 | end
88 |
89 | def activate_bundler
90 | gem_error = activation_error_handling do
91 | gem "bundler", bundler_requirement
92 | end
93 | return if gem_error.nil?
94 | require_error = activation_error_handling do
95 | require "bundler/version"
96 | end
97 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
98 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
99 | exit 42
100 | end
101 |
102 | def activation_error_handling
103 | yield
104 | nil
105 | rescue StandardError, LoadError => e
106 | e
107 | end
108 | end
109 |
110 | m.load_bundler!
111 |
112 | if m.invoked_as_script?
113 | load Gem.bin_path("bundler", "bundle")
114 | end
115 |
--------------------------------------------------------------------------------
/lookbook/bin/dev:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | if ! command -v foreman &> /dev/null
4 | then
5 | echo "Installing foreman..."
6 | gem install foreman
7 | fi
8 |
9 | while [[ "$#" > 0 ]]; do case $1 in
10 | -d|--debug) debug="1"; shift;;
11 | esac; done
12 |
13 | if [ -n "$debug" ]; then
14 | foreman start -f Procfile.dev css > tmp/css.log 2>&1 &
15 | echo $! > tmp/pids/css.pid
16 |
17 | # ensure we cleanup tmp files and overmind
18 | function cleanup {
19 | echo "Shutting down..."
20 | kill $(cat tmp/pids/css.pid) &>/dev/null
21 | rm -f tmp/pids/css.pid tmp/css.log
22 | }
23 |
24 | trap cleanup EXIT
25 |
26 | bundle exec rails server
27 | else
28 | foreman start -f Procfile.dev "$@"
29 | fi
30 |
--------------------------------------------------------------------------------
/lookbook/bin/importmap:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require_relative "../config/application"
4 | require "importmap/commands"
5 |
--------------------------------------------------------------------------------
/lookbook/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path("../config/application", __dir__)
3 | require_relative "../config/boot"
4 | require "rails/commands"
5 |
--------------------------------------------------------------------------------
/lookbook/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative "../config/boot"
3 | require "rake"
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/lookbook/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require "fileutils"
3 |
4 | # path to your application root.
5 | APP_ROOT = File.expand_path("..", __dir__)
6 |
7 | def system!(*args)
8 | system(*args) || abort("\n== Command #{args} failed ==")
9 | end
10 |
11 | FileUtils.chdir APP_ROOT do
12 | # This script is a way to set up or update your development environment automatically.
13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome.
14 | # Add necessary setup steps to this file.
15 |
16 | puts "== Installing dependencies =="
17 | system! "gem install bundler --conservative"
18 | system("bundle check") || system!("bundle install")
19 |
20 | # puts "\n== Copying sample files =="
21 | # unless File.exist?("config/database.yml")
22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml"
23 | # end
24 |
25 | puts "\n== Preparing database =="
26 | system! "bin/rails db:prepare"
27 |
28 | puts "\n== Removing old logs and tempfiles =="
29 | system! "bin/rails log:clear tmp:clear"
30 |
31 | puts "\n== Restarting application server =="
32 | system! "bin/rails restart"
33 | end
34 |
--------------------------------------------------------------------------------
/lookbook/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative "config/environment"
4 |
5 | run Rails.application
6 | Rails.application.load_server
7 |
--------------------------------------------------------------------------------
/lookbook/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative "boot"
2 |
3 | require "rails/all"
4 |
5 | # Require the gems listed in Gemfile, including any gems
6 | # you've limited to :test, :development, or :production.
7 | Bundler.require(*Rails.groups)
8 |
9 | module Lookbook
10 | class Application < Rails::Application
11 | # Initialize configuration defaults for originally generated Rails version.
12 | config.load_defaults 7.0
13 |
14 | # Configuration for the application, engines, and railties goes here.
15 | #
16 | # These settings can be overridden in specific environments using the files
17 | # in config/environments, which are processed later.
18 | #
19 | # config.time_zone = "Central Time (US & Canada)"
20 | # config.eager_load_paths << Rails.root.join("extras")
21 |
22 | config.autoload_paths << Rails.root.dirname.join("test/app/forms")
23 | config.autoload_paths << Rails.root.dirname.join("test/app/components")
24 | config.view_component.preview_paths << Rails.root.dirname.join("test/test/components/previews")
25 | end
26 | end
27 |
28 | require "view_component"
29 | require "primer/view_components/engine"
30 |
--------------------------------------------------------------------------------
/lookbook/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
2 |
3 | require "bundler/setup" # Set up gems listed in the Gemfile.
4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations.
5 |
--------------------------------------------------------------------------------
/lookbook/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: redis
3 | url: redis://localhost:6379/1
4 |
5 | test:
6 | adapter: test
7 |
8 | production:
9 | adapter: redis
10 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
11 | channel_prefix: lookbook_production
12 |
--------------------------------------------------------------------------------
/lookbook/config/credentials.yml.enc:
--------------------------------------------------------------------------------
1 | K9TDMfjilDAIn3QoH9lcp8nkCtjHFW5M7LXIVLD0+E1gJ2y5WN2nPELCdnoamlshhcxsQP4mbPkppY+zXSJ7Er7bzpvaHeb9YZ6I7Gv83rcB/blEhTGvAXsz/Qq6MHaQFWa2RA0QydH63abvOsUGYfthiIsAmIizxIxcPcyqWsCjGyqfJzFy76EfuBy0WfRv7k21u9Gp2yWx2XZPg1g2f3ppmn3CALwZmsFwl/lKZEDHNjMndyM7dI25nl/wLC5ABViyJm6eAXF3UOHfai1f2EzYJVDaXP0pKy71txDNFFz1LVaVwEJTgulAcZOLedgQlsFD/3dTt/QI4EjbUslyWhmtVqzaxaMzWZ+b8ecX8+aL9yDcQYPC1Hccgy5f7aWpqAtSlkRcu0WT2bcWy+p31iZo7Q5M4R6qbc7U--tfJ9UIhgvvI9uD92--0WI69yaLtZTxNS4qKnz/uQ==
--------------------------------------------------------------------------------
/lookbook/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite. Versions 3.8.0 and up are supported.
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem "sqlite3"
6 | #
7 | default: &default
8 | adapter: sqlite3
9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | database: db/development.sqlite3
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | database: db/test.sqlite3
22 |
23 | production:
24 | <<: *default
25 | database: db/production.sqlite3
26 |
--------------------------------------------------------------------------------
/lookbook/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative "application"
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/lookbook/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # In the development environment your application's code is reloaded any time
7 | # it changes. This slows down response time but is perfect for development
8 | # since you don't have to restart the web server when you make code changes.
9 | config.cache_classes = false
10 |
11 | # Do not eager load code on boot.
12 | config.eager_load = false
13 |
14 | # Show full error reports.
15 | config.consider_all_requests_local = true
16 |
17 | # Enable server timing
18 | config.server_timing = true
19 |
20 | # Enable/disable caching. By default caching is disabled.
21 | # Run rails dev:cache to toggle caching.
22 | if Rails.root.join("tmp/caching-dev.txt").exist?
23 | config.action_controller.perform_caching = true
24 | config.action_controller.enable_fragment_cache_logging = true
25 |
26 | config.cache_store = :memory_store
27 | config.public_file_server.headers = {
28 | "Cache-Control" => "public, max-age=#{2.days.to_i}"
29 | }
30 | else
31 | config.action_controller.perform_caching = false
32 |
33 | config.cache_store = :null_store
34 | end
35 |
36 | # Store uploaded files on the local file system (see config/storage.yml for options).
37 | config.active_storage.service = :local
38 |
39 | # Don't care if the mailer can't send.
40 | config.action_mailer.raise_delivery_errors = false
41 |
42 | config.action_mailer.perform_caching = false
43 |
44 | # Print deprecation notices to the Rails logger.
45 | config.active_support.deprecation = :log
46 |
47 | # Raise exceptions for disallowed deprecations.
48 | config.active_support.disallowed_deprecation = :raise
49 |
50 | # Tell Active Support which deprecation messages to disallow.
51 | config.active_support.disallowed_deprecation_warnings = []
52 |
53 | # Raise an error on page load if there are pending migrations.
54 | config.active_record.migration_error = :page_load
55 |
56 | # Highlight code that triggered database queries in logs.
57 | config.active_record.verbose_query_logs = true
58 |
59 | # Suppress logger output for asset requests.
60 | config.assets.quiet = true
61 |
62 | # Raises error for missing translations.
63 | # config.i18n.raise_on_missing_translations = true
64 |
65 | # Annotate rendered view with file names.
66 | # config.action_view.annotate_rendered_view_with_filenames = true
67 |
68 | # Uncomment if you wish to allow Action Cable access from any origin.
69 | # config.action_cable.disable_request_forgery_protection = true
70 | end
71 |
--------------------------------------------------------------------------------
/lookbook/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # Code is not reloaded between requests.
7 | config.cache_classes = true
8 |
9 | # Eager load code on boot. This eager loads most of Rails and
10 | # your application in memory, allowing both threaded web servers
11 | # and those relying on copy on write to perform better.
12 | # Rake tasks automatically ignore this option for performance.
13 | config.eager_load = true
14 |
15 | # Full error reports are disabled and caching is turned on.
16 | config.consider_all_requests_local = false
17 | config.action_controller.perform_caching = true
18 |
19 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
20 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
21 | # config.require_master_key = true
22 |
23 | # Disable serving static files from the `/public` folder by default since
24 | # Apache or NGINX already handles this.
25 | config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?
26 |
27 | # Compress CSS using a preprocessor.
28 | # config.assets.css_compressor = :sass
29 |
30 | # Do not fallback to assets pipeline if a precompiled asset is missed.
31 | config.assets.compile = false
32 |
33 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
34 | # config.asset_host = "http://assets.example.com"
35 |
36 | # Specifies the header that your server uses for sending files.
37 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache
38 | # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX
39 |
40 | # Store uploaded files on the local file system (see config/storage.yml for options).
41 | config.active_storage.service = :local
42 |
43 | # Mount Action Cable outside main process or domain.
44 | # config.action_cable.mount_path = nil
45 | # config.action_cable.url = "wss://example.com/cable"
46 | # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ]
47 |
48 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
49 | # config.force_ssl = true
50 |
51 | # Include generic and useful information about system operation, but avoid logging too much
52 | # information to avoid inadvertent exposure of personally identifiable information (PII).
53 | config.log_level = :info
54 |
55 | # Prepend all log lines with the following tags.
56 | config.log_tags = [ :request_id ]
57 |
58 | # Use a different cache store in production.
59 | # config.cache_store = :mem_cache_store
60 |
61 | # Use a real queuing backend for Active Job (and separate queues per environment).
62 | # config.active_job.queue_adapter = :resque
63 | # config.active_job.queue_name_prefix = "lookbook_production"
64 |
65 | config.action_mailer.perform_caching = false
66 |
67 | # Ignore bad email addresses and do not raise email delivery errors.
68 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
69 | # config.action_mailer.raise_delivery_errors = false
70 |
71 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
72 | # the I18n.default_locale when a translation cannot be found).
73 | config.i18n.fallbacks = true
74 |
75 | # Don't log any deprecations.
76 | config.active_support.report_deprecations = false
77 |
78 | # Use default logging formatter so that PID and timestamp are not suppressed.
79 | config.log_formatter = ::Logger::Formatter.new
80 |
81 | # Use a different logger for distributed setups.
82 | # require "syslog/logger"
83 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name")
84 |
85 | if ENV["RAILS_LOG_TO_STDOUT"].present?
86 | logger = ActiveSupport::Logger.new(STDOUT)
87 | logger.formatter = config.log_formatter
88 | config.logger = ActiveSupport::TaggedLogging.new(logger)
89 | end
90 |
91 | # Do not dump schema after migrations.
92 | config.active_record.dump_schema_after_migration = false
93 | end
94 |
--------------------------------------------------------------------------------
/lookbook/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | # The test environment is used exclusively to run your application's
4 | # test suite. You never need to work with it otherwise. Remember that
5 | # your test database is "scratch space" for the test suite and is wiped
6 | # and recreated between test runs. Don't rely on the data there!
7 |
8 | Rails.application.configure do
9 | # Settings specified here will take precedence over those in config/application.rb.
10 |
11 | # Turn false under Spring and add config.action_view.cache_template_loading = true.
12 | config.cache_classes = true
13 |
14 | # Eager loading loads your whole application. When running a single test locally,
15 | # this probably isn't necessary. It's a good idea to do in a continuous integration
16 | # system, or in some way before deploying your code.
17 | config.eager_load = ENV["CI"].present?
18 |
19 | # Configure public file server for tests with Cache-Control for performance.
20 | config.public_file_server.enabled = true
21 | config.public_file_server.headers = {
22 | "Cache-Control" => "public, max-age=#{1.hour.to_i}"
23 | }
24 |
25 | # Show full error reports and disable caching.
26 | config.consider_all_requests_local = true
27 | config.action_controller.perform_caching = false
28 | config.cache_store = :null_store
29 |
30 | # Raise exceptions instead of rendering exception templates.
31 | config.action_dispatch.show_exceptions = false
32 |
33 | # Disable request forgery protection in test environment.
34 | config.action_controller.allow_forgery_protection = false
35 |
36 | # Store uploaded files on the local file system in a temporary directory.
37 | config.active_storage.service = :test
38 |
39 | config.action_mailer.perform_caching = false
40 |
41 | # Tell Action Mailer not to deliver emails to the real world.
42 | # The :test delivery method accumulates sent emails in the
43 | # ActionMailer::Base.deliveries array.
44 | config.action_mailer.delivery_method = :test
45 |
46 | # Print deprecation notices to the stderr.
47 | config.active_support.deprecation = :stderr
48 |
49 | # Raise exceptions for disallowed deprecations.
50 | config.active_support.disallowed_deprecation = :raise
51 |
52 | # Tell Active Support which deprecation messages to disallow.
53 | config.active_support.disallowed_deprecation_warnings = []
54 |
55 | # Raises error for missing translations.
56 | # config.i18n.raise_on_missing_translations = true
57 |
58 | # Annotate rendered view with file names.
59 | # config.action_view.annotate_rendered_view_with_filenames = true
60 | end
61 |
--------------------------------------------------------------------------------
/lookbook/config/importmap.rb:
--------------------------------------------------------------------------------
1 | # Pin npm packages by running ./bin/importmap
2 |
3 | pin "application", preload: true
4 | pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
5 | pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
6 | pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
7 | pin_all_from "app/javascript/controllers", under: "controllers"
8 |
--------------------------------------------------------------------------------
/lookbook/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = "1.0"
5 |
6 | # Add additional assets to the asset load path.
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 |
9 | # Precompile additional assets.
10 | # application.js, application.css, and all non-JS/CSS in the app/assets
11 | # folder are already added.
12 | # Rails.application.config.assets.precompile += %w( admin.js admin.css )
13 |
--------------------------------------------------------------------------------
/lookbook/config/initializers/content_security_policy.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Define an application-wide content security policy.
4 | # See the Securing Rails Applications Guide for more information:
5 | # https://guides.rubyonrails.org/security.html#content-security-policy-header
6 |
7 | # Rails.application.configure do
8 | # config.content_security_policy do |policy|
9 | # policy.default_src :self, :https
10 | # policy.font_src :self, :https, :data
11 | # policy.img_src :self, :https, :data
12 | # policy.object_src :none
13 | # policy.script_src :self, :https
14 | # policy.style_src :self, :https
15 | # # Specify URI for violation reports
16 | # # policy.report_uri "/csp-violation-report-endpoint"
17 | # end
18 | #
19 | # # Generate session nonces for permitted importmap and inline scripts
20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
21 | # config.content_security_policy_nonce_directives = %w(script-src)
22 | #
23 | # # Report violations without enforcing the policy.
24 | # # config.content_security_policy_report_only = true
25 | # end
26 |
--------------------------------------------------------------------------------
/lookbook/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure parameters to be filtered from the log file. Use this to limit dissemination of
4 | # sensitive information. See the ActiveSupport::ParameterFilter documentation for supported
5 | # notations and behaviors.
6 | Rails.application.config.filter_parameters += [
7 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
8 | ]
9 |
--------------------------------------------------------------------------------
/lookbook/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, "\\1en"
8 | # inflect.singular /^(ox)en/i, "\\1"
9 | # inflect.irregular "person", "people"
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym "RESTful"
16 | # end
17 |
--------------------------------------------------------------------------------
/lookbook/config/initializers/permissions_policy.rb:
--------------------------------------------------------------------------------
1 | # Define an application-wide HTTP permissions policy. For further
2 | # information see https://developers.google.com/web/updates/2018/06/feature-policy
3 | #
4 | # Rails.application.config.permissions_policy do |f|
5 | # f.camera :none
6 | # f.gyroscope :none
7 | # f.microphone :none
8 | # f.usb :none
9 | # f.fullscreen :self
10 | # f.payment :self, "https://secure.example.com"
11 | # end
12 |
--------------------------------------------------------------------------------
/lookbook/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t "hello"
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t("hello") %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # "true": "foo"
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at https://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/lookbook/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers: a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum; this matches the default thread size of Active Record.
6 | #
7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
9 | threads min_threads_count, max_threads_count
10 |
11 | # Specifies the `worker_timeout` threshold that Puma will use to wait before
12 | # terminating a worker in development environments.
13 | #
14 | worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"
15 |
16 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
17 | #
18 | port ENV.fetch("PORT") { 3000 }
19 |
20 | # Specifies the `environment` that Puma will run in.
21 | #
22 | environment ENV.fetch("RAILS_ENV") { "development" }
23 |
24 | # Specifies the `pidfile` that Puma will use.
25 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
26 |
27 | # Specifies the number of `workers` to boot in clustered mode.
28 | # Workers are forked web server processes. If using threads and workers together
29 | # the concurrency of the application would be max `threads` * `workers`.
30 | # Workers do not work on JRuby or Windows (both of which do not support
31 | # processes).
32 | #
33 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
34 |
35 | # Use the `preload_app!` method when specifying a `workers` number.
36 | # This directive tells Puma to first boot the application and load code
37 | # before forking the application. This takes advantage of Copy On Write
38 | # process behavior so workers use less memory.
39 | #
40 | # preload_app!
41 |
42 | # Allow puma to be restarted by `bin/rails restart` command.
43 | plugin :tmp_restart
44 |
--------------------------------------------------------------------------------
/lookbook/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
3 |
4 | # Defines the root path route ("/")
5 | # root "articles#index"
6 |
7 | mount Lookbook::Engine, at: "/"
8 | end
9 |
--------------------------------------------------------------------------------
/lookbook/config/storage.yml:
--------------------------------------------------------------------------------
1 | test:
2 | service: Disk
3 | root: <%= Rails.root.join("tmp/storage") %>
4 |
5 | local:
6 | service: Disk
7 | root: <%= Rails.root.join("storage") %>
8 |
9 | # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
10 | # amazon:
11 | # service: S3
12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
14 | # region: us-east-1
15 | # bucket: your_own_bucket-<%= Rails.env %>
16 |
17 | # Remember not to checkin your GCS keyfile to a repository
18 | # google:
19 | # service: GCS
20 | # project: your_project
21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
22 | # bucket: your_own_bucket-<%= Rails.env %>
23 |
24 | # Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
25 | # microsoft:
26 | # service: AzureStorage
27 | # storage_account_name: your_account_name
28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
29 | # container: your_container_name-<%= Rails.env %>
30 |
31 | # mirror:
32 | # service: Mirror
33 | # primary: local
34 | # mirrors: [ amazon, google, microsoft ]
35 |
--------------------------------------------------------------------------------
/lookbook/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }])
7 | # Character.create(name: "Luke", movie: movies.first)
8 |
--------------------------------------------------------------------------------
/lookbook/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/lib/assets/.keep
--------------------------------------------------------------------------------
/lookbook/lib/tasks/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/lib/tasks/.keep
--------------------------------------------------------------------------------
/lookbook/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/log/.keep
--------------------------------------------------------------------------------
/lookbook/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "primer_rails_forms",
3 | "private": "true",
4 | "dependencies": {
5 | "chokidar-cli": "3.0.0"
6 | },
7 | "scripts": {
8 | "build:css": "script/css.sh"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/lookbook/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/lookbook/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/lookbook/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/lookbook/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/lookbook/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/lookbook/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/public/favicon.ico
--------------------------------------------------------------------------------
/lookbook/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 |
--------------------------------------------------------------------------------
/lookbook/script/css-compile.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | echo "Compiling primer/css..."
4 | pushd /workspaces/@primer/css > /dev/null
5 | yarn dist
6 | popd > /dev/null
7 | echo "done."
8 |
9 | mkdir -p /workspaces/rails_forms/lookbook/app/assets/stylesheets/
10 | cp /workspaces/@primer/css/dist/primer.css /workspaces/rails_forms/lookbook/app/assets/stylesheets/
11 |
--------------------------------------------------------------------------------
/lookbook/script/css.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | if [[ $1 == "--watch" ]]; then
4 | node_modules/.bin/chokidar "/workspaces/@primer/css/src/**/*.scss" -c ./script/css-compile.sh --initial
5 | else
6 | ./script/css-compile.sh
7 | fi
8 |
--------------------------------------------------------------------------------
/lookbook/storage/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/storage/.keep
--------------------------------------------------------------------------------
/lookbook/test/application_system_test_case.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
5 | end
6 |
--------------------------------------------------------------------------------
/lookbook/test/channels/application_cable/connection_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
4 | # test "connects with cookies" do
5 | # cookies.signed[:user_id] = 42
6 | #
7 | # connect
8 | #
9 | # assert_equal connection.user_id, "42"
10 | # end
11 | end
12 |
--------------------------------------------------------------------------------
/lookbook/test/controllers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/test/controllers/.keep
--------------------------------------------------------------------------------
/lookbook/test/fixtures/files/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/test/fixtures/files/.keep
--------------------------------------------------------------------------------
/lookbook/test/helpers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/test/helpers/.keep
--------------------------------------------------------------------------------
/lookbook/test/integration/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/test/integration/.keep
--------------------------------------------------------------------------------
/lookbook/test/mailers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/test/mailers/.keep
--------------------------------------------------------------------------------
/lookbook/test/models/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/test/models/.keep
--------------------------------------------------------------------------------
/lookbook/test/system/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/test/system/.keep
--------------------------------------------------------------------------------
/lookbook/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | ENV["RAILS_ENV"] ||= "test"
2 | require_relative "../config/environment"
3 | require "rails/test_help"
4 |
5 | class ActiveSupport::TestCase
6 | # Run tests in parallel with specified workers
7 | parallelize(workers: :number_of_processors)
8 |
9 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
10 | fixtures :all
11 |
12 | # Add more helper methods to be used by all tests here...
13 | end
14 |
--------------------------------------------------------------------------------
/lookbook/tmp/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/tmp/.keep
--------------------------------------------------------------------------------
/lookbook/tmp/pids/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/tmp/pids/.keep
--------------------------------------------------------------------------------
/lookbook/tmp/storage/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/tmp/storage/.keep
--------------------------------------------------------------------------------
/lookbook/vendor/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/vendor/.keep
--------------------------------------------------------------------------------
/lookbook/vendor/javascript/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primer/rails_forms/cd98888a4df1a5c8d107b5eb94f1ef4ff2eb67d9/lookbook/vendor/javascript/.keep
--------------------------------------------------------------------------------
/lookbook/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | ansi-regex@^4.1.0:
6 | version "4.1.1"
7 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed"
8 | integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==
9 |
10 | ansi-styles@^3.2.0:
11 | version "3.2.1"
12 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
13 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
14 | dependencies:
15 | color-convert "^1.9.0"
16 |
17 | anymatch@~3.1.2:
18 | version "3.1.2"
19 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
20 | integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
21 | dependencies:
22 | normalize-path "^3.0.0"
23 | picomatch "^2.0.4"
24 |
25 | binary-extensions@^2.0.0:
26 | version "2.2.0"
27 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
28 | integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
29 |
30 | braces@~3.0.2:
31 | version "3.0.2"
32 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
33 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
34 | dependencies:
35 | fill-range "^7.0.1"
36 |
37 | camelcase@^5.0.0:
38 | version "5.3.1"
39 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
40 | integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
41 |
42 | chokidar-cli@3.0.0:
43 | version "3.0.0"
44 | resolved "https://registry.yarnpkg.com/chokidar-cli/-/chokidar-cli-3.0.0.tgz#29283666063b9e167559d30f247ff8fc48794eb7"
45 | integrity sha512-xVW+Qeh7z15uZRxHOkP93Ux8A0xbPzwK4GaqD8dQOYc34TlkqUhVSS59fK36DOp5WdJlrRzlYSy02Ht99FjZqQ==
46 | dependencies:
47 | chokidar "^3.5.2"
48 | lodash.debounce "^4.0.8"
49 | lodash.throttle "^4.1.1"
50 | yargs "^13.3.0"
51 |
52 | chokidar@^3.5.2:
53 | version "3.5.3"
54 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
55 | integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
56 | dependencies:
57 | anymatch "~3.1.2"
58 | braces "~3.0.2"
59 | glob-parent "~5.1.2"
60 | is-binary-path "~2.1.0"
61 | is-glob "~4.0.1"
62 | normalize-path "~3.0.0"
63 | readdirp "~3.6.0"
64 | optionalDependencies:
65 | fsevents "~2.3.2"
66 |
67 | cliui@^5.0.0:
68 | version "5.0.0"
69 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
70 | integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==
71 | dependencies:
72 | string-width "^3.1.0"
73 | strip-ansi "^5.2.0"
74 | wrap-ansi "^5.1.0"
75 |
76 | color-convert@^1.9.0:
77 | version "1.9.3"
78 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
79 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
80 | dependencies:
81 | color-name "1.1.3"
82 |
83 | color-name@1.1.3:
84 | version "1.1.3"
85 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
86 | integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
87 |
88 | decamelize@^1.2.0:
89 | version "1.2.0"
90 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
91 | integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
92 |
93 | emoji-regex@^7.0.1:
94 | version "7.0.3"
95 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
96 | integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
97 |
98 | fill-range@^7.0.1:
99 | version "7.0.1"
100 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
101 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
102 | dependencies:
103 | to-regex-range "^5.0.1"
104 |
105 | find-up@^3.0.0:
106 | version "3.0.0"
107 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
108 | integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
109 | dependencies:
110 | locate-path "^3.0.0"
111 |
112 | fsevents@~2.3.2:
113 | version "2.3.2"
114 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
115 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
116 |
117 | get-caller-file@^2.0.1:
118 | version "2.0.5"
119 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
120 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
121 |
122 | glob-parent@~5.1.2:
123 | version "5.1.2"
124 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
125 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
126 | dependencies:
127 | is-glob "^4.0.1"
128 |
129 | is-binary-path@~2.1.0:
130 | version "2.1.0"
131 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
132 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
133 | dependencies:
134 | binary-extensions "^2.0.0"
135 |
136 | is-extglob@^2.1.1:
137 | version "2.1.1"
138 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
139 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
140 |
141 | is-fullwidth-code-point@^2.0.0:
142 | version "2.0.0"
143 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
144 | integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==
145 |
146 | is-glob@^4.0.1, is-glob@~4.0.1:
147 | version "4.0.3"
148 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
149 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
150 | dependencies:
151 | is-extglob "^2.1.1"
152 |
153 | is-number@^7.0.0:
154 | version "7.0.0"
155 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
156 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
157 |
158 | locate-path@^3.0.0:
159 | version "3.0.0"
160 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
161 | integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
162 | dependencies:
163 | p-locate "^3.0.0"
164 | path-exists "^3.0.0"
165 |
166 | lodash.debounce@^4.0.8:
167 | version "4.0.8"
168 | resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
169 | integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
170 |
171 | lodash.throttle@^4.1.1:
172 | version "4.1.1"
173 | resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
174 | integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==
175 |
176 | normalize-path@^3.0.0, normalize-path@~3.0.0:
177 | version "3.0.0"
178 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
179 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
180 |
181 | p-limit@^2.0.0:
182 | version "2.3.0"
183 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
184 | integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
185 | dependencies:
186 | p-try "^2.0.0"
187 |
188 | p-locate@^3.0.0:
189 | version "3.0.0"
190 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
191 | integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
192 | dependencies:
193 | p-limit "^2.0.0"
194 |
195 | p-try@^2.0.0:
196 | version "2.2.0"
197 | resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
198 | integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
199 |
200 | path-exists@^3.0.0:
201 | version "3.0.0"
202 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
203 | integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==
204 |
205 | picomatch@^2.0.4, picomatch@^2.2.1:
206 | version "2.3.1"
207 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
208 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
209 |
210 | readdirp@~3.6.0:
211 | version "3.6.0"
212 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
213 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
214 | dependencies:
215 | picomatch "^2.2.1"
216 |
217 | require-directory@^2.1.1:
218 | version "2.1.1"
219 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
220 | integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
221 |
222 | require-main-filename@^2.0.0:
223 | version "2.0.0"
224 | resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
225 | integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
226 |
227 | set-blocking@^2.0.0:
228 | version "2.0.0"
229 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
230 | integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
231 |
232 | string-width@^3.0.0, string-width@^3.1.0:
233 | version "3.1.0"
234 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
235 | integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==
236 | dependencies:
237 | emoji-regex "^7.0.1"
238 | is-fullwidth-code-point "^2.0.0"
239 | strip-ansi "^5.1.0"
240 |
241 | strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
242 | version "5.2.0"
243 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
244 | integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
245 | dependencies:
246 | ansi-regex "^4.1.0"
247 |
248 | to-regex-range@^5.0.1:
249 | version "5.0.1"
250 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
251 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
252 | dependencies:
253 | is-number "^7.0.0"
254 |
255 | which-module@^2.0.0:
256 | version "2.0.0"
257 | resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
258 | integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==
259 |
260 | wrap-ansi@^5.1.0:
261 | version "5.1.0"
262 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"
263 | integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==
264 | dependencies:
265 | ansi-styles "^3.2.0"
266 | string-width "^3.0.0"
267 | strip-ansi "^5.0.0"
268 |
269 | y18n@^4.0.0:
270 | version "4.0.3"
271 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
272 | integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
273 |
274 | yargs-parser@^13.1.2:
275 | version "13.1.2"
276 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
277 | integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==
278 | dependencies:
279 | camelcase "^5.0.0"
280 | decamelize "^1.2.0"
281 |
282 | yargs@^13.3.0:
283 | version "13.3.2"
284 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
285 | integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==
286 | dependencies:
287 | cliui "^5.0.0"
288 | find-up "^3.0.0"
289 | get-caller-file "^2.0.1"
290 | require-directory "^2.1.1"
291 | require-main-filename "^2.0.0"
292 | set-blocking "^2.0.0"
293 | string-width "^3.0.0"
294 | which-module "^2.0.0"
295 | y18n "^4.0.0"
296 | yargs-parser "^13.1.2"
297 |
--------------------------------------------------------------------------------
/primer_rails_forms.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | lib = File.expand_path("lib", __dir__)
4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5 | require "primer/rails_forms/version"
6 |
7 | Gem::Specification.new do |spec|
8 | spec.name = "primer_rails_forms"
9 | spec.version = Primer::RailsForms::VERSION::STRING
10 | spec.authors = ["GitHub Open Source"]
11 | spec.email = ["opensource+primer_view_components@github.com"]
12 |
13 | spec.summary = "Primer forms framework for Rails"
14 | spec.homepage = "https://github.com/primer/rails_forms"
15 | spec.license = "MIT"
16 | spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
17 |
18 | # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
19 | # to allow pushing to a single host or delete this section to allow pushing to any host.
20 | if spec.respond_to?(:metadata)
21 | spec.metadata["allowed_push_host"] = "https://rubygems.org"
22 | else
23 | raise "RubyGems 2.0 or newer is required to protect against " \
24 | "public gem pushes."
25 | end
26 |
27 | spec.files = Dir["CHANGELOG.md", "LICENSE.txt", "README.md", "lib/**/*", "app/**/*"]
28 | spec.require_paths = ["lib"]
29 |
30 | spec.add_runtime_dependency "primer_view_components", "~> 0.0.81"
31 | spec.add_runtime_dependency "railties", ">= 7"
32 | end
33 |
--------------------------------------------------------------------------------
/test/.gitignore:
--------------------------------------------------------------------------------
1 | tmp/
2 | log/
3 |
--------------------------------------------------------------------------------
/test/app/components/form_component.html.erb:
--------------------------------------------------------------------------------
1 | <%= form_with(url: "/foo", skip_default_ids: false) do |f| %>
2 | <%= render(@form_class.new(f)) %>
3 | <% end %>
4 |
--------------------------------------------------------------------------------
/test/app/components/form_component.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class FormComponent < ViewComponent::Base
4 | def initialize(form_class:)
5 | @form_class = form_class
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/test/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationController < ActionController::Base
4 | end
5 |
--------------------------------------------------------------------------------
/test/app/forms/after_content_form.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AfterContentForm < ApplicationForm
4 | form do |after_content_form|
5 | after_content_form.text_field(
6 | name: :first_name,
7 | label: "First name",
8 | required: true,
9 | caption: "What you go by."
10 | )
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/test/app/forms/after_content_form/after_content.html.erb:
--------------------------------------------------------------------------------
1 | Hello world
2 |
--------------------------------------------------------------------------------
/test/app/forms/application_form.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationForm < Primer::RailsForms::Base
4 | end
5 |
--------------------------------------------------------------------------------
/test/app/forms/both_types_of_caption_form.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class BothTypesOfCaptionForm < ApplicationForm
4 | form do |caption_form|
5 | caption_form.text_field(
6 | name: :first_name,
7 | label: "First name",
8 | required: true,
9 | caption: "What you go by."
10 | )
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/test/app/forms/both_types_of_caption_form/first_name_caption.html.erb:
--------------------------------------------------------------------------------
1 | I am a caption
2 |
--------------------------------------------------------------------------------
/test/app/forms/caption_template_form.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CaptionTemplateForm < ApplicationForm
4 | form do |name_form|
5 | name_form.text_field(
6 | name: :first_name,
7 | label: "First name",
8 | required: true
9 | )
10 |
11 | name_form.check_box(
12 | name: :cool,
13 | label: "Are you cool?"
14 | )
15 |
16 | name_form.radio_button_group(name: :age) do |age_radios|
17 | age_radios.radio_button(value: "young", label: "10-15")
18 | age_radios.radio_button(value: "middle_aged", label: "16-21")
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/test/app/forms/caption_template_form/age_middle_aged_caption.html.erb:
--------------------------------------------------------------------------------
1 | No longer a spring chicken.
2 |
--------------------------------------------------------------------------------
/test/app/forms/caption_template_form/age_young_caption.html.erb:
--------------------------------------------------------------------------------
1 | A young thing.
2 |
--------------------------------------------------------------------------------
/test/app/forms/caption_template_form/cool_caption.html.erb:
--------------------------------------------------------------------------------
1 | Check only if you are cool.
2 |
--------------------------------------------------------------------------------
/test/app/forms/caption_template_form/first_name_caption.html.erb:
--------------------------------------------------------------------------------
1 | Be honest!
2 |
--------------------------------------------------------------------------------
/test/app/forms/check_box_group_form.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CheckBoxGroupForm < ApplicationForm
4 | form do |check_form|
5 | check_form.check_box_group(label: "I like to eat, eat, eat:") do |check_group|
6 | check_group.check_box(name: "long_a", value: "long_a", label: "Ey-ples and ba-naynays", caption: 'Long "A" sound')
7 | check_group.check_box(name: "long_i", value: "long_i", label: "Eye-ples and ba-nainais", caption: 'Long "I" sound')
8 | check_group.check_box(name: "long_o", value: "long_o", label: "Oh-ples and ba-nonos", caption: 'Long "O" sound')
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/test/app/forms/composed_form.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ComposedForm < ApplicationForm
4 | form do |composed_form|
5 | composed_form.fields_for(:first_name) do |builder|
6 | FirstNameForm.new(builder)
7 | end
8 |
9 | composed_form.fields_for(:last_name) do |builder|
10 | LastNameForm.new(builder)
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/test/app/forms/first_name_form.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class FirstNameForm < ApplicationForm
4 | form do |first_name_form|
5 | first_name_form.text_field(
6 | name: :first_name,
7 | label: "First name",
8 | required: true,
9 | caption: "That which we call a rose by any other name would smell as sweet."
10 | )
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/test/app/forms/horizontal_form.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class HorizontalForm < ApplicationForm
4 | form do |my_form|
5 | my_form.group(layout: :horizontal) do |name_group|
6 | name_group.text_field(
7 | name: :first_name,
8 | label: "First name",
9 | required: true,
10 | caption: "What your friends call you."
11 | )
12 |
13 | name_group.text_field(
14 | name: :last_name,
15 | label: "Last name",
16 | required: true,
17 | caption: "What the principal calls you."
18 | )
19 | end
20 |
21 | my_form.text_field(
22 | name: :dietary_restrictions,
23 | label: "Dietary restrictions",
24 | caption: "Any allergies?"
25 | )
26 |
27 | my_form.check_box(
28 | name: :email_notifications,
29 | label: "Send me gobs of email!",
30 | caption: "Check this if you enjoy getting spam."
31 | )
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/test/app/forms/invalid_form.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class InvalidForm < ApplicationForm
4 | form do |my_form|
5 | my_form.text_field(
6 | name: :first_name,
7 | label: "First name",
8 | required: true,
9 | caption: "That which we call a rose by any other name would smell as sweet."
10 | )
11 |
12 | my_form.text_field(
13 | name: :last_name,
14 | label: "Last name",
15 | required: true,
16 | caption: "Bueller. Bueller. Bueller.",
17 | validation_message: "That doesn't look right"
18 | )
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/test/app/forms/last_name_form.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class LastNameForm < ApplicationForm
4 | form do |last_name_form|
5 | last_name_form.text_field(
6 | name: :last_name,
7 | label: "Last name",
8 | required: true,
9 | caption: "Bueller. Bueller. Bueller."
10 | )
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/test/app/forms/multi_text_field_form.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class MultiTextFieldForm < ApplicationForm
4 | form do |my_form|
5 | my_form.text_field(
6 | name: :first_name,
7 | label: "First name",
8 | required: true,
9 | caption: "That which we call a rose by any other name would smell as sweet."
10 | )
11 |
12 | my_form.separator
13 |
14 | my_form.text_field(
15 | name: :last_name,
16 | label: "Last name",
17 | required: true,
18 | caption: "Bueller. Bueller. Bueller."
19 | )
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/test/app/forms/radio_button_group_form.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class RadioButtonGroupForm < ApplicationForm
4 | form do |radio_form|
5 | radio_form.radio_button_group(name: "channel", label: "How did you hear about us?") do |radio_group|
6 | radio_group.radio_button(value: "online", label: "Online advertisement", caption: "Facebook maybe?")
7 | radio_group.radio_button(value: "radio", label: "Radio advertisement", caption: "We love us some NPR")
8 | radio_group.radio_button(value: "friend", label: "From a friend", caption: "Wow, what a good person")
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/test/app/forms/radio_button_with_nested_form.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class FriendForm < ApplicationForm
4 | form do |friend_form|
5 | friend_form.group(layout: :horizontal) do |name_group|
6 | name_group.text_field(name: "first_name", label: "First Name")
7 | name_group.text_field(name: "last_name", label: "Last Name")
8 | end
9 | end
10 | end
11 |
12 | class FriendTextAreaForm < ApplicationForm
13 | form do |friend_text_area_form|
14 | friend_text_area_form.text_area(
15 | name: "description",
16 | label: "Describe this wonderful person in loving detail"
17 | )
18 | end
19 | end
20 |
21 | class RadioButtonWithNestedForm < ApplicationForm
22 | form do |radio_form|
23 | radio_form.radio_button_group(name: "channel") do |radio_group|
24 | radio_group.radio_button(value: "online", label: "Online advertisement", caption: "Facebook maybe?")
25 | radio_group.radio_button(value: "radio", label: "Radio advertisement", caption: "We love us some NPR")
26 | radio_group.radio_button(value: "friend", label: "From a friend", caption: "Wow, what a good person") do |friend_button|
27 | friend_button.nested_form do |builder|
28 | Primer::RailsForms::FormList.new(
29 | FriendForm.new(builder),
30 | FriendTextAreaForm.new(builder)
31 | )
32 | end
33 | end
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/test/app/forms/select_list_form.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class SelectListForm < ApplicationForm
4 | form do |check_form|
5 | check_form.select_list(name: "cities", label: "Cool cities", caption: "Select your favorite!") do |city_list|
6 | city_list.option(label: "Lopez Island", value: "lopez_island")
7 | city_list.option(label: "Bellevue", value: "bellevue")
8 | city_list.option(label: "Seattle", value: "seattle")
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/test/app/forms/single_text_field_form.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class SingleTextFieldForm < ApplicationForm
4 | form do |my_form|
5 | my_form.text_field(
6 | name: :ultimate_answer,
7 | label: "Ultimate answer",
8 | required: true,
9 | caption: "The answer to life, the universe, and everything"
10 | )
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/test/app/forms/submit_button_form.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class SubmitButtonForm < ApplicationForm
4 | form do |my_form|
5 | my_form.fields_for(:name_form) do |builder|
6 | MultiTextFieldForm.new(builder)
7 | end
8 |
9 | my_form.submit(name: :submit, label: "Submit", scheme: :primary) do |c|
10 | c.with_leading_visual_icon(icon: :"check-circle")
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/test/app/forms/text_field_and_checkbox_form.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class TextFieldAndCheckboxForm < ApplicationForm
4 | form do |my_form|
5 | my_form.text_field(
6 | name: :ultimate_answer,
7 | label: "Ultimate answer",
8 | required: true,
9 | caption: "The answer to life, the universe, and everything"
10 | )
11 |
12 | my_form.check_box(
13 | name: :enable_ipd,
14 | label: "Enable the Infinite Improbability Drive",
15 | caption: "Cross interstellar distances in a mere nothingth of a second."
16 | )
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/test/config/application.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class DummyApplication < ::Rails::Application
4 | config.eager_load = false
5 | config.active_support.deprecation = :stderr
6 | end
7 |
8 | require "view_component"
9 | require "primer/view_components/engine"
10 |
--------------------------------------------------------------------------------
/test/forms_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "test_helper"
4 |
5 | class FormsTest < ActiveSupport::TestCase
6 | include ViewComponent::TestHelpers
7 | include ViewComponent::RenderPreviewHelper
8 |
9 | class DeepThought
10 | include ActiveModel::API
11 | include ActiveModel::Validations
12 |
13 | attr_reader :ultimate_answer
14 |
15 | def initialize(ultimate_answer)
16 | @ultimate_answer = ultimate_answer
17 | end
18 |
19 | validates :ultimate_answer, comparison: { greater_than: 41, less_than: 43 }
20 | end
21 |
22 | test "renders correct form structure" do
23 | render_preview(:single_text_field_form)
24 |
25 | assert_selector "form[action='/foo']" do
26 | assert_selector ".FormControl" do
27 | assert_selector "label[for='ultimate_answer']", text: "Ultimate answer" do
28 | # asterisk for required field
29 | assert_selector "span[aria-hidden='true']", text: "*"
30 | end
31 |
32 | assert_selector "input[type='text'][name='ultimate_answer'][id='ultimate_answer'][aria-required='true']"
33 | end
34 | end
35 | end
36 |
37 | test "renders correct form structure for a checkbox" do
38 | render_preview(:text_field_and_checkbox_form)
39 |
40 | assert_selector "input[type='checkbox'][name='enable_ipd'][id='enable_ipd']"
41 | assert_selector "label[for='enable_ipd']", text: "Enable the Infinite Improbability Drive" do
42 | assert_selector ".FormControl-caption", text: "Cross interstellar distances in a mere nothingth of a second."
43 | end
44 | end
45 |
46 | test "includes the given note" do
47 | render_preview(:single_text_field_form)
48 |
49 | assert_selector ".FormControl-caption", text: "The answer to life, the universe, and everything"
50 | end
51 |
52 | test "the input is described by the caption" do
53 | render_preview(:single_text_field_form)
54 |
55 | caption_id = page.find_all(".FormControl-caption").first["id"]
56 | assert_selector "input[aria-describedby='#{caption_id}']"
57 | end
58 |
59 | test "includes activemodel validation messages" do
60 | model = DeepThought.new(41)
61 | model.valid? # populate validation error messages
62 |
63 | render_in_view_context do
64 | form_with(model: model, url: "/foo", skip_default_ids: false) do |f|
65 | render(SingleTextFieldForm.new(f))
66 | end
67 | end
68 |
69 | assert_selector ".FormControl" do
70 | assert_selector ".FormControl-inlineValidation", text: "Ultimate answer must be greater than 41" do
71 | assert_selector ".octicon-alert-fill"
72 | end
73 | end
74 | end
75 |
76 | test "names inputs correctly when rendered against an activemodel" do
77 | model = DeepThought.new(42)
78 |
79 | render_in_view_context do
80 | form_with(model: model, url: "/foo", skip_default_ids: false) do |f|
81 | render(SingleTextFieldForm.new(f))
82 | end
83 | end
84 |
85 | text_field = page.find_all("input[type=text]").first
86 | assert_equal text_field["name"], "forms_test_deep_thought[ultimate_answer]"
87 | end
88 |
89 | test "the input is described by the validation message" do
90 | model = DeepThought.new(41)
91 | model.valid? # populate validation error messages
92 |
93 | render_in_view_context do
94 | form_with(model: model, url: "/foo", skip_default_ids: false) do |f|
95 | render(SingleTextFieldForm.new(f))
96 | end
97 | end
98 |
99 | validation_id = page.find_all(".FormControl-inlineValidation").first["id"]
100 | described_by = page.find_all("input[type='text']").first["aria-describedby"]
101 | assert described_by.split.include?(validation_id)
102 | end
103 |
104 | test "renders the caption template when present" do
105 | render_preview :caption_template_form
106 |
107 | assert_selector ".FormControl-caption .color-fg-danger", text: "Be honest!"
108 | assert_selector ".FormControl-caption .color-fg-danger", text: "Check only if you are cool."
109 | assert_selector ".FormControl-caption .color-fg-danger", text: "A young thing."
110 | assert_selector ".FormControl-caption .color-fg-danger", text: "No longer a spring chicken."
111 | end
112 |
113 | test "the input is described by the caption when caption templates are used" do
114 | num_inputs = 4
115 | render_preview :caption_template_form
116 |
117 | caption_ids = page
118 | .find_all("span.FormControl-caption")
119 | .map { |caption| caption["id"] }
120 | .reject(&:empty?)
121 | .uniq
122 |
123 | assert caption_ids.size == num_inputs, "Expected #{num_inputs} unique caption IDs, got #{caption_ids.size}"
124 |
125 | assert_selector("input", count: num_inputs) do |input|
126 | caption_id = input["aria-describedby"]
127 | assert_includes caption_ids, caption_id
128 | caption_ids.delete(caption_id)
129 | end
130 |
131 | assert_empty caption_ids
132 | end
133 |
134 | test "renders content after the form when present" do
135 | render_preview :after_content_form
136 |
137 | assert_selector ".content-after"
138 | end
139 |
140 | test "raises an error if both a caption argument and a caption template are provided" do
141 | error = assert_raises RuntimeError do
142 | render_in_view_context do
143 | form_with(url: "/foo", skip_default_ids: false) do |f|
144 | render(BothTypesOfCaptionForm.new(f))
145 | end
146 | end
147 | end
148 |
149 | assert_includes error.message, "Please provide either a caption: argument or caption template"
150 | end
151 |
152 | test "form list renders multiple forms" do
153 | render_in_view_context do
154 | form_with(url: "/foo", skip_default_ids: false) do |f|
155 | render(Primer::RailsForms::FormList.new(FirstNameForm.new(f), LastNameForm.new(f)))
156 | end
157 | end
158 |
159 | assert_selector "input[type=text][name=first_name]"
160 | assert_selector "input[type=text][name=last_name]"
161 | end
162 |
163 | test "renders a submit button" do
164 | render_preview :submit_button_form
165 |
166 | assert_selector "button[type=submit]"
167 | end
168 |
169 | test "renders a submit button without data-disable-with" do
170 | render_preview :submit_button_form
171 |
172 | button = page.find_all("button[type=submit]").first
173 | assert_nil button["data-disable-with"]
174 | end
175 |
176 | test "autofocuses the first invalid input" do
177 | render_preview :invalid_form
178 |
179 | assert_selector "input[type=text][name=last_name][autofocus]"
180 | end
181 | end
182 |
--------------------------------------------------------------------------------
/test/support/application_form.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationForm < Primer::RailsForms::Base
4 | end
5 |
--------------------------------------------------------------------------------
/test/test/components/previews/forms_preview.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class FormsPreview < ViewComponent::Preview
4 | def single_text_field_form; end
5 |
6 | def multi_text_field_form; end
7 |
8 | def text_field_and_checkbox_form; end
9 |
10 | def horizontal_form; end
11 |
12 | def composed_form; end
13 |
14 | def submit_button_form; end
15 |
16 | def radio_button_group_form; end
17 |
18 | def check_box_group_form; end
19 |
20 | def select_list_form; end
21 |
22 | def radio_button_with_nested_form; end
23 |
24 | def caption_template_form; end
25 |
26 | def after_content_form; end
27 |
28 | def invalid_form; end
29 | end
30 |
--------------------------------------------------------------------------------
/test/test/components/previews/forms_preview/after_content_form.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= form_with(url: "/foo", skip_default_ids: false) do |f| %>
3 | <%= render(AfterContentForm.new(f)) %>
4 | <% end %>
5 |
6 |
--------------------------------------------------------------------------------
/test/test/components/previews/forms_preview/caption_template_form.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= form_with(url: "/foo", skip_default_ids: false) do |f| %>
3 | <%= render(CaptionTemplateForm.new(f)) %>
4 | <% end %>
5 |
6 |
--------------------------------------------------------------------------------
/test/test/components/previews/forms_preview/check_box_group_form.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= form_with(url: "/foo", skip_default_ids: false) do |f| %>
3 | <%= render(CheckBoxGroupForm.new(f)) %>
4 | <% end %>
5 |
6 |
--------------------------------------------------------------------------------
/test/test/components/previews/forms_preview/composed_form.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
Each of these fields comes from its own form definition.
3 | <%= form_with(url: "/foo", skip_default_ids: false) do |f| %>
4 | <%= render(ComposedForm.new(f)) %>
5 | <% end %>
6 |
7 |
--------------------------------------------------------------------------------
/test/test/components/previews/forms_preview/horizontal_form.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= form_with(url: "/foo", skip_default_ids: false) do |f| %>
3 | <%= render(HorizontalForm.new(f)) %>
4 | <% end %>
5 |
6 |
--------------------------------------------------------------------------------
/test/test/components/previews/forms_preview/invalid_form.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= form_with(url: "/foo", skip_default_ids: false) do |f| %>
3 | <%= render(InvalidForm.new(f)) %>
4 | <% end %>
5 |
6 |
--------------------------------------------------------------------------------
/test/test/components/previews/forms_preview/multi_text_field_form.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= form_with(url: "/foo", skip_default_ids: false) do |f| %>
3 | <%= render(MultiTextFieldForm.new(f)) %>
4 | <% end %>
5 |
6 |
--------------------------------------------------------------------------------
/test/test/components/previews/forms_preview/radio_button_group_form.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= form_with(url: "/foo", skip_default_ids: false) do |f| %>
3 | <%= render(RadioButtonGroupForm.new(f)) %>
4 | <% end %>
5 |
6 |
--------------------------------------------------------------------------------
/test/test/components/previews/forms_preview/radio_button_with_nested_form.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= form_with(url: "/foo", skip_default_ids: false) do |f| %>
3 | <%= render(RadioButtonWithNestedForm.new(f)) %>
4 | <% end %>
5 |
6 |
--------------------------------------------------------------------------------
/test/test/components/previews/forms_preview/select_list_form.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= form_with(url: "/foo", skip_default_ids: false) do |f| %>
3 | <%= render(SelectListForm.new(f)) %>
4 | <% end %>
5 |
6 |
--------------------------------------------------------------------------------
/test/test/components/previews/forms_preview/single_text_field_form.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= form_with(url: "/foo", skip_default_ids: false) do |f| %>
3 | <%= render(SingleTextFieldForm.new(f)) %>
4 | <% end %>
5 |
6 |
--------------------------------------------------------------------------------
/test/test/components/previews/forms_preview/submit_button_form.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= form_with(url: "/foo", skip_default_ids: false) do |f| %>
3 | <%= render(SubmitButtonForm.new(f)) %>
4 | <% end %>
5 |
6 |
--------------------------------------------------------------------------------
/test/test/components/previews/forms_preview/text_field_and_checkbox_form.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= form_with(url: "/foo", skip_default_ids: false) do |f| %>
3 | <%= render(TextFieldAndCheckboxForm.new(f)) %>
4 | <% end %>
5 |
6 |
--------------------------------------------------------------------------------
/test/test/components/previews/text_field_preview.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @label Text Field Component
4 | class TextFieldPreview < ViewComponent::Preview
5 | # @label Playground
6 | #
7 | # @param name text
8 | # @param id text
9 | # @param label text
10 | # @param show_label toggle
11 | # @param trailing_label text
12 | # @param size [Symbol] select [small, medium, large]
13 | # @param show_clear_button toggle
14 | # @param clear_button_id text
15 | # @param full_width toggle
16 | # @param disabled toggle
17 | # @param invalid toggle
18 | # @param placeholder text
19 | # @param inset toggle
20 | # @param monospace toggle
21 | # @param leading_visual_icon text
22 | def playground(
23 | name: "my-text-field",
24 | id: "my-text-field",
25 | label: "My text field",
26 | show_label: true,
27 | trailing_label: nil,
28 | size: Primer::RailsForms::Dsl::Input::DEFAULT_SIZE.to_s,
29 | show_clear_button: false,
30 | clear_button_id: "my-text-field-clear-button",
31 | full_width: false,
32 | disabled: false,
33 | invalid: false,
34 | placeholder: nil,
35 | inset: false,
36 | monospace: false,
37 | leading_visual_icon: nil
38 | )
39 | system_arguments = {
40 | name: name,
41 | id: id,
42 | label: label,
43 | show_label: show_label,
44 | trailing_label: trailing_label,
45 | size: size,
46 | show_clear_button: show_clear_button,
47 | clear_button_id: clear_button_id,
48 | full_width: full_width,
49 | disabled: disabled,
50 | invalid: invalid,
51 | placeholder: placeholder,
52 | inset: inset,
53 | monospace: monospace
54 | }
55 |
56 | if leading_visual_icon
57 | system_arguments[:leading_visual] = {
58 | icon: leading_visual_icon,
59 | size: :small
60 | }
61 | end
62 |
63 | render(Primer::TextField.new(**system_arguments))
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "rails"
4 | require "rails/test_help"
5 | require "action_controller/railtie"
6 | require "rails/test_unit/railtie"
7 | require "active_model/railtie"
8 | require "primer/rails_forms"
9 |
10 | require "pry-byebug"
11 |
12 | Dir.chdir("test") do
13 | require "config/application"
14 |
15 | DummyApplication.initialize!
16 | end
17 |
18 | require "view_component/test_helpers"
19 |
20 | Dir.chdir("test") do
21 | Dir["support/**/*.rb"].each do |f|
22 | require f.chomp(".rb")
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/test/text_field_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "test_helper"
4 |
5 | class TextFieldTest < ActiveSupport::TestCase
6 | include ViewComponent::TestHelpers
7 |
8 | setup do
9 | @default_params = {
10 | name: "foo", id: "foo", label: "Foo"
11 | }
12 | end
13 |
14 | test "renders a text input with the given name and ID" do
15 | render_inline(Primer::TextField.new(**@default_params))
16 |
17 | assert_selector "label.FormControl-label", text: "Foo"
18 | assert_selector "input[type=text][name=foo][id=foo]"
19 | end
20 |
21 | test "visually hides the label" do
22 | render_inline(Primer::TextField.new(**@default_params, show_label: false))
23 |
24 | assert_selector "label.FormControl-label.sr-only", text: "Foo"
25 | end
26 |
27 | test "renders in the medium size by default" do
28 | render_inline(Primer::TextField.new(**@default_params))
29 |
30 | assert_selector("input.FormControl-medium")
31 | end
32 |
33 | test "renders in the large size" do
34 | render_inline(Primer::TextField.new(**@default_params, size: :large))
35 |
36 | assert_selector("input.FormControl-large")
37 | end
38 |
39 | test "renders a clear button" do
40 | render_inline(
41 | Primer::TextField.new(
42 | **@default_params,
43 | show_clear_button: true,
44 | clear_button_id: "clear-button-id"
45 | )
46 | )
47 |
48 | assert_selector "button.FormControl-input-trailingAction#clear-button-id"
49 | end
50 |
51 | test "renders the component full-width" do
52 | render_inline(Primer::TextField.new(**@default_params, full_width: true))
53 |
54 | assert_selector ".FormControl--fullWidth input"
55 | end
56 |
57 | test "marks the input as disabled" do
58 | render_inline(Primer::TextField.new(**@default_params, disabled: true))
59 |
60 | assert_selector "input[disabled]"
61 | end
62 |
63 | test "marks the input as invalid" do
64 | render_inline(Primer::TextField.new(**@default_params, invalid: true))
65 |
66 | assert_selector "input[invalid]"
67 | end
68 |
69 | test "renders the component with an inset style" do
70 | render_inline(Primer::TextField.new(**@default_params, inset: true))
71 |
72 | assert_selector "input.FormControl-inset"
73 | end
74 |
75 | test "renders the component with a monospace font" do
76 | render_inline(Primer::TextField.new(**@default_params, monospace: true))
77 |
78 | assert_selector "input.FormControl-monospace"
79 | end
80 |
81 | test "renders a leading visual icon" do
82 | render_inline(Primer::TextField.new(**@default_params, leading_visual: { icon: :search }))
83 |
84 | assert_selector ".FormControl-input-leadingVisualWrap" do
85 | assert_selector "svg.octicon.octicon-search"
86 | end
87 | end
88 |
89 | test "renders a trailing label" do
90 | render_inline(Primer::TextField.new(**@default_params, trailing_label: "bar"))
91 |
92 | assert_selector ".d-flex .ml-2", text: "bar"
93 | end
94 | end
95 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 |
--------------------------------------------------------------------------------