Commit a4bfc6da authored by Mark Florian's avatar Mark Florian

Merge branch 'peterhegman/gitlab-ui-checkbox-haml-partial' into 'master'

Add GitLab UI form builder and custom checkbox field

See merge request gitlab-org/gitlab!64260
parents 70dcf651 28eb8e2e
......@@ -413,6 +413,12 @@ module ApplicationHelper
end
end
def gitlab_ui_form_for(record, *args, &block)
options = args.extract_options!
form_for(record, *(args << options.merge({ builder: ::Gitlab::FormBuilders::GitlabUiFormBuilder })), &block)
end
private
def appearance
......
= form_for @group, html: { multipart: true, class: 'gl-show-field-errors js-general-permissions-form' }, authenticity_token: true do |f|
= gitlab_ui_form_for @group, html: { multipart: true, class: 'gl-show-field-errors js-general-permissions-form' }, authenticity_token: true do |f|
%input{ type: 'hidden', name: 'update_section', value: 'js-permissions-settings' }
= form_errors(@group)
......@@ -9,12 +9,10 @@
- if @group.root?
.form-group.gl-mb-3
.gl-form-checkbox.custom-control.custom-checkbox
= f.check_box :prevent_sharing_groups_outside_hierarchy, disabled: !can_change_prevent_sharing_groups_outside_hierarchy?(@group), class: 'custom-control-input'
= f.label :prevent_sharing_groups_outside_hierarchy, class: 'custom-control-label' do
%span
= s_('GroupSettings|Prevent members from sending invitations to groups outside of %{group} and its subgroups.').html_safe % { group: link_to_group(@group) }
%p.js-descr.help-text= prevent_sharing_groups_outside_hierarchy_help_text(@group)
= f.gitlab_ui_checkbox_component :prevent_sharing_groups_outside_hierarchy,
s_('GroupSettings|Prevent members from sending invitations to groups outside of %{group} and its subgroups.').html_safe % { group: link_to_group(@group) },
help_text: prevent_sharing_groups_outside_hierarchy_help_text(@group),
checkbox_options: { disabled: !can_change_prevent_sharing_groups_outside_hierarchy?(@group) }
.form-group.gl-mb-3
.gl-form-checkbox.custom-control.custom-checkbox
......
---
stage: none
group: unassigned
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# HAML
[HAML](https://haml.info/) is the [Ruby on Rails](https://rubyonrails.org/) template language that GitLab uses.
## GitLab UI form builder
[GitLab UI](https://gitlab-org.gitlab.io/gitlab-ui/) is a Vue component library that conforms
to the [Pajamas design system](https://design.gitlab.com/). Most of these components
rely on JavaScript and therefore can only be used in Vue.
However, some of the simpler components (checkboxes, radio buttons, form inputs) can be
used in HAML by applying the correct CSS classes to the elements. A custom
[Ruby on Rails form builder](https://gitlab.com/gitlab-org/gitlab/-/blob/7c108df101e86d8a27d69df2b5b1ff1fc24133c5/lib/gitlab/form_builders/gitlab_ui_form_builder.rb) exists to help use GitLab UI components in HAML.
### Use the GitLab UI form builder
To use the GitLab UI form builder:
1. Change `form_for` to `gitlab_ui_form_for`.
1. Change `f.check_box` to `f.gitlab_ui_checkbox_component`.
1. Remove `f.label` and instead pass the label as the second argument in `f.gitlab_ui_checkbox_component`.
For example:
- Before:
```haml
= gitlab_ui_form_for @group do |f|
.form-group.gl-mb-3
.gl-form-checkbox.custom-control.custom-checkbox
= f.check_box :prevent_sharing_groups_outside_hierarchy, disabled: !can_change_prevent_sharing_groups_outside_hierarchy?(@group), class: 'custom-control-input'
= f.label :prevent_sharing_groups_outside_hierarchy, class: 'custom-control-label' do
%span
= s_('GroupSettings|Prevent members from sending invitations to groups outside of %{group} and its subgroups.').html_safe % { group: link_to_group(@group) }
%p.help-text= prevent_sharing_groups_outside_hierarchy_help_text(@group)
```
- After:
```haml
= gitlab_ui_form_for @group do |f|
.form-group.gl-mb-3
= f.gitlab_ui_checkbox_component :prevent_sharing_groups_outside_hierarchy,
s_('GroupSettings|Prevent members from sending invitations to groups outside of %{group} and its subgroups.').html_safe % { group: link_to_group(@group) },
help_text: prevent_sharing_groups_outside_hierarchy_help_text(@group),
checkbox_options: { disabled: !can_change_prevent_sharing_groups_outside_hierarchy?(@group) }
```
### Available components
When using the GitLab UI form builder, the following components are available for use in HAML.
NOTE:
Currently only `gitlab_ui_checkbox_component` is available but more components are planned.
#### gitlab_ui_checkbox_component
[GitLab UI Docs](https://gitlab-org.gitlab.io/gitlab-ui/?path=/story/base-form-form-checkbox--default)
| Argument | Description | Type | Required (default value) |
|---|---|---|---|
| `method` | Attribute on the object passed to `gitlab_ui_form_for`. | `Symbol` | `true` |
| `label` | Checkbox label. | `String` | `true` |
| `help_text` | Help text displayed below the checkbox. | `String` | `false` (`nil`) |
| `checkbox_options` | Options that are passed to [Rails `check_box` method](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-check_box). | `Hash` | `false` (`{}`) |
| `checked_value` | Value when checkbox is checked. | `String` | `false` (`'1'`) |
| `unchecked_value` | Value when checkbox is unchecked. | `String` | `false` (`'0'`) |
| `label_options` | Options that are passed to [Rails `label` method](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-label). | `Hash` | `false` (`{}`) |
......@@ -81,6 +81,10 @@ Vue specific [design patterns and practices](vue.md).
How to use [GraphQL](graphql.md).
## HAML
How to use [HAML](haml.md).
## Icons and Illustrations
How we use SVG for our [Icons and Illustrations](icons.md).
......
# frozen_string_literal: true
module Gitlab
module FormBuilders
class GitlabUiFormBuilder < ActionView::Helpers::FormBuilder
def gitlab_ui_checkbox_component(
method,
label,
help_text: nil,
checkbox_options: {},
checked_value: '1',
unchecked_value: '0',
label_options: {}
)
@template.content_tag(
:div,
class: 'gl-form-checkbox custom-control custom-checkbox'
) do
@template.check_box(
@object_name,
method,
format_options(checkbox_options, ['custom-control-input']),
checked_value,
unchecked_value
) +
@template.label(
@object_name, method, format_options(label_options, ['custom-control-label'])
) do
if help_text
@template.content_tag(
:span,
label
) +
@template.content_tag(
:p,
help_text,
class: 'help-text'
)
else
label
end
end
end
end
private
def format_options(options, classes)
classes << options[:class]
objectify_options(options.merge({ class: classes.flatten.compact }))
end
end
end
end
......@@ -472,4 +472,23 @@ RSpec.describe ApplicationHelper do
allow(helper.controller).to receive(method_name).and_return(value)
end
end
describe '#gitlab_ui_form_for' do
let_it_be(:user) { build(:user) }
before do
allow(helper).to receive(:users_path).and_return('/root')
allow(helper).to receive(:form_for).and_call_original
end
it 'adds custom form builder to options and calls `form_for`' do
options = { html: { class: 'foo-bar' } }
expected_options = options.merge({ builder: ::Gitlab::FormBuilders::GitlabUiFormBuilder, url: '/root' })
expect do |b|
helper.gitlab_ui_form_for(user, options, &b)
end.to yield_with_args(::Gitlab::FormBuilders::GitlabUiFormBuilder)
expect(helper).to have_received(:form_for).with(user, expected_options)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::FormBuilders::GitlabUiFormBuilder do
let_it_be(:user) { build(:user) }
let_it_be(:fake_template) do
Object.new.tap do |template|
template.extend ActionView::Helpers::FormHelper
template.extend ActionView::Helpers::FormOptionsHelper
template.extend ActionView::Helpers::TagHelper
template.extend ActionView::Context
end
end
let_it_be(:form_builder) { described_class.new(:user, user, fake_template, {}) }
describe '#gitlab_ui_checkbox_component' do
let(:optional_args) { {} }
subject(:checkbox_html) { form_builder.gitlab_ui_checkbox_component(:view_diffs_file_by_file, "Show one file at a time on merge request's Changes tab", **optional_args) }
context 'without optional arguments' do
it 'renders correct html' do
expected_html = <<~EOS
<div class="gl-form-checkbox custom-control custom-checkbox">
<input name="user[view_diffs_file_by_file]" type="hidden" value="0" />
<input class="custom-control-input" type="checkbox" value="1" name="user[view_diffs_file_by_file]" id="user_view_diffs_file_by_file" />
<label class="custom-control-label" for="user_view_diffs_file_by_file">
Show one file at a time on merge request&#39;s Changes tab
</label>
</div>
EOS
expect(checkbox_html).to eq(html_strip_whitespace(expected_html))
end
end
context 'with optional arguments' do
let(:optional_args) do
{
help_text: 'Instead of all the files changed, show only one file at a time.',
checkbox_options: { class: 'checkbox-foo-bar' },
label_options: { class: 'label-foo-bar' },
checked_value: '3',
unchecked_value: '1'
}
end
it 'renders help text' do
expected_html = <<~EOS
<div class="gl-form-checkbox custom-control custom-checkbox">
<input name="user[view_diffs_file_by_file]" type="hidden" value="1" />
<input class="custom-control-input checkbox-foo-bar" type="checkbox" value="3" name="user[view_diffs_file_by_file]" id="user_view_diffs_file_by_file" />
<label class="custom-control-label label-foo-bar" for="user_view_diffs_file_by_file">
<span>Show one file at a time on merge request&#39;s Changes tab</span>
<p class="help-text">Instead of all the files changed, show only one file at a time.</p>
</label>
</div>
EOS
expect(checkbox_html).to eq(html_strip_whitespace(expected_html))
end
it 'passes arguments to `check_box` method' do
allow(fake_template).to receive(:check_box).and_return('')
checkbox_html
expect(fake_template).to have_received(:check_box).with(:user, :view_diffs_file_by_file, { class: %w(custom-control-input checkbox-foo-bar), object: user }, '3', '1')
end
it 'passes arguments to `label` method' do
allow(fake_template).to receive(:label).and_return('')
checkbox_html
expect(fake_template).to have_received(:label).with(:user, :view_diffs_file_by_file, { class: %w(custom-control-label label-foo-bar), object: user })
end
end
end
private
def html_strip_whitespace(html)
html.lines.map(&:strip).join('')
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment