primer_view_components 0.0.17 → 0.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +102 -0
- data/app/assets/javascripts/primer_view_components.js +2 -0
- data/app/assets/javascripts/primer_view_components.js.map +1 -0
- data/app/components/primer/avatar_component.rb +27 -9
- data/app/components/primer/avatar_stack_component.html.erb +10 -0
- data/app/components/primer/avatar_stack_component.rb +81 -0
- data/app/components/primer/base_component.rb +8 -5
- data/app/components/primer/blankslate_component.html.erb +3 -3
- data/app/components/primer/blankslate_component.rb +18 -25
- data/app/components/primer/border_box_component.html.erb +4 -18
- data/app/components/primer/border_box_component.rb +75 -68
- data/app/components/primer/box_component.rb +10 -0
- data/app/components/primer/breadcrumb_component.rb +3 -2
- data/app/components/primer/button_component.rb +3 -3
- data/app/components/primer/button_group_component.html.erb +5 -0
- data/app/components/primer/button_group_component.rb +37 -0
- data/app/components/primer/button_marketing_component.rb +73 -0
- data/app/components/primer/component.rb +16 -0
- data/app/components/primer/counter_component.rb +16 -9
- data/app/components/primer/details_component.html.erb +2 -6
- data/app/components/primer/details_component.rb +28 -37
- data/app/components/primer/dropdown/menu_component.html.erb +12 -0
- data/app/components/primer/dropdown/menu_component.rb +48 -0
- data/app/components/primer/dropdown_component.html.erb +9 -0
- data/app/components/primer/dropdown_component.rb +75 -0
- data/app/components/primer/dropdown_menu_component.rb +35 -3
- data/app/components/primer/flash_component.html.erb +4 -7
- data/app/components/primer/flash_component.rb +18 -17
- data/app/components/primer/flex_component.rb +47 -9
- data/app/components/primer/flex_item_component.rb +16 -1
- data/app/components/primer/heading_component.rb +9 -0
- data/app/components/primer/label_component.rb +6 -6
- data/app/components/primer/layout_component.rb +2 -2
- data/app/components/primer/link_component.rb +6 -2
- data/app/components/primer/markdown_component.rb +293 -0
- data/app/components/primer/menu_component.html.erb +6 -0
- data/app/components/primer/menu_component.rb +71 -0
- data/app/components/primer/octicon_component.rb +13 -6
- data/app/components/primer/popover_component.rb +5 -3
- data/app/components/primer/primer.js +1 -0
- data/app/components/primer/primer.ts +1 -0
- data/app/components/primer/progress_bar_component.rb +6 -6
- data/app/components/primer/spinner_component.rb +8 -5
- data/app/components/primer/state_component.rb +23 -12
- data/app/components/primer/subhead_component.rb +6 -3
- data/app/components/primer/tab_container_component.js +1 -0
- data/app/components/primer/tab_container_component.rb +41 -0
- data/app/components/primer/tab_container_component.ts +1 -0
- data/app/components/primer/tab_nav_component.html.erb +17 -0
- data/app/components/primer/tab_nav_component.rb +108 -0
- data/app/components/primer/text_component.rb +1 -1
- data/app/components/primer/timeline_item_component.html.erb +4 -16
- data/app/components/primer/timeline_item_component.rb +41 -49
- data/app/components/primer/tooltip_component.rb +88 -0
- data/app/components/primer/truncate_component.rb +41 -0
- data/app/components/primer/underline_nav_component.rb +26 -1
- data/{lib → app/lib}/primer/class_name_helper.rb +1 -0
- data/app/lib/primer/classify.rb +280 -0
- data/app/lib/primer/classify/cache.rb +125 -0
- data/{lib → app/lib}/primer/fetch_or_fallback_helper.rb +1 -0
- data/{lib → app/lib}/primer/join_style_arguments_helper.rb +1 -0
- data/app/lib/primer/view_helper.rb +22 -0
- data/app/lib/primer/view_helper/dsl.rb +34 -0
- data/lib/primer/view_components.rb +32 -0
- data/lib/primer/view_components/engine.rb +11 -2
- data/lib/primer/view_components/version.rb +5 -1
- data/lib/yard/renders_many_handler.rb +19 -0
- data/lib/yard/renders_one_handler.rb +19 -0
- data/static/statuses.json +1 -0
- metadata +94 -24
- data/app/components/primer/view_components.rb +0 -52
- data/lib/primer/classify.rb +0 -250
| @@ -3,6 +3,8 @@ | |
| 3 3 | 
             
            module Primer
         | 
| 4 4 | 
             
              # Renders an [Octicon](https://primer.style/octicons/) with <%= link_to_system_arguments_docs %>.
         | 
| 5 5 | 
             
              class OcticonComponent < Primer::Component
         | 
| 6 | 
            +
                view_helper :octicon
         | 
| 7 | 
            +
             | 
| 6 8 | 
             
                include Primer::ClassNameHelper
         | 
| 7 9 | 
             
                include OcticonsHelper
         | 
| 8 10 |  | 
| @@ -10,24 +12,25 @@ module Primer | |
| 10 12 | 
             
                SIZE_MAPPINGS = {
         | 
| 11 13 | 
             
                  SIZE_DEFAULT => 16,
         | 
| 12 14 | 
             
                  :medium => 32,
         | 
| 13 | 
            -
                  :large => 64 | 
| 15 | 
            +
                  :large => 64
         | 
| 14 16 | 
             
                }.freeze
         | 
| 15 17 | 
             
                SIZE_OPTIONS = SIZE_MAPPINGS.keys
         | 
| 16 18 |  | 
| 17 | 
            -
                # @example  | 
| 19 | 
            +
                # @example Default
         | 
| 18 20 | 
             
                #   <%= render(Primer::OcticonComponent.new(icon: "check")) %>
         | 
| 19 21 | 
             
                #
         | 
| 20 | 
            -
                # @example  | 
| 22 | 
            +
                # @example Medium
         | 
| 21 23 | 
             
                #   <%= render(Primer::OcticonComponent.new(icon: "people", size: :medium)) %>
         | 
| 22 24 | 
             
                #
         | 
| 23 | 
            -
                # @example  | 
| 25 | 
            +
                # @example Large
         | 
| 24 26 | 
             
                #   <%= render(Primer::OcticonComponent.new(icon: "x", size: :large)) %>
         | 
| 25 27 | 
             
                #
         | 
| 26 28 | 
             
                # @param icon [String] Name of [Octicon](https://primer.style/octicons/) to use.
         | 
| 27 29 | 
             
                # @param size [Symbol] <%= one_of(Primer::OcticonComponent::SIZE_MAPPINGS) %>
         | 
| 28 30 | 
             
                # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
         | 
| 29 31 | 
             
                def initialize(icon:, size: SIZE_DEFAULT, **system_arguments)
         | 
| 30 | 
            -
                  @icon | 
| 32 | 
            +
                  @icon = icon
         | 
| 33 | 
            +
                  @system_arguments = system_arguments
         | 
| 31 34 |  | 
| 32 35 | 
             
                  @system_arguments[:class] = Primer::Classify.call(**@system_arguments)[:class]
         | 
| 33 36 | 
             
                  @system_arguments[:height] ||= SIZE_MAPPINGS[size]
         | 
| @@ -39,7 +42,11 @@ module Primer | |
| 39 42 | 
             
                end
         | 
| 40 43 |  | 
| 41 44 | 
             
                def call
         | 
| 42 | 
            -
                  octicon(@icon, **@system_arguments)
         | 
| 45 | 
            +
                  octicon(@icon, { **@system_arguments })
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def self.status
         | 
| 49 | 
            +
                  Primer::Component::STATUSES[:beta]
         | 
| 43 50 | 
             
                end
         | 
| 44 51 | 
             
              end
         | 
| 45 52 | 
             
            end
         | 
| @@ -10,7 +10,7 @@ module Primer | |
| 10 10 | 
             
                with_slot :heading, class_name: "Heading"
         | 
| 11 11 | 
             
                with_slot :body, class_name: "Body"
         | 
| 12 12 |  | 
| 13 | 
            -
                # @example  | 
| 13 | 
            +
                # @example Default
         | 
| 14 14 | 
             
                #   <%= render Primer::PopoverComponent.new do |component| %>
         | 
| 15 15 | 
             
                #     <% component.slot(:heading) do %>
         | 
| 16 16 | 
             
                #       Activity feed
         | 
| @@ -20,7 +20,7 @@ module Primer | |
| 20 20 | 
             
                #     <% end %>
         | 
| 21 21 | 
             
                #   <% end %>
         | 
| 22 22 | 
             
                #
         | 
| 23 | 
            -
                # @example  | 
| 23 | 
            +
                # @example Large
         | 
| 24 24 | 
             
                #   <%= render Primer::PopoverComponent.new do |component| %>
         | 
| 25 25 | 
             
                #     <% component.slot(:heading) do %>
         | 
| 26 26 | 
             
                #       Activity feed
         | 
| @@ -30,7 +30,7 @@ module Primer | |
| 30 30 | 
             
                #     <% end %>
         | 
| 31 31 | 
             
                #   <% end %>
         | 
| 32 32 | 
             
                #
         | 
| 33 | 
            -
                # @example  | 
| 33 | 
            +
                # @example Caret position
         | 
| 34 34 | 
             
                #   <%= render Primer::PopoverComponent.new do |component| %>
         | 
| 35 35 | 
             
                #     <% component.slot(:heading) do %>
         | 
| 36 36 | 
             
                #       Activity feed
         | 
| @@ -57,6 +57,7 @@ module Primer | |
| 57 57 | 
             
                  body.present?
         | 
| 58 58 | 
             
                end
         | 
| 59 59 |  | 
| 60 | 
            +
                # :nodoc:
         | 
| 60 61 | 
             
                class Heading < Primer::Slot
         | 
| 61 62 | 
             
                  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
         | 
| 62 63 | 
             
                  def initialize(**system_arguments)
         | 
| @@ -70,6 +71,7 @@ module Primer | |
| 70 71 | 
             
                  end
         | 
| 71 72 | 
             
                end
         | 
| 72 73 |  | 
| 74 | 
            +
                # :nodoc:
         | 
| 73 75 | 
             
                class Body < Slot
         | 
| 74 76 | 
             
                  CARET_DEFAULT = :top
         | 
| 75 77 | 
             
                  CARET_MAPPINGS = {
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            import './tab_container_component';
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            import './tab_container_component'
         | 
| @@ -12,26 +12,26 @@ module Primer | |
| 12 12 | 
             
                SIZE_MAPPINGS = {
         | 
| 13 13 | 
             
                  SIZE_DEFAULT => "",
         | 
| 14 14 | 
             
                  :small => "Progress--small",
         | 
| 15 | 
            -
                  :large => "Progress--large" | 
| 15 | 
            +
                  :large => "Progress--large"
         | 
| 16 16 | 
             
                }.freeze
         | 
| 17 17 |  | 
| 18 18 | 
             
                SIZE_OPTIONS = SIZE_MAPPINGS.keys
         | 
| 19 | 
            -
                # @example  | 
| 19 | 
            +
                # @example Default
         | 
| 20 20 | 
             
                #   <%= render(Primer::ProgressBarComponent.new) do |component| %>
         | 
| 21 21 | 
             
                #     <% component.slot(:item, percentage: 25) %>
         | 
| 22 22 | 
             
                #   <% end %>
         | 
| 23 23 | 
             
                #
         | 
| 24 | 
            -
                # @example  | 
| 24 | 
            +
                # @example Small
         | 
| 25 25 | 
             
                #   <%= render(Primer::ProgressBarComponent.new(size: :small)) do |component| %>
         | 
| 26 26 | 
             
                #     <% component.slot(:item, bg: :blue_4, percentage: 50) %>
         | 
| 27 27 | 
             
                #   <% end %>
         | 
| 28 28 | 
             
                #
         | 
| 29 | 
            -
                # @example  | 
| 29 | 
            +
                # @example Large
         | 
| 30 30 | 
             
                #   <%= render(Primer::ProgressBarComponent.new(size: :large)) do |component| %>
         | 
| 31 31 | 
             
                #     <% component.slot(:item, bg: :red_4, percentage: 75) %>
         | 
| 32 32 | 
             
                #   <% end %>
         | 
| 33 33 | 
             
                #
         | 
| 34 | 
            -
                # @example  | 
| 34 | 
            +
                # @example Multiple items
         | 
| 35 35 | 
             
                #   <%= render(Primer::ProgressBarComponent.new) do |component| %>
         | 
| 36 36 | 
             
                #     <% component.slot(:item, percentage: 10) %>
         | 
| 37 37 | 
             
                #     <% component.slot(:item, bg: :blue_4, percentage: 20) %>
         | 
| @@ -48,13 +48,13 @@ module Primer | |
| 48 48 | 
             
                    SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, SIZE_DEFAULT)]
         | 
| 49 49 | 
             
                  )
         | 
| 50 50 | 
             
                  @system_arguments[:tag] = :span
         | 
| 51 | 
            -
             | 
| 52 51 | 
             
                end
         | 
| 53 52 |  | 
| 54 53 | 
             
                def render?
         | 
| 55 54 | 
             
                  items.any?
         | 
| 56 55 | 
             
                end
         | 
| 57 56 |  | 
| 57 | 
            +
                # :nodoc:
         | 
| 58 58 | 
             
                class Item < Primer::Slot
         | 
| 59 59 | 
             
                  attr_reader :system_arguments
         | 
| 60 60 |  | 
| @@ -3,12 +3,11 @@ | |
| 3 3 | 
             
            module Primer
         | 
| 4 4 | 
             
              # Use Primer::SpinnerComponent to let users know that content is being loaded.
         | 
| 5 5 | 
             
              class SpinnerComponent < Primer::Component
         | 
| 6 | 
            -
             | 
| 7 6 | 
             
                DEFAULT_SIZE = :medium
         | 
| 8 7 | 
             
                SIZE_MAPPINGS = {
         | 
| 9 8 | 
             
                  :small => 16,
         | 
| 10 9 | 
             
                  DEFAULT_SIZE => 32,
         | 
| 11 | 
            -
                  :large => 64 | 
| 10 | 
            +
                  :large => 64
         | 
| 12 11 | 
             
                }.freeze
         | 
| 13 12 | 
             
                SIZE_OPTIONS = SIZE_MAPPINGS.keys
         | 
| 14 13 | 
             
                # Setting `box-sizing: content-box` allows consumers to add padding
         | 
| @@ -16,13 +15,13 @@ module Primer | |
| 16 15 | 
             
                DEFAULT_STYLE = "box-sizing: content-box; color: var(--color-icon-primary);"
         | 
| 17 16 |  | 
| 18 17 | 
             
                #
         | 
| 19 | 
            -
                # @example  | 
| 18 | 
            +
                # @example Default
         | 
| 20 19 | 
             
                #   <%= render(Primer::SpinnerComponent.new) %>
         | 
| 21 20 | 
             
                #
         | 
| 22 | 
            -
                # @example  | 
| 21 | 
            +
                # @example Small
         | 
| 23 22 | 
             
                #   <%= render(Primer::SpinnerComponent.new(size: :small)) %>
         | 
| 24 23 | 
             
                #
         | 
| 25 | 
            -
                # @example  | 
| 24 | 
            +
                # @example Large
         | 
| 26 25 | 
             
                #   <%= render(Primer::SpinnerComponent.new(size: :large)) %>
         | 
| 27 26 | 
             
                #
         | 
| 28 27 | 
             
                # @param size [Symbol] <%= one_of(Primer::SpinnerComponent::SIZE_MAPPINGS) %>
         | 
| @@ -35,5 +34,9 @@ module Primer | |
| 35 34 | 
             
                  @system_arguments[:viewBox] = "0 0 16 16"
         | 
| 36 35 | 
             
                  @system_arguments[:fill] = :none
         | 
| 37 36 | 
             
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def self.status
         | 
| 39 | 
            +
                  Primer::Component::STATUSES[:beta]
         | 
| 40 | 
            +
                end
         | 
| 38 41 | 
             
              end
         | 
| 39 42 | 
             
            end
         | 
| @@ -4,34 +4,41 @@ module Primer | |
| 4 4 | 
             
              # Component for rendering the status of an item.
         | 
| 5 5 | 
             
              class StateComponent < Primer::Component
         | 
| 6 6 | 
             
                COLOR_DEFAULT = :default
         | 
| 7 | 
            -
                 | 
| 7 | 
            +
                NEW_COLOR_MAPPINGS = {
         | 
| 8 | 
            +
                  open: "State--open",
         | 
| 9 | 
            +
                  closed: "State--closed",
         | 
| 10 | 
            +
                  merged: "State--merged"
         | 
| 11 | 
            +
                }.freeze
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                DEPRECATED_COLOR_MAPPINGS = {
         | 
| 8 14 | 
             
                  COLOR_DEFAULT => "",
         | 
| 9 | 
            -
                  :green => "State-- | 
| 10 | 
            -
                  :red => "State-- | 
| 11 | 
            -
                  :purple => "State-- | 
| 15 | 
            +
                  :green => "State--open",
         | 
| 16 | 
            +
                  :red => "State--closed",
         | 
| 17 | 
            +
                  :purple => "State--merged"
         | 
| 12 18 | 
             
                }.freeze
         | 
| 19 | 
            +
                COLOR_MAPPINGS = NEW_COLOR_MAPPINGS.merge(DEPRECATED_COLOR_MAPPINGS)
         | 
| 13 20 | 
             
                COLOR_OPTIONS = COLOR_MAPPINGS.keys
         | 
| 14 21 |  | 
| 15 22 | 
             
                SIZE_DEFAULT = :default
         | 
| 16 23 | 
             
                SIZE_MAPPINGS = {
         | 
| 17 24 | 
             
                  SIZE_DEFAULT => "",
         | 
| 18 | 
            -
                  :small => "State--small" | 
| 25 | 
            +
                  :small => "State--small"
         | 
| 19 26 | 
             
                }.freeze
         | 
| 20 27 | 
             
                SIZE_OPTIONS = SIZE_MAPPINGS.keys
         | 
| 21 28 |  | 
| 22 29 | 
             
                TAG_DEFAULT = :span
         | 
| 23 | 
            -
                TAG_OPTIONS = [TAG_DEFAULT, :div, :a]
         | 
| 30 | 
            +
                TAG_OPTIONS = [TAG_DEFAULT, :div, :a].freeze
         | 
| 24 31 |  | 
| 25 | 
            -
                # @example  | 
| 32 | 
            +
                # @example Default
         | 
| 26 33 | 
             
                #   <%= render(Primer::StateComponent.new(title: "title")) { "State" } %>
         | 
| 27 34 | 
             
                #
         | 
| 28 | 
            -
                # @example  | 
| 35 | 
            +
                # @example Colors
         | 
| 29 36 | 
             
                #   <%= render(Primer::StateComponent.new(title: "title")) { "Default" } %>
         | 
| 30 | 
            -
                #   <%= render(Primer::StateComponent.new(title: "title", color: : | 
| 31 | 
            -
                #   <%= render(Primer::StateComponent.new(title: "title", color: : | 
| 32 | 
            -
                #   <%= render(Primer::StateComponent.new(title: "title", color: : | 
| 37 | 
            +
                #   <%= render(Primer::StateComponent.new(title: "title", color: :open)) { "Open" } %>
         | 
| 38 | 
            +
                #   <%= render(Primer::StateComponent.new(title: "title", color: :closed)) { "Closed" } %>
         | 
| 39 | 
            +
                #   <%= render(Primer::StateComponent.new(title: "title", color: :merged)) { "Merged" } %>
         | 
| 33 40 | 
             
                #
         | 
| 34 | 
            -
                # @example  | 
| 41 | 
            +
                # @example Sizes
         | 
| 35 42 | 
             
                #   <%= render(Primer::StateComponent.new(title: "title")) { "Default" } %>
         | 
| 36 43 | 
             
                #   <%= render(Primer::StateComponent.new(title: "title", size: :small)) { "Small" } %>
         | 
| 37 44 | 
             
                #
         | 
| @@ -61,5 +68,9 @@ module Primer | |
| 61 68 | 
             
                def call
         | 
| 62 69 | 
             
                  render(Primer::BaseComponent.new(**@system_arguments)) { content }
         | 
| 63 70 | 
             
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                def self.status
         | 
| 73 | 
            +
                  Primer::Component::STATUSES[:beta]
         | 
| 74 | 
            +
                end
         | 
| 64 75 | 
             
              end
         | 
| 65 76 | 
             
            end
         | 
| @@ -9,7 +9,7 @@ module Primer | |
| 9 9 | 
             
                with_slot :actions, class_name: "Actions"
         | 
| 10 10 | 
             
                with_slot :description, class_name: "Description"
         | 
| 11 11 |  | 
| 12 | 
            -
                # @example  | 
| 12 | 
            +
                # @example Default
         | 
| 13 13 | 
             
                #   <%= render(Primer::SubheadComponent.new) do |component| %>
         | 
| 14 14 | 
             
                #     <% component.slot(:heading) do %>
         | 
| 15 15 | 
             
                #       My Heading
         | 
| @@ -19,7 +19,7 @@ module Primer | |
| 19 19 | 
             
                #     <% end %>
         | 
| 20 20 | 
             
                #   <% end %>
         | 
| 21 21 | 
             
                #
         | 
| 22 | 
            -
                # @example  | 
| 22 | 
            +
                # @example Without border
         | 
| 23 23 | 
             
                #   <%= render(Primer::SubheadComponent.new(hide_border: true)) do |component| %>
         | 
| 24 24 | 
             
                #     <% component.slot(:heading) do %>
         | 
| 25 25 | 
             
                #       My Heading
         | 
| @@ -29,7 +29,7 @@ module Primer | |
| 29 29 | 
             
                #     <% end %>
         | 
| 30 30 | 
             
                #   <% end %>
         | 
| 31 31 | 
             
                #
         | 
| 32 | 
            -
                # @example  | 
| 32 | 
            +
                # @example With actions
         | 
| 33 33 | 
             
                #   <%= render(Primer::SubheadComponent.new) do |component| %>
         | 
| 34 34 | 
             
                #     <% component.slot(:heading) do %>
         | 
| 35 35 | 
             
                #       My Heading
         | 
| @@ -67,6 +67,7 @@ module Primer | |
| 67 67 | 
             
                  heading.present?
         | 
| 68 68 | 
             
                end
         | 
| 69 69 |  | 
| 70 | 
            +
                # :nodoc:
         | 
| 70 71 | 
             
                class Heading < ViewComponent::Slot
         | 
| 71 72 | 
             
                  include ClassNameHelper
         | 
| 72 73 |  | 
| @@ -85,6 +86,7 @@ module Primer | |
| 85 86 | 
             
                  end
         | 
| 86 87 | 
             
                end
         | 
| 87 88 |  | 
| 89 | 
            +
                # :nodoc:
         | 
| 88 90 | 
             
                class Actions < ViewComponent::Slot
         | 
| 89 91 | 
             
                  include ClassNameHelper
         | 
| 90 92 |  | 
| @@ -98,6 +100,7 @@ module Primer | |
| 98 100 | 
             
                  end
         | 
| 99 101 | 
             
                end
         | 
| 100 102 |  | 
| 103 | 
            +
                # :nodoc:
         | 
| 101 104 | 
             
                class Description < ViewComponent::Slot
         | 
| 102 105 | 
             
                  include ClassNameHelper
         | 
| 103 106 |  | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            import '@github/tab-container-element';
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Primer
         | 
| 4 | 
            +
              # Use TabContainer to create tabbed content with keyboard support. This component does not add any styles.
         | 
| 5 | 
            +
              # It only provides the tab functionality. If you want styled Tabs you can look at <%= link_to_component(Primer::TabNavComponent) %>.
         | 
| 6 | 
            +
              #
         | 
| 7 | 
            +
              # This component requires javascript.
         | 
| 8 | 
            +
              class TabContainerComponent < Primer::Component
         | 
| 9 | 
            +
                # @example Default
         | 
| 10 | 
            +
                #   <%= render(Primer::TabContainerComponent.new)  do %>
         | 
| 11 | 
            +
                #     <div role="tablist">
         | 
| 12 | 
            +
                #       <button type="button" role="tab" aria-selected="true">Tab one</button>
         | 
| 13 | 
            +
                #       <button type="button" role="tab" tabindex="-1">Tab two</button>
         | 
| 14 | 
            +
                #       <button type="button" role="tab" tabindex="-1">Tab three</button>
         | 
| 15 | 
            +
                #     </div>
         | 
| 16 | 
            +
                #     <div role="tabpanel">
         | 
| 17 | 
            +
                #       Panel 1
         | 
| 18 | 
            +
                #     </div>
         | 
| 19 | 
            +
                #     <div role="tabpanel" hidden>
         | 
| 20 | 
            +
                #       Panel 2
         | 
| 21 | 
            +
                #     </div>
         | 
| 22 | 
            +
                #     <div role="tabpanel" hidden>
         | 
| 23 | 
            +
                #       Panel 3
         | 
| 24 | 
            +
                #     </div>
         | 
| 25 | 
            +
                #   <% end %>
         | 
| 26 | 
            +
                #
         | 
| 27 | 
            +
                # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
         | 
| 28 | 
            +
                def initialize(**system_arguments)
         | 
| 29 | 
            +
                  @system_arguments = system_arguments
         | 
| 30 | 
            +
                  @system_arguments[:tag] = "tab-container"
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def call
         | 
| 34 | 
            +
                  render(Primer::BaseComponent.new(**@system_arguments)) { content }
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def render?
         | 
| 38 | 
            +
                  content.present?
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
            end
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            import '@github/tab-container-element'
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            <%= render wrapper.new(**@system_arguments) do %>
         | 
| 2 | 
            +
              <nav role="tablist" aria-label="<%= @aria_label %>" class="tabnav-tabs">
         | 
| 3 | 
            +
                <% tabs.each do |tab| %>
         | 
| 4 | 
            +
                  <%= tab %>
         | 
| 5 | 
            +
                <% end %>
         | 
| 6 | 
            +
              </nav >
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              <% if @with_panel %>
         | 
| 9 | 
            +
                <% tabs.each do |tab| %>
         | 
| 10 | 
            +
                  <% if tab.panel.present? %>
         | 
| 11 | 
            +
                    <div role="tabpanel" <%= "hidden" if tab.hidden? %>>
         | 
| 12 | 
            +
                      <%= tab.panel %>
         | 
| 13 | 
            +
                    </div>
         | 
| 14 | 
            +
                  <% end %>
         | 
| 15 | 
            +
                <% end %>
         | 
| 16 | 
            +
              <% end %>
         | 
| 17 | 
            +
            <% end %>
         | 
| @@ -0,0 +1,108 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Primer
         | 
| 4 | 
            +
              # Use TabNav to style navigation with a tab-based selected state, typically used for navigation placed at the top of the page.
         | 
| 5 | 
            +
              class TabNavComponent < Primer::Component
         | 
| 6 | 
            +
                include ViewComponent::SlotableV2
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                class MultipleSelectedTabsError < StandardError; end
         | 
| 9 | 
            +
                class NoSelectedTabsError < StandardError; end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                # Tabs to be rendered.
         | 
| 12 | 
            +
                #
         | 
| 13 | 
            +
                # @param title [String] Text to be rendered by the tab.
         | 
| 14 | 
            +
                # @param selected [Boolean] Whether the tab is selected.
         | 
| 15 | 
            +
                # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
         | 
| 16 | 
            +
                renders_many :tabs, lambda { |**system_arguments|
         | 
| 17 | 
            +
                  return TabComponent.new(**system_arguments) unless @with_panel
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  TabComponent.new(tag: :button, type: :button, **system_arguments)
         | 
| 20 | 
            +
                }
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                # @example Default
         | 
| 23 | 
            +
                #   <%= render(Primer::TabNavComponent.new) do |c| %>
         | 
| 24 | 
            +
                #     <% c.tab(selected: true, title: "Tab 1", href: "#") %>
         | 
| 25 | 
            +
                #     <% c.tab(title: "Tab 2", href: "#") %>
         | 
| 26 | 
            +
                #     <% c.tab(title: "Tab 3", href: "#") %>
         | 
| 27 | 
            +
                #   <% end %>
         | 
| 28 | 
            +
                #
         | 
| 29 | 
            +
                # @example With panels
         | 
| 30 | 
            +
                #   <%= render(Primer::TabNavComponent.new(with_panel: true)) do |c| %>
         | 
| 31 | 
            +
                #     <% c.tab(selected: true, title: "Tab 1") { "Panel 1" } %>
         | 
| 32 | 
            +
                #     <% c.tab(title: "Tab 2") { "Panel 1" } %>
         | 
| 33 | 
            +
                #     <% c.tab(title: "Tab 3") { "Panel 1" } %>
         | 
| 34 | 
            +
                #   <% end %>
         | 
| 35 | 
            +
                #
         | 
| 36 | 
            +
                # @param aria_label [String] Used to set the `aria-label` on the top level `<nav>` element.
         | 
| 37 | 
            +
                # @param with_panel [Boolean] Whether the TabNav should navigate through pages or panels.
         | 
| 38 | 
            +
                # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
         | 
| 39 | 
            +
                def initialize(aria_label: nil, with_panel: false, **system_arguments)
         | 
| 40 | 
            +
                  @aria_label = aria_label
         | 
| 41 | 
            +
                  @with_panel = with_panel
         | 
| 42 | 
            +
                  @system_arguments = system_arguments
         | 
| 43 | 
            +
                  @system_arguments[:tag] ||= :div
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  @system_arguments[:classes] = class_names(
         | 
| 46 | 
            +
                    "tabnav",
         | 
| 47 | 
            +
                    system_arguments[:classes]
         | 
| 48 | 
            +
                  )
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def before_render
         | 
| 52 | 
            +
                  validate_single_selected_tab
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                private
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                def wrapper
         | 
| 58 | 
            +
                  @with_panel ? Primer::TabContainerComponent : Primer::BaseComponent
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def validate_single_selected_tab
         | 
| 62 | 
            +
                  raise MultipleSelectedTabsError, "only one tab can be selected" if selected_tabs_count > 1
         | 
| 63 | 
            +
                  raise NoSelectedTabsError, "a tab must be selected" if selected_tabs_count != 1
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                def selected_tabs_count
         | 
| 67 | 
            +
                  @selected_tabs_count ||= tabs.count(&:selected)
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                # Tabs to be rendered.
         | 
| 71 | 
            +
                class TabComponent < Primer::Component
         | 
| 72 | 
            +
                  attr_reader :selected
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  def initialize(title:, selected: false, **system_arguments)
         | 
| 75 | 
            +
                    @title = title
         | 
| 76 | 
            +
                    @selected = selected
         | 
| 77 | 
            +
                    @system_arguments = system_arguments
         | 
| 78 | 
            +
                    @system_arguments[:tag] ||= :a
         | 
| 79 | 
            +
                    @system_arguments[:role] = :tab
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    if selected
         | 
| 82 | 
            +
                      if @system_arguments[:tag] == :a
         | 
| 83 | 
            +
                        @system_arguments[:"aria-current"] = :page
         | 
| 84 | 
            +
                      else
         | 
| 85 | 
            +
                        @system_arguments[:"aria-selected"] = true
         | 
| 86 | 
            +
                      end
         | 
| 87 | 
            +
                    end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                    @system_arguments[:classes] = class_names(
         | 
| 90 | 
            +
                      "tabnav-tab",
         | 
| 91 | 
            +
                      system_arguments[:classes]
         | 
| 92 | 
            +
                    )
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                  def call
         | 
| 96 | 
            +
                    render(Primer::BaseComponent.new(**@system_arguments)) { @title }
         | 
| 97 | 
            +
                  end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                  def panel
         | 
| 100 | 
            +
                    content
         | 
| 101 | 
            +
                  end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  def hidden?
         | 
| 104 | 
            +
                    !@selected
         | 
| 105 | 
            +
                  end
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
              end
         | 
| 108 | 
            +
            end
         |