primer_view_components 0.0.1 → 0.0.7

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +92 -2
  3. data/README.md +149 -0
  4. data/app/components/primer/avatar_component.rb +28 -0
  5. data/app/components/primer/base_component.rb +42 -0
  6. data/app/components/primer/blankslate_component.html.erb +27 -0
  7. data/app/components/primer/blankslate_component.rb +164 -0
  8. data/app/components/primer/border_box_component.html.erb +26 -0
  9. data/app/components/primer/border_box_component.rb +77 -0
  10. data/app/components/primer/box_component.rb +14 -0
  11. data/app/components/primer/breadcrumb_component.html.erb +8 -0
  12. data/app/components/primer/breadcrumb_component.rb +52 -0
  13. data/app/components/primer/button_component.rb +58 -0
  14. data/app/components/primer/component.rb +9 -0
  15. data/app/components/primer/counter_component.rb +85 -0
  16. data/app/components/primer/details_component.html.erb +8 -0
  17. data/app/components/primer/details_component.rb +63 -0
  18. data/app/components/primer/dropdown_menu_component.html.erb +8 -0
  19. data/app/components/primer/dropdown_menu_component.rb +28 -0
  20. data/app/components/primer/flex_component.rb +81 -0
  21. data/app/components/primer/flex_item_component.rb +21 -0
  22. data/app/components/primer/heading_component.rb +14 -0
  23. data/app/components/primer/label_component.rb +48 -0
  24. data/app/components/primer/layout_component.html.erb +17 -0
  25. data/app/components/primer/layout_component.rb +28 -0
  26. data/app/components/primer/link_component.rb +19 -0
  27. data/app/components/primer/popover_component.html.erb +10 -0
  28. data/app/components/primer/popover_component.rb +75 -0
  29. data/app/components/primer/progress_bar_component.html.erb +5 -0
  30. data/app/components/primer/progress_bar_component.rb +66 -0
  31. data/app/components/primer/slot.rb +8 -0
  32. data/app/components/primer/state_component.rb +53 -0
  33. data/app/components/primer/subhead_component.html.erb +17 -0
  34. data/app/components/primer/subhead_component.rb +89 -0
  35. data/app/components/primer/text_component.rb +14 -0
  36. data/app/components/primer/timeline_item_component.html.erb +17 -0
  37. data/app/components/primer/timeline_item_component.rb +69 -0
  38. data/app/components/primer/underline_nav_component.html.erb +11 -0
  39. data/app/components/primer/underline_nav_component.rb +22 -0
  40. data/app/components/primer/view_components.rb +48 -0
  41. data/lib/primer/class_name_helper.rb +27 -0
  42. data/lib/primer/classify.rb +245 -0
  43. data/lib/primer/fetch_or_fallback_helper.rb +41 -0
  44. data/lib/primer/view_components.rb +3 -0
  45. data/lib/primer/view_components/engine.rb +11 -0
  46. data/lib/primer/view_components/version.rb +1 -1
  47. metadata +203 -4
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ class FlexComponent < Primer::Component
5
+ JUSTIFY_CONTENT_DEFAULT = nil
6
+ JUSTIFY_CONTENT_MAPPINGS = {
7
+ flex_start: "flex-justify-start",
8
+ flex_end: "flex-justify-end",
9
+ center: "flex-justify-center",
10
+ space_between: "flex-justify-between",
11
+ space_around: "flex-justify-around",
12
+ }
13
+ JUSTIFY_CONTENT_OPTIONS = [JUSTIFY_CONTENT_DEFAULT, *JUSTIFY_CONTENT_MAPPINGS.keys]
14
+
15
+ ALIGN_ITEMS_DEFAULT = nil
16
+ ALIGN_ITEMS_MAPPINGS = {
17
+ start: "flex-items-start",
18
+ end: "flex-items-end",
19
+ center: "flex-items-center",
20
+ baseline: "flex-items-baseline",
21
+ stretch: "flex-items-stretch",
22
+ }
23
+ ALIGN_ITEMS_OPTIONS = [ALIGN_ITEMS_DEFAULT, *ALIGN_ITEMS_MAPPINGS.keys]
24
+
25
+ INLINE_DEFAULT = false
26
+ INLINE_OPTIONS = [INLINE_DEFAULT, true]
27
+
28
+ FLEX_WRAP_DEFAULT = nil
29
+ FLEX_WRAP_OPTIONS = [FLEX_WRAP_DEFAULT, true, false]
30
+
31
+ DEFAULT_DIRECTION = nil
32
+ ALLOWED_DIRECTIONS = [DEFAULT_DIRECTION, :column, :column_reverse, :row, :row_reverse]
33
+
34
+ def initialize(
35
+ justify_content: JUSTIFY_CONTENT_DEFAULT,
36
+ inline: INLINE_DEFAULT,
37
+ flex_wrap: FLEX_WRAP_DEFAULT,
38
+ align_items: ALIGN_ITEMS_DEFAULT,
39
+ direction: nil,
40
+ **kwargs
41
+ )
42
+ @align_items = fetch_or_fallback(ALIGN_ITEMS_OPTIONS, align_items, ALIGN_ITEMS_DEFAULT)
43
+ @justify_content = fetch_or_fallback(JUSTIFY_CONTENT_OPTIONS, justify_content, JUSTIFY_CONTENT_DEFAULT)
44
+ @flex_wrap = fetch_or_fallback(FLEX_WRAP_OPTIONS, flex_wrap, FLEX_WRAP_DEFAULT)
45
+
46
+ # Support direction argument that is an array
47
+ @direction =
48
+ if (Array(direction) - ALLOWED_DIRECTIONS).empty?
49
+ direction
50
+ else
51
+ DEFAULT_DIRECTION
52
+ end
53
+
54
+ @kwargs = kwargs.merge({ direction: @direction }.compact)
55
+ @kwargs[:classes] = class_names(@kwargs[:classes], component_class_names)
56
+ @kwargs[:display] = fetch_or_fallback(INLINE_OPTIONS, inline, INLINE_DEFAULT) ? :inline_flex : :flex
57
+ end
58
+
59
+ def call
60
+ render(Primer::BoxComponent.new(**@kwargs)) { content }
61
+ end
62
+
63
+ private
64
+
65
+ def component_class_names
66
+ out = []
67
+ out << JUSTIFY_CONTENT_MAPPINGS[@justify_content]
68
+ out << ALIGN_ITEMS_MAPPINGS[@align_items]
69
+
70
+ out <<
71
+ case @flex_wrap
72
+ when true
73
+ "flex-wrap"
74
+ when false
75
+ "flex-nowrap"
76
+ end
77
+
78
+ out.compact.join(" ")
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ class FlexItemComponent < Primer::Component
5
+ FLEX_AUTO_DEFAULT = false
6
+ FLEX_AUTO_ALLOWED_VALUES = [FLEX_AUTO_DEFAULT, true]
7
+
8
+ def initialize(flex_auto: FLEX_AUTO_DEFAULT, **kwargs)
9
+ @kwargs = kwargs
10
+ @kwargs[:classes] =
11
+ class_names(
12
+ @kwargs[:classes],
13
+ "flex-auto" => fetch_or_fallback(FLEX_AUTO_ALLOWED_VALUES, flex_auto, FLEX_AUTO_DEFAULT)
14
+ )
15
+ end
16
+
17
+ def call
18
+ render(Primer::BoxComponent.new(**@kwargs)) { content }
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ class HeadingComponent < Primer::Component
5
+ def initialize(**kwargs)
6
+ @kwargs = kwargs
7
+ @kwargs[:tag] ||= :h1
8
+ end
9
+
10
+ def call
11
+ render(Primer::BaseComponent.new(**@kwargs)) { content }
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ class LabelComponent < Primer::Component
5
+ SCHEME_MAPPINGS = {
6
+ # gray
7
+ gray: "Label--gray",
8
+ dark_gray: "Label--gray-darker",
9
+
10
+ # colored
11
+ yellow: "Label--yellow",
12
+ orange: "Label--orange",
13
+ red: "Label--red",
14
+ green: "Label--green",
15
+ blue: "Label--blue",
16
+ purple: "Label--purple",
17
+ pink: "Label--pink",
18
+
19
+ # Deprecated
20
+ outline: "Label--outline",
21
+ green_outline: "Label--outline-green",
22
+ }.freeze
23
+ SCHEME_OPTIONS = SCHEME_MAPPINGS.keys << nil
24
+
25
+ VARIANT_MAPPINGS = {
26
+ large: "Label--large",
27
+ inline: "Label--inline",
28
+ }.freeze
29
+ VARIANT_OPTIONS = VARIANT_MAPPINGS.keys << nil
30
+
31
+ def initialize(title:, scheme: nil, variant: nil, **kwargs)
32
+ @kwargs = kwargs
33
+ @kwargs[:bg] = :blue if scheme.nil?
34
+ @kwargs[:tag] ||= :span
35
+ @kwargs[:title] = title
36
+ @kwargs[:classes] = class_names(
37
+ "Label",
38
+ kwargs[:classes],
39
+ SCHEME_MAPPINGS[fetch_or_fallback(SCHEME_OPTIONS, scheme)],
40
+ VARIANT_MAPPINGS[fetch_or_fallback(VARIANT_OPTIONS, variant)]
41
+ )
42
+ end
43
+
44
+ def call
45
+ render(Primer::BaseComponent.new(**@kwargs)) { content }
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,17 @@
1
+ <%= render(Primer::FlexComponent.new(**@kwargs)) do %>
2
+ <% if @side == :left %>
3
+ <%= render Primer::BaseComponent.new(tag: :div, classes: "flex-shrink-0", col: (@responsive ? [12, nil, @sidebar_col] : @sidebar_col), mb: (@responsive ? [4, nil, 0] : nil)) do %>
4
+ <%= sidebar %>
5
+ <% end %>
6
+ <% end %>
7
+
8
+ <%= render Primer::BaseComponent.new(tag: :div, classes: "flex-shrink-0", col: (@responsive ? [12, nil, @main_col] : @main_col), mb: (@responsive ? [4, nil, 0] : nil)) do %>
9
+ <%= main %>
10
+ <% end %>
11
+
12
+ <% if @side == :right %>
13
+ <%= render Primer::BaseComponent.new(tag: :div, classes: "flex-shrink-0", col: (@responsive ? [12, nil, @sidebar_col] : @sidebar_col)) do %>
14
+ <%= sidebar %>
15
+ <% end %>
16
+ <% end %>
17
+ <% end %>
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ class LayoutComponent < Primer::Component
5
+ with_content_areas :main, :sidebar
6
+
7
+ DEFAULT_SIDE = :right
8
+ ALLOWED_SIDES = [DEFAULT_SIDE, :left].freeze
9
+
10
+ MAX_COL = 12
11
+ DEFAULT_SIDEBAR_COL = 3
12
+ ALLOWED_SIDEBAR_COLS = (1..(MAX_COL - 1)).to_a.freeze
13
+
14
+ def initialize(responsive: false, side: DEFAULT_SIDE, sidebar_col: DEFAULT_SIDEBAR_COL, **kwargs)
15
+ @kwargs = kwargs
16
+ @side = fetch_or_fallback(ALLOWED_SIDES, side.to_sym, DEFAULT_SIDE)
17
+ @responsive = responsive
18
+ @kwargs[:classes] = class_names(
19
+ "gutter-condensed gutter-lg",
20
+ @kwargs[:classes]
21
+ )
22
+ @kwargs[:direction] = responsive ? [:column, nil, :row] : nil
23
+
24
+ @sidebar_col = fetch_or_fallback(ALLOWED_SIDEBAR_COLS, sidebar_col, DEFAULT_SIDEBAR_COL)
25
+ @main_col = MAX_COL - @sidebar_col
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ class LinkComponent < Primer::Component
5
+ def initialize(href:, muted: false, **kwargs)
6
+ @kwargs = kwargs
7
+ @kwargs[:tag] = :a
8
+ @kwargs[:href] = href
9
+ @kwargs[:classes] = class_names(
10
+ @kwargs[:classes],
11
+ "muted-link" => fetch_or_fallback([true, false], muted, false)
12
+ )
13
+ end
14
+
15
+ def call
16
+ render(Primer::BaseComponent.new(**@kwargs)) { content }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ <%= render Primer::BaseComponent.new(**@kwargs) do %>
2
+ <%= render body.component do %>
3
+ <% if heading %>
4
+ <%= render heading.component do %>
5
+ <%= heading.content %>
6
+ <% end %>
7
+ <% end %>
8
+ <%= body.content %>
9
+ <% end %>
10
+ <% end %>
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ class PopoverComponent < Primer::Component
5
+ include ViewComponent::Slotable
6
+
7
+ with_slot :heading, class_name: "Heading"
8
+ with_slot :body, class_name: "Body"
9
+
10
+ def initialize(**kwargs)
11
+ @kwargs = kwargs
12
+ @kwargs[:tag] ||= :div
13
+ @kwargs[:classes] = class_names(
14
+ kwargs[:classes],
15
+ "Popover"
16
+ )
17
+ @kwargs[:position] ||= :relative
18
+ @kwargs[:right] = false unless kwargs.key?(:right)
19
+ @kwargs[:left] = false unless kwargs.key?(:left)
20
+ end
21
+
22
+ def render?
23
+ body.present?
24
+ end
25
+
26
+ class Heading < ViewComponent::Slot
27
+ def initialize(**kwargs)
28
+ @kwargs = kwargs
29
+ @kwargs[:mb] ||= 2
30
+ @kwargs[:tag] ||= :h4
31
+ end
32
+
33
+ def component
34
+ Primer::HeadingComponent.new(**@kwargs)
35
+ end
36
+ end
37
+
38
+ class Body < Slot
39
+ CARET_DEFAULT = :top
40
+ CARET_MAPPINGS = {
41
+ CARET_DEFAULT => "",
42
+ :bottom => "Popover-message--bottom",
43
+ :bottom_right => "Popover-message--bottom-right",
44
+ :bottom_left => "Popover-message--bottom-left",
45
+ :left => "Popover-message--left",
46
+ :left_bottom => "Popover-message--left-bottom",
47
+ :left_top => "Popover-message--left-top",
48
+ :right => "Popover-message--right",
49
+ :right_bottom => "Popover-message--right-bottom",
50
+ :right_top => "Popover-message--right-top",
51
+ :top_left => "Popover-message--top-left",
52
+ :top_right => "Popover-message--top-right"
53
+ }.freeze
54
+
55
+ def initialize(caret: CARET_DEFAULT, large: false, **kwargs)
56
+ @kwargs = kwargs
57
+ @kwargs[:classes] = class_names(
58
+ kwargs[:classes],
59
+ "Popover-message Box",
60
+ CARET_MAPPINGS[fetch_or_fallback(CARET_MAPPINGS.keys, caret, CARET_DEFAULT)],
61
+ "Popover-message--large" => large
62
+ )
63
+ @kwargs[:p] ||= 4
64
+ @kwargs[:mt] ||= 2
65
+ @kwargs[:mx] ||= :auto
66
+ @kwargs[:text_align] ||= :left
67
+ @kwargs[:box_shadow] ||= :large
68
+ end
69
+
70
+ def component
71
+ Primer::BoxComponent.new(**@kwargs)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,5 @@
1
+ <%= render Primer::BaseComponent.new(**@kwargs) do %>
2
+ <% items.each do |item| %>
3
+ <%= render Primer::BaseComponent.new(**item.kwargs) %>
4
+ <% end %>
5
+ <% end %>
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Use progress components to visualize task completion.
5
+
6
+ ## Basic example
7
+ #
8
+ # The `Primer::ProgressBarComponent` can take the following arguments:
9
+ #
10
+ # 1. `size` (string). Can be "small" or "large". Increases the height of the progress bar.
11
+ #
12
+ # The `Primer::ProgressBarComponent` uses the [Slots API](https://github.com/github/view_component#slots-experimental) and at least one slot is required for the component to render. Each slot accepts a `percentage` parameter, which is used to set the width of the completed bar.
13
+ #
14
+ # ```ruby
15
+ # <%= render(Primer::ProgressBarComponent.new(size: :small)) do |component| %>
16
+ # <% component.slot(:item, bg: :blue-4, percentage: 50) %>
17
+ # <% end %>
18
+ # ```
19
+ ##
20
+ module Primer
21
+ class ProgressBarComponent < Primer::Component
22
+ include ViewComponent::Slotable
23
+
24
+ with_slot :item, collection: true, class_name: "Item"
25
+
26
+ SIZE_DEFAULT = :default
27
+
28
+ SIZE_MAPPINGS = {
29
+ SIZE_DEFAULT => "",
30
+ :small => "Progress--small",
31
+ :large => "Progress--large",
32
+ }.freeze
33
+
34
+ SIZE_OPTIONS = SIZE_MAPPINGS.keys
35
+
36
+ def initialize(size: SIZE_DEFAULT, percentage: 0, **kwargs)
37
+ @kwargs = kwargs
38
+ @kwargs[:classes] = class_names(
39
+ @kwargs[:classes],
40
+ "Progress",
41
+ SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, SIZE_DEFAULT)]
42
+ )
43
+ @kwargs[:tag] = :span
44
+
45
+ end
46
+
47
+ def render?
48
+ items.any?
49
+ end
50
+
51
+ class Item < ViewComponent::Slot
52
+ include ClassNameHelper
53
+ attr_reader :kwargs
54
+
55
+ def initialize(percentage: 0, bg: :green, **kwargs)
56
+ @percentage = percentage
57
+ @kwargs = kwargs
58
+
59
+ @kwargs[:tag] = :span
60
+ @kwargs[:bg] = bg
61
+ @kwargs[:style] = "width: #{@percentage}%;"
62
+ @kwargs[:classes] = class_names("Progress-item", @kwargs[:classes])
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ class Slot < ViewComponent::Slot
5
+ include ClassNameHelper
6
+ include FetchOrFallbackHelper
7
+ end
8
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ class StateComponent < Primer::Component
5
+ # Component for rendering the status of an item
6
+ #
7
+ # title(string): (required) title attribute
8
+ # color(symbol): label background color
9
+ # size(symbol): label size
10
+ # counter(integer): counter value
11
+ # **args(hash): utility parameters for Primer::Classify
12
+ COLOR_DEFAULT = :default
13
+ COLOR_MAPPINGS = {
14
+ COLOR_DEFAULT => "",
15
+ :green => "State--green",
16
+ :red => "State--red",
17
+ :purple => "State--purple",
18
+ }.freeze
19
+ COLOR_OPTIONS = COLOR_MAPPINGS.keys
20
+
21
+ SIZE_DEFAULT = :default
22
+ SIZE_MAPPINGS = {
23
+ SIZE_DEFAULT => "",
24
+ :small => "State--small",
25
+ }.freeze
26
+ SIZE_OPTIONS = SIZE_MAPPINGS.keys
27
+
28
+ TAG_DEFAULT = :span
29
+ TAG_OPTIONS = [TAG_DEFAULT, :div, :a]
30
+
31
+ def initialize(
32
+ title:,
33
+ color: COLOR_DEFAULT,
34
+ tag: TAG_DEFAULT,
35
+ size: SIZE_DEFAULT,
36
+ **kwargs
37
+ )
38
+ @kwargs = kwargs
39
+ @kwargs[:title] = title
40
+ @kwargs[:tag] = fetch_or_fallback(TAG_OPTIONS, tag, TAG_DEFAULT)
41
+ @kwargs[:classes] = class_names(
42
+ @kwargs[:classes],
43
+ "State",
44
+ COLOR_MAPPINGS[fetch_or_fallback(COLOR_OPTIONS, color, COLOR_DEFAULT)],
45
+ SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, SIZE_DEFAULT)]
46
+ )
47
+ end
48
+
49
+ def call
50
+ render(Primer::BaseComponent.new(**@kwargs)) { content }
51
+ end
52
+ end
53
+ end