Skip to content

Commit

Permalink
add overlay component
Browse files Browse the repository at this point in the history
  • Loading branch information
keithamus committed Sep 22, 2022
1 parent 72575dc commit dbe2867
Show file tree
Hide file tree
Showing 9 changed files with 427 additions and 0 deletions.
11 changes: 11 additions & 0 deletions app/components/primer/alpha/overlay.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<%= show_button %>
<%= render Primer::BaseComponent.new(**@system_arguments) do %>
<%= header %>
<% if content.present? %>
<%= content %>
<% else %>
<%= body %>
<%= footer %>
<% end %>
<% end %>
194 changes: 194 additions & 0 deletions app/components/primer/alpha/overlay.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# frozen_string_literal: true

module Primer
module Alpha
# Overlay components codify design patterns related to floating surfaces such
# as dialogs and menus. They are private components intended to be used by
# specialized components, and mostly contain presentational logic and
# behavior.
#
# @accessibility
# - **Overlay Accessible Name**: A Overlay should have an accessible name,
# so screen readers are aware of the purpose of the Overlay when it opens.
# Give an accessible name setting `:title`. The accessible name will be
# used as the main heading inside the Overlay.
# - **Overlay unique id**: A Overlay should be unique. Give a unique id
# setting `:Overlay_id`. If no `:Overlay_id` is given, a default randomize
# hex id is generated.
#
# The combination of both `:title` and `:Overlay_id` establishes an
# `aria-labelledby` relationship between the title and the unique id of
# the Overlay.
class Overlay < Primer::Component
DEFAULT_SIZE = :auto
SIZE_MAPPINGS = {
DEFAULT_SIZE => "Overlay--size-auto",
:small => "Overlay--size-small",
:medium => "Overlay--size-medium",
:medium_portrait => "Overlay--size-medium-portrait",
:large => "Overlay--size-large",
:xlarge => "Overlay--size-xlarge"
}.freeze
SIZE_OPTIONS = SIZE_MAPPINGS.keys

DEFAULT_PLACEMENT = :anchored
PLACEMENT_MAPPINGS = {
DEFAULT_PLACEMENT => "Overlay--placement-anchored",
:center => "Overlay--placement-center",
:full => "Overlay--placement-full",
:top => "Overlay--placement-top",
:bottom => "Overlay--placement-bottom",
:start => "Overlay--placement-start",
:end => "Overlay--placement-end"
}.freeze
PLACEMENT_OPTIONS = [nil, *PLACEMENT_MAPPINGS.keys].freeze

DEFAULT_ANCHOR_ALIGN = :start
ANCHOR_ALIGN_MAPPINGS = {
DEFAULT_ANCHOR_ALIGN => "Overlay--anchorAlign-start",
:center => "Overlay--anchorAlign-center",
:end => "Overlay--anchorAlign-end",
}.freeze
ANCHOR_ALIGN_OPTIONS = ANCHOR_ALIGN_MAPPINGS.keys

DEFAULT_ANCHOR_SIDE = :outside_bottom
ANCHOR_SIDE_MAPPINGS = {
:inside_top => "Overlay--anchorSide-insideTop",
:inside_bottom => "Overlay--anchorSide-insideBottom",
:inside_left => "Overlay--anchorSide-insideLeft",
:inside_right => "Overlay--anchorSide-insideRight",
:inside_center => "Overlay--anchorSide-insideCenter",
:outside_top => "Overlay--anchorSide-outsideTop",
DEFAULT_ANCHOR_SIDE => "Overlay--anchorSide-outsideBottom",
:outside_left => "Overlay--anchorSide-outsideLeft",
:outside_right => "Overlay--anchorSide-outsideRight",
}.freeze
ANCHOR_SIDE_OPTIONS = ANCHOR_SIDE_MAPPINGS.keys

DEFAULT_POPUP = :auto
POPUP_OPTIONS = [ DEFAULT_POPUP, :hint, :manual ].freeze

ROLE_OPTIONS = [ :dialog, :menu ].freeze

# Optional button to open the Overlay.
#
# @param system_arguments [Hash] The same arguments as <%= link_to_component(Primer::ButtonComponent) %>.
renders_one :show_button, lambda { |**system_arguments|
system_arguments[:classes] = class_names(
system_arguments[:classes]
)
system_arguments[:id] = "overlay-show-#{@system_arguments[:id]}"
system_arguments["popuptoggletarget"] = @system_arguments[:id]
system_arguments[:data] = (system_arguments[:data] || {}).merge({ "show-dialog-id": @system_arguments[:id] })
Primer::ButtonComponent.new(**system_arguments)
}

# Header content.
#
# @param divider [Boolean] Show a divider between the header and body.
# @param visually_hide_title [Boolean] Visually hide the `title` while maintaining a label for assistive technologies.
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
renders_one :header, lambda { |divider: false, size: :medium, visually_hide_title: @visually_hide_title, **system_arguments|
Primer::Alpha::Overlay::Header.new(
id: @id,
title: @title,
subtitle: @subtitle,
size: size,
divider: divider,
visually_hide_title: visually_hide_title,
**system_arguments
)
}

# Required body content.
#
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
renders_one :body, "Body"

# Footer content.
#
# @param show_divider [Boolean] Show a divider between the footer and body.
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
renders_one :footer, "Footer"

# @example Overlay with Cancel and Submit buttons
# @description
# An ID is provided which enables wiring of the open and close buttons to the Overlay.
# @code
# <%= render(Primer::Alpha::Overlay.new(
# title: "Overlay Example",
# id: "my-Overlay",
# )) do |d| %>
# <% d.with_show_button { "Show Overlay" } %>
# <% d.with_body do %>
# <p>Some content</p>
# <% end %>
# <% d.footer do %>
# <%= render(Primer::ButtonComponent.new(data: { "close-Overlay-id": "my-Overlay" })) { "Cancel" } %>
# <%= render(Primer::ButtonComponent.new(scheme: :primary)) { "Submit" } %>
# <% end %>
# <% end %>
# @param id [String] The id of the Overlay.
# @param title [String] Describes the content of the Overlay.
# @param subtitle [String] Provides dditional context for the Overlay, also setting the `aria-describedby` attribute.
# @param size [Symbol] The size of the Overlay. <%= one_of(Primer::Alpha::Overlay::SIZE_OPTIONS) %>
# @param placement [Symbol] The placement of the Overlay. <%= one_of(Primer::Alpha::Overlay::PLACEMENT_OPTIONS) %>
# @param anchor_align [Symbol] The anchor alignment of the Overlay. <%= one_of(Primer::Alpha::Overlay::ANCHOR_ALIGN_OPTIONS) %>
# @param anchor_side [Symbol] The side to anchor the Overlay to. <%= one_of(Primer::Alpha::Overlay::ANCHOR_SIDE_OPTIONS) %>
# @param allow_out_of_bounds [Boolean] Allow the Overlay to overflow its container.
# @param visually_hide_title [Boolean] If true will hide the heading title, while still making it available to Screen Readers.
# @param role [String] The ARIA role. <%= one_of(Primer::Alpha::Overlay::ROLE_OPTIONS) %>
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
def initialize(
title:,
subtitle: nil,
popup: DEFAULT_POPUP,
defaultopen: false,
size: DEFAULT_SIZE,
placement: DEFAULT_PLACEMENT,
anchor_align: DEFAULT_ANCHOR_ALIGN,
anchor_side: DEFAULT_ANCHOR_SIDE,
allow_out_of_bounds: false,
visually_hide_title: false,
role:,
id: "Overlay-#{(36**3 + rand(36**4)).to_s(36)}",
**system_arguments
)
@system_arguments = deny_tag_argument(**system_arguments)

@system_arguments[:tag] = "primer-overlay"
@system_arguments[:role] = fetch_or_fallback(ROLE_OPTIONS, role)

@system_arguments[:id] = id.to_s
@system_arguments[:classes] = class_names(
"Overlay",
"Overlay-whenNarrow",
SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, DEFAULT_SIZE)],
"Overlay--motion-scaleFade",
system_arguments[:classes]
)
@backdrop_classes = class_names(
"Overlay-backdrop",
PLACEMENT_MAPPINGS[fetch_or_fallback(PLACEMENT_OPTIONS, placement, nil)],
ANCHOR_ALIGN_MAPPINGS[fetch_or_fallback(ANCHOR_ALIGN_OPTIONS, anchor_align, DEFAULT_ANCHOR_ALIGN)],
ANCHOR_SIDE_MAPPINGS[fetch_or_fallback(ANCHOR_SIDE_OPTIONS, anchor_side, DEFAULT_ANCHOR_SIDE)]
)

@id = id.to_s
@title = title
@subtitle = subtitle
@visually_hide_title = visually_hide_title

@system_arguments[:popup] = popup
@system_arguments[:defaultopen] = "" if defaultopen
@system_arguments[:aria] ||= {}
@system_arguments[:aria][:describedby] ||= "#{@id}-description"
end

def before_render
with_header unless header?
with_body unless body?
end
end
end
end
25 changes: 25 additions & 0 deletions app/components/primer/alpha/overlay/body.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module Primer
module Alpha
class Overlay
# A `Overlay::Body` is a compositional component, used to render the
# Body of an overlay. See <%= link_to_component(Primer::Alpha::Overlay) %>.
class Body < Primer::Component
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
def initialize(**system_arguments)
@system_arguments = deny_tag_argument(**system_arguments)
@system_arguments[:tag] = :div
@system_arguments[:classes] = class_names(
"Overlay-body",
system_arguments[:classes]
)
end

def call
render(Primer::BaseComponent.new(**@system_arguments)) { content }
end
end
end
end
end
31 changes: 31 additions & 0 deletions app/components/primer/alpha/overlay/footer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

module Primer
module Alpha
class Overlay
# A `Overlay::Footer` is a compositional component, used to render the
# Footer of an overlay. See <%= link_to_component(Primer::Alpha::Overlay) %>.
class Footer < Primer::Component
# @param show_divider [Boolean] Show a divider between the footer and body.
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
def initialize(
show_divider: false,
**system_arguments
)
@system_arguments = deny_tag_argument(**system_arguments)
@system_arguments[:tag] = :div
@system_arguments[:classes] = class_names(
"Overlay-footer",
"Overlay-footer--alignEnd",
{ "Overlay-footer--divided": show_divider },
system_arguments[:classes]
)
end

def call
render(Primer::BaseComponent.new(**@system_arguments)) { content }
end
end
end
end
end
15 changes: 15 additions & 0 deletions app/components/primer/alpha/overlay/header.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<%= render Primer::BaseComponent.new(**@system_arguments) do %>
<div class="Overlay-headerContentWrap">
<div class="Overlay-titleWrap">
<h1 class="Overlay-title <% if @visually_hide_title || content.present? %>sr-only<% end %>"><%= @title %></h1>
<% if content.present? %>
<%= content %>
<% elsif @subtitle.present? %>
<h2 id="<%= @id %>-description" class="Overlay-description"><%= @subtitle %></h2>
<% end %>
</div>
<div class="Overlay-actionWrap">
<%= render Primer::Beta::CloseButton.new(classes: "Overlay-closeButton", "data-close-dialog-id": @id) %>
</div>
</div>
<% end %>
46 changes: 46 additions & 0 deletions app/components/primer/alpha/overlay/header.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# frozen_string_literal: true

module Primer
module Alpha
class Overlay
# A `Overlay::Header` is a compositional component, used to render the
# Header of an overlay. See <%= link_to_component(Primer::Alpha::Overlay) %>.
class Header < Primer::Component
DEFAULT_SIZE = :medium
SIZE_MAPPINGS = {
DEFAULT_SIZE => nil,
:large => "Overlay-header--large",
}.freeze
SIZE_OPTIONS = SIZE_MAPPINGS.keys

# @param title [String] Describes the content of the Overlay.
# @param subtitle [String] Provides dditional context for the Overlay, also setting the `aria-describedby` attribute.
# @param divider [Boolean] Show a divider between the header and body.
# @param visually_hide_title [Boolean] Visually hide the `title` while maintaining a label for assistive technologies.
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
def initialize(
id:,
title:,
subtitle: nil,
size: DEFAULT_SIZE,
divider: false,
visually_hide_title: false,
**system_arguments
)
@id = id
@title = title
@subtitle = subtitle
@visually_hide_title = visually_hide_title
@system_arguments = deny_tag_argument(**system_arguments)
@system_arguments[:tag] = :header
@system_arguments[:classes] = class_names(
"Overlay-header",
{ "Overlay-header--divided": divider },
SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, DEFAULT_SIZE)],
system_arguments[:classes]
)
end
end
end
end
end
Loading

0 comments on commit dbe2867

Please sign in to comment.