Skip to content

Commit

Permalink
Merge pull request #167 from opf/code-maintenance/57090-extract-succe…
Browse files Browse the repository at this point in the history
…ssconfirmation-dialog-content-into-common-component

[57090] Extract success/confirmation dialog content into common component
  • Loading branch information
oliverguenther authored Sep 3, 2024
2 parents 2906031 + 55bfcf7 commit 599c8e6
Show file tree
Hide file tree
Showing 28 changed files with 461 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/popular-stingrays-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@openproject/primer-view-components': minor
---

Add `FeedbackDialog` and `FeedbackMessage` components to display different kinds of user feedback
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ lib/primer/forms/**/*.css
lib/primer/forms/**/*.css.json
lib/primer/forms/**/*.css.map
lib/primer/forms/**/*.d.ts
app/assets/
app/assets/*

# Generated by demo npm post-install
demo/app/assets/stylesheets/primer*
Expand Down Expand Up @@ -57,3 +57,6 @@ demo/app/assets/builds/

# IDE folders
.idea

# OpenProject custom rules
!app/assets/images
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions app/assets/images/loading_indicator.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions app/components/primer/open_project/feedback_dialog.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<%= render @dialog do |dialog| %>
<% dialog.with_body do %>
<%= feedback_message %>
<% if additional_content.present? %>
<%= additional_content %>
<% end %>
<% end %>
<% dialog.with_footer do %>
<% if footer.present? %>
<%= footer %>
<% else %>
<%= render(Primer::Beta::Button.new("data-close-dialog-id": @system_arguments[:id])) { I18n.t("button_close") } %>
<% end %>
<% end %>
<% end %>
64 changes: 64 additions & 0 deletions app/components/primer/open_project/feedback_dialog.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# frozen_string_literal: true

module Primer
module OpenProject
# A pre-configured dialog which includes the FeedbackMessage
class FeedbackDialog < Primer::Component
status :open_project

# A feedback message with some defaults that are necessary for rendering nicely
#
# @param heading [String] the heading for the success message
# @param description [String] the description for the success message
# @param icon_arguments [Hash] the system_arguments for the icon
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
renders_one :feedback_message, lambda { |icon_arguments: {}, **system_arguments|
system_arguments[:border] = false
Primer::OpenProject::FeedbackMessage.new(icon_arguments: icon_arguments, **system_arguments)
}

# Optional additional_content like a form input or toast.
#
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
renders_one :additional_content, lambda { |**system_arguments|
deny_tag_argument(**system_arguments)
system_arguments[:tag] = :div
system_arguments[:classes] = class_names(
system_arguments[:classes],
"FeedbackDialog-additionalContent"
)

system_arguments[:display] ||= :flex
system_arguments[:align_items] ||= :center
system_arguments[:justify_content] ||= :center
system_arguments[:mb] ||= 3

Primer::BaseComponent.new(**system_arguments)
}

renders_one :footer

# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
def initialize(**system_arguments)
@system_arguments = system_arguments
@system_arguments[:classes] = class_names(
system_arguments[:classes],
"FeedbackDialog"
)
@system_arguments[:id] ||= self.class.generate_id

@dialog = Primer::Alpha::Dialog.new(title: @system_arguments[:title], subtitle: nil, visually_hide_title: true, **@system_arguments)
end

delegate :header?, :header, :with_header, :with_header_content,
:show_button?, :show_button, :with_show_button, :with_show_button_content,
to: :@dialog

private

def before_render
content
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= render(@blankslate) %>
53 changes: 53 additions & 0 deletions app/components/primer/open_project/feedback_message.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# frozen_string_literal: true

module Primer
module OpenProject
# A view component for messages, inspired by the Primer Blankslate,
# which serves a different use-case (messages for when data is missing).
# We decided to wrap the Blankslate, because we don't want to have to adapt
# lots of different usages if Primer decides to change the Blankslate
# in a way that does not go well with our "misuse".
class FeedbackMessage < Primer::Component
status :open_project

# @param icon_arguments [Hash] special arguments for the icon
# @param loading [Boolean] Show a loading spinner instead of an icon
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
def initialize(icon_arguments: {}, loading: false, **system_arguments)
@system_arguments = system_arguments
@icon_arguments = icon_arguments
@system_arguments[:classes] = class_names(
system_arguments[:classes],
"FeedbackMessage"
)

@icon_arguments[:icon] ||= :"check-circle"
@icon_arguments[:color] ||= :success

@loading = loading

@blankslate = Primer::Beta::Blankslate.new(**@system_arguments)
end

delegate :description?, :description, :with_description, :with_description_content,
:heading?, :heading, :with_heading, :with_heading_content,
to: :@blankslate

private

def before_render
if @loading
@blankslate.with_visual_image(src: asset_path("loading_indicator.svg"), alt: I18n.t(:label_loading))
else
@blankslate.with_visual_icon(size: :medium, **@icon_arguments)
end

content
end

def render?
heading.present?
end
end
end
end
70 changes: 70 additions & 0 deletions previews/primer/open_project/feedback_dialog_preview.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# frozen_string_literal: true

# Setup Playground to use all available component props
# Setup Features to use individual component props and combinations

module Primer
module OpenProject
# @label FeedbackDialog
class FeedbackDialogPreview < ViewComponent::Preview
# @label Default
# @snapshot interactive
def default
render(Primer::OpenProject::FeedbackDialog.new(title: "Success dialog")) do |dialog|
dialog.with_show_button { "Click me" }
dialog.with_feedback_message do |message|
message.with_heading(tag: :h2) { "Success" }
message.with_description { "Great! Everything worked well." }
end
end
end

# @label Playground
# @param icon [Symbol] octicon
# @param icon_color [Symbol] select [default, muted, subtle, accent, success, attention, severe, danger, open, closed, done, sponsors, on_emphasis, inherit]
# @param loading_state [Boolean] toggle
# @param show_description toggle
# @param show_additional_content toggle
# @param custom_footer toggle
def playground(icon: :"check-circle", icon_color: :success, loading_state: false, show_description: true, show_additional_content: false, custom_footer: false)
render_with_template(locals: { icon: icon,
icon_color: icon_color,
loading_state: loading_state,
show_description: show_description,
show_additional_content: show_additional_content,
custom_footer: custom_footer })
end

# @label With additional content
def additional_content
render_with_template(locals: {})
end

# @label With custom icon
def custom_icon
render(Primer::OpenProject::FeedbackDialog.new(title: "Error message")) do |dialog|
dialog.with_show_button { "Click me" }
dialog.with_feedback_message(icon_arguments: { icon: :"x-circle", color: :danger }) do |message|
message.with_heading(tag: :h2) { "Ups, something went wrong" }
message.with_description { "Please try again or contact your administrator if the issue persists." }
end
end
end

# @label With custom footer
def custom_footer
render_with_template(locals: {})
end

# @label With loading spinner
def loading_spinner
render(Primer::OpenProject::FeedbackDialog.new(title: "Waiting...")) do |dialog|
dialog.with_show_button { "Click me" }
dialog.with_feedback_message(loading: true) do |message|
message.with_heading(tag: :h2) { "Please wait, your request is being processed." }
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<%= render(Primer::OpenProject::FeedbackDialog.new) do |dialog| %>
<% dialog.with_show_button { "Click me" } %>
<% dialog.with_feedback_message do |message| %>
<% message.with_heading(tag: :h2).with_content("Action successful") %>
<% end %>
<% dialog.with_additional_content do %>
<% render(Primer::Beta::Text.new) { "You can render whatever component you want here." } %>
<% end %>
<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<%= render(Primer::OpenProject::FeedbackDialog.new(id: "my-dialog")) do |dialog| %>
<% dialog.with_show_button { "Click me" } %>
<% dialog.with_feedback_message do |message| %>
<% message.with_heading(tag: :h2).with_content("Action successful") %>
<% end %>
<% dialog.with_footer do %>
<%= render(Primer::Beta::Button.new("data-close-dialog-id": "my-dialog")) { "Cancel" } %>
<%= render(Primer::Beta::Button.new(scheme: :primary)) { "Accept" } %>
<% end %>
<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<%= render(Primer::OpenProject::FeedbackDialog.new(id: "my-dialog")) do |dialog| %>
<% dialog.with_show_button { "Click me" } %>
<% dialog.with_feedback_message(icon_arguments: { icon: icon, color: icon_color }, loading: loading_state) do |message| %>
<% message.with_heading(tag: :h2).with_content("Awesome!") %>
<% message.with_description { "Great! Everything worked well." } if show_description %>
<% end %>
<% if show_additional_content %>
<% dialog.with_additional_content(display: :inline) do %>
<%= render(Primer::Alpha::Banner.new) { "Some additional content below" } %>
<% end %>
<% end %>
<% if custom_footer %>
<% dialog.with_footer do %>
<%= render(Primer::Beta::Button.new("data-close-dialog-id": "my-dialog")) { "Cancel" } %>
<%= render(Primer::Beta::Button.new(scheme: :primary)) { "Accept" } %>
<% end %>
<% end %>
<% end %>
58 changes: 58 additions & 0 deletions previews/primer/open_project/feedback_message_preview.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true

module Primer
module OpenProject
# @label FeedbackMessage
class FeedbackMessagePreview < ViewComponent::Preview
# @label Default
# @snapshot
def default
render Primer::OpenProject::FeedbackMessage.new do |component|
component.with_heading(tag: :h2) { "Success" }
component.with_description { "You successfully created your first FeedbackMessage component" }
end
end


# @label Playground
#
# @param icon [Symbol] octicon
# @param icon_color [Symbol] select [default, muted, subtle, accent, success, attention, severe, danger, open, closed, done, sponsors, on_emphasis, inherit]
# @param loading_state [Boolean] toggle
# @param title [String]
# @param description [String]
# @param narrow [Boolean] toggle
# @param spacious [Boolean] toggle
# @param border [Boolean] toggle
def playground(icon: "check-circle", icon_color: :success, loading_state: false, title: "Yeah!", description: "Some description below...", narrow: false, spacious: false, border: false)
render Primer::OpenProject::FeedbackMessage.new(icon_arguments: { icon: icon, color: icon_color}, loading: loading_state, narrow: narrow, spacious: spacious, border: border) do |component|
component.with_heading(tag: :h2).with_content(title)
component.with_description { description }
end
end

# @label With custom icon
def with_custom_icon
render Primer::OpenProject::FeedbackMessage.new(icon_arguments: { icon: :"op-enterprise-addons", classes: "upsale-colored" }) do |component|
component.with_heading(tag: :h2) { "You are a hero" }
component.with_description { "Thanks for supporting an open source project!" }
end
end

# @label With custom color
def with_custom_color
render Primer::OpenProject::FeedbackMessage.new(icon_arguments: { icon: :"x-circle", color: :danger }) do |component|
component.with_heading(tag: :h2) { "Ups, something went wrong" }
component.with_description { "Please try again or contact your administrator." }
end
end

# @label With loading spinner
def loading_spinner
render(Primer::OpenProject::FeedbackMessage.new(loading: true)) do |component|
component.with_heading(tag: :h2) { "Please wait, your request is being processed." }
end
end
end
end
end
8 changes: 8 additions & 0 deletions test/components/component_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ class PrimerComponentTest < Minitest::Test

# Components with any arguments necessary to make them render
COMPONENTS_WITH_ARGS = [
[Primer::OpenProject::FeedbackDialog, {}, proc { |component|
component.with_feedback_message do |feedback|
feedback.with_heading(tag: :h2) { "You are a hero" }
end
}],
[Primer::OpenProject::FeedbackMessage, {}, proc { |component|
component.with_heading(tag: :h2) { "Foo" }
}],
[Primer::OpenProject::SidePanel, {}, proc { |component|
component.with_section do |section|
section.with_title { "First" }
Expand Down
Loading

0 comments on commit 599c8e6

Please sign in to comment.