primer_view_components 0.0.1 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
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