# frozen_string_literal: true module Primer module Alpha class ActionList # An individual `ActionList` item. Items can optionally include leading and/or trailing visuals, # such as icons, avatars, and counters. class Item < Primer::Component DEFAULT_SIZE = :medium SIZE_MAPPINGS = { DEFAULT_SIZE => nil, :large => "ActionListContent--sizeLarge", :xlarge => "ActionListContent--sizeXLarge" }.freeze SIZE_OPTIONS = SIZE_MAPPINGS.keys.freeze DEFAULT_DESCRIPTION_SCHEME = :block DESCRIPTION_SCHEME_MAPPINGS = { :inline => "ActionListItem-descriptionWrap--inline", DEFAULT_DESCRIPTION_SCHEME => "ActionListItem-descriptionWrap" }.freeze DESCRIPTION_SCHEME_OPTIONS = DESCRIPTION_SCHEME_MAPPINGS.keys.freeze DEFAULT_SCHEME = :default SCHEME_MAPPINGS = { DEFAULT_SCHEME => nil, :danger => "ActionListItem--danger" }.freeze SCHEME_OPTIONS = SCHEME_MAPPINGS.keys.freeze # Description content that complements the item's label. See `ActionList`'s `description_scheme` argument # for layout options. renders_one :description # An icon, avatar, SVG, or custom content that will render to the left of the label. # # To render an icon, call the `with_leading_visual_icon` method, which accepts the arguments accepted by <%= link_to_component(Primer::Beta::Octicon) %>. # # To render an SVG, call the `with_leading_visual_svg` method. # # To render custom content, call the `with_leading_visual_content` method and pass a block that returns a string. renders_one :leading_visual, types: { icon: lambda { |**system_arguments, &block| deny_aria_key( :label, "Avoid using `aria-label` on leading visual icons, as they are purely decorative.", **system_arguments ) Primer::Beta::Octicon.new(**system_arguments, &block) }, avatar: lambda { |*| return unless should_raise_error? raise "Leading visual avatars are no longer supported. Please use the #with_avatar_item slot instead." }, svg: lambda { |**system_arguments| Primer::BaseComponent.new(tag: :svg, width: "16", height: "16", **system_arguments) }, content: lambda { |**system_arguments| Primer::BaseComponent.new(tag: :span, **system_arguments) }, raw_content: nil } # Used internally. # # @private renders_one :private_leading_action_icon, Primer::Beta::Octicon # An icon, label, counter, or text to render to the right of the label. # # To render an icon, call the `with_leading_visual_icon` method, which accepts the arguments accepted by <%= link_to_component(Primer::Beta::Octicon) %>. # # To render a label, call the `with_leading_visual_label` method, which accepts the arguments accepted by <%= link_to_component(Primer::Beta::Label) %>. # # To render a counter, call the `with_leading_visual_counter` method, which accepts the arguments accepted by <%= link_to_component(Primer::Beta::Counter) %>. # # To render text, call the `with_leading_visual_text` method and pass a block that returns a string. Eg: # ```ruby # with_leading_visual_text { "Text here" } # ``` renders_one :trailing_visual, types: { icon: Primer::Beta::Octicon, label: Primer::Beta::Label, counter: Primer::Beta::Counter, text: ->(text) { text } } # Used internally. # # @private renders_one :private_trailing_action_icon, Primer::Beta::Octicon # A button rendered after the trailing icon that can be used to show a menu, activate # a dialog, etc. # # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Beta::IconButton) %>. renders_one :trailing_action, lambda { |**system_arguments| Primer::Beta::IconButton.new( scheme: :invisible, classes: class_names( system_arguments[:classes], "ActionListItem-trailingAction" ), **system_arguments ) } # `Tooltip` that appears on mouse hover or keyboard focus over the trailing action button. Use tooltips sparingly and as # a last resort. **Important:** This tooltip defaults to `type: :description`. In a few scenarios, `type: :label` may be # more appropriate. Consult the <%= link_to_component(Primer::Alpha::Tooltip) %> documentation for more information. # # @param type [Symbol] (:description) <%= one_of(Primer::Alpha::Tooltip::TYPE_OPTIONS) %> # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Alpha::Tooltip) %>. renders_one :tooltip, lambda { |**system_arguments| raise ArgumentError, "Buttons with a tooltip must have a unique `id` set on the `Button`." if @id.blank? && !Rails.env.production? system_arguments[:for_id] = @id system_arguments[:type] ||= :description Primer::Alpha::Tooltip.new(**system_arguments) } # Used internally. # # @private renders_one :private_content attr_reader :id, :item_id, :list, :href, :active, :disabled, :parent # Whether or not this item is active. # # @return [Boolean] alias active? active # Whether or not this item is disabled. # # @return [Boolean] alias disabled? disabled # @param list [Primer::Alpha::ActionList] The list that contains this item. Used internally. # @param parent [Primer::Alpha::ActionList::Item] This item's parent item. `nil` if this item is at the root. Used internally. # @param label [String] Item label. If no label is provided, content is used. # @param item_id [String] An ID that will be attached to the item's `
  • ` element as `data-item-id` for distinguishing between items, perhaps in JavaScript. # @param label_classes [String] CSS classes that will be added to the label. # @param label_arguments [Hash] <%= link_to_system_arguments_docs %> used to construct the label. # @param content_arguments [Hash] <%= link_to_system_arguments_docs %> used to construct the item's anchor or button tag. # @param form_arguments [Hash] Allows the item to submit a form on click. The URL passed in the `href:` option will be used as the form action. Pass the `method:` option to this hash to control what kind of request is made, <%= one_of(Primer::Alpha::ActionList::FormWrapper::HTTP_METHOD_OPTIONS) %> The `name:` option is required and specifies the desired name of the field that will be included in the params sent to the server on form submission. Specify the `value:` option to send a custom value to the server; otherwise the value of `name:` is sent. # @param truncate_label [Boolean] Truncate label with ellipsis. # @param href [String] Link URL. # @param role [String] ARIA role describing the function of the item. # @param size [Symbol] Controls block sizing of the item. # @param scheme [Symbol] Controls color/style based on behavior. # @param disabled [Boolean] Disabled items are not clickable and visually dim. # @param description_scheme [Symbol] Display description inline with label, or block on the next line. <%= one_of(Primer::Alpha::ActionList::Item::DESCRIPTION_SCHEME_OPTIONS) %> # @param active [Boolean] If the parent list's `select_variant` is set to `:single` or `:multiple`, causes this item to render checked. # @param on_click [String] JavaScript to execute when the item is clicked. # @param id [String] Used internally. # @param system_arguments [Hash] <%= link_to_system_arguments_docs %> def initialize( list:, label: nil, item_id: nil, label_classes: nil, label_arguments: {}, content_arguments: {}, form_arguments: {}, parent: nil, truncate_label: false, href: nil, role: nil, size: DEFAULT_SIZE, scheme: DEFAULT_SCHEME, disabled: false, description_scheme: DEFAULT_DESCRIPTION_SCHEME, active: false, on_click: nil, id: self.class.generate_id, **system_arguments ) @list = list @parent = parent @label = label @item_id = item_id @href = href || content_arguments[:href] @truncate_label = truncate_label @disabled = disabled @active = active @id = id @system_arguments = system_arguments @content_arguments = content_arguments @form_wrapper = FormWrapper.new(list: @list, action: @href, **form_arguments) @size = fetch_or_fallback(SIZE_OPTIONS, size, DEFAULT_SIZE) @scheme = fetch_or_fallback(SCHEME_OPTIONS, scheme, DEFAULT_SCHEME) @description_scheme = fetch_or_fallback( DESCRIPTION_SCHEME_OPTIONS, description_scheme, DEFAULT_DESCRIPTION_SCHEME ) @system_arguments[:classes] = class_names( @system_arguments[:classes], SCHEME_MAPPINGS[@scheme], "ActionListItem", "ActionListItem--disabled" => @disabled ) @system_arguments[:data] ||= {} @system_arguments[:data][:targets] = "#{list_class.custom_element_name}.items" @system_arguments[:data] = merge_data( @system_arguments, { data: { targets: "#{list_class.custom_element_name}.items", **(@item_id ? { item_id: @item_id } : {}) } } ) @label_arguments = { **label_arguments, classes: class_names( label_classes, label_arguments[:classes], "ActionListItem-label", "ActionListItem-label--truncate" => @truncate_label ) } @content_arguments[:id] = @id @content_arguments[:classes] = class_names( @content_arguments[:classes], "ActionListContent", SIZE_MAPPINGS[@size] ) unless @content_arguments[:tag] if @href && @form_wrapper.get? && !@disabled @content_arguments[:tag] = :a @content_arguments[:href] = @href else @content_arguments[:tag] = :button @content_arguments[:type] = @form_wrapper.form_required? ? :submit : :button @content_arguments[:onclick] = on_click if on_click end end if @content_arguments[:tag] != :button && @form_wrapper.form_required? raise ArgumentError, "items that submit forms must use a \"button\" tag instead of \"#{@content_arguments[:tag]}\"" end if @content_arguments[:tag] != :button && @list.acts_as_form_input? raise ArgumentError, "items within lists/menus that act as form inputs must use \"button\" tags instead of \"#{@content_arguments[:tag]}\"" end if @disabled @content_arguments[:aria] ||= merge_aria( @content_arguments, { aria: { disabled: "true" } } ) end @content_arguments[:role] = role || if @list.allows_selection? ActionList::SELECT_VARIANT_ROLE_MAP[@list.select_variant] elsif @list.acts_as_menu? ActionList::DEFAULT_MENU_ITEM_ROLE end @system_arguments[:role] = @list.acts_as_menu? ? :none : nil @description_wrapper_arguments = { classes: class_names( "ActionListItem-descriptionWrap", DESCRIPTION_SCHEME_MAPPINGS[@description_scheme] ) } end private def before_render if @list.allows_selection? @content_arguments[:aria] = merge_aria( @content_arguments, { aria: { checked: active? } } ) end @system_arguments[:classes] = class_names( @system_arguments[:classes], "ActionListItem--withActions" => trailing_action.present? ) return unless leading_visual @content_arguments[:classes] = class_names( @content_arguments[:classes], "ActionListContent--visual16" => leading_visual, "ActionListContent--blockDescription" => description && @description_scheme == :block ) end def list_class Primer::Alpha::ActionList end end end end end