app/components/primer/alpha/tooltip.rb



# frozen_string_literal: true

module Primer
  module Alpha
    # `Tooltip` only appears on mouse hover or keyboard focus and contain a label or description text. Use tooltips sparingly and as a last resort.
    # Use tooltips as a last resort. Please consider [Tooltips alternatives](https://primer.style/design/accessibility/tooltip-alternatives).
    #
    # When using a tooltip, follow the provided guidelines to avoid accessibility issues:
    # - Tooltips should contain only **non-essential text**. Tooltips can easily be missed and are not accessible on touch devices so never use tooltips to convey critical information.
    # - `Tooltip` should be rendered through the API of <%= link_to_component(Primer::ButtonComponent)%>, <%= link_to_component(Primer::Beta::Link)%>, or <%= link_to_component(Primer::IconButton)%>. Avoid using `Tooltip` a standalone component unless absolutely necessary (and **only** on interactive elements).
    # - Tooltip text must be brief and concise even when used to display a description.
    # - Tooltips can only hold string content.
    # - Tooltips are not allowed on `disabled` elements because such elements are not keyboard-accessible. If you must set a tooltip on a disabled element, use `aria-disabled="true"` instead and programmatically disable the element.
    # - **Never set tooltips on static, non-interactive elements** like `span` or `div`. Tooltips should only be used on interactive elements like buttons or links to avoid excluding keyboard-only
    # and screen reader users. Use of tooltips through <%= link_to_component(Primer::Beta::Button) %>, <%= link_to_component(Primer::Beta::Link) %>, or <%= link_to_component(Primer::Beta::IconButton) %> will guarantee this.
    # - If you must use `Tooltip` as a standalone component, place it immediately after the trigger element in the DOM. This allows screen reader users to navigate to the tooltip and copy its contents if desired.
    #   content.
    #
    # Semantically, a tooltip will either act an accessible name or an accessible description for the element that it is associated with resulting in either a
    # `aria-labelledby` or an `aria-describedby` association. The `type` drastically changes semantics and screen reader behavior so follow these guidelines carefully:
    # - When there is already a visible label text on the trigger element, the tooltip is likely intended be supplementary, so set `type: :description`.
    # The majority of tooltips will fall under this category.
    # - When there is no visible text on the trigger element and the tooltip content is appropriate as a label for the element, set `type: :label`.
    # `label` type is usually only appropriate for an icon-only control.
    class Tooltip < Primer::Component
      DIRECTION_DEFAULT = :s
      DIRECTION_OPTIONS = [DIRECTION_DEFAULT, :n, :e, :w, :ne, :nw, :se, :sw].freeze

      TYPE_FALLBACK = :description
      TYPE_OPTIONS = [:label, :description].freeze

      attr_reader :id

      # @param for_id [String] The ID of the element that the tooltip should be attached to.
      # @param type [Symbol] <%= one_of(Primer::Alpha::Tooltip::TYPE_OPTIONS) %>
      # @param direction [Symbol] <%= one_of(Primer::Alpha::Tooltip::DIRECTION_OPTIONS) %>
      # @param text [String] The text content of the tooltip. This should be brief and no longer than a sentence.
      # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
      def initialize(type:, for_id:, text:, direction: DIRECTION_DEFAULT, **system_arguments)
        raise TypeError, "tooltip text must be a string" unless text.is_a?(String)

        @text = text
        @system_arguments = system_arguments
        @id = @system_arguments[:id] ||= self.class.generate_id
        @system_arguments[:tag] = :"tool-tip"
        @system_arguments[:for] = for_id
        @system_arguments[:popover] = "manual"
        @system_arguments[:classes] = class_names(
          @system_arguments[:classes],
          "sr-only"
        )
        @system_arguments[:position] = :absolute
        @system_arguments[:"data-direction"] = fetch_or_fallback(DIRECTION_OPTIONS, direction, DIRECTION_DEFAULT).to_s
        @system_arguments[:"data-type"] = fetch_or_fallback(TYPE_OPTIONS, type, TYPE_FALLBACK).to_s
      end

      def call
        render(Primer::BaseComponent.new(**@system_arguments)) { @text }
      end
    end
  end
end