pulse_view_components 0.3.0 → 0.4.0

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +98 -1
  3. data/README.md +26 -0
  4. data/app/assets/builds/js/pulse_view_components.js +3 -3
  5. data/app/assets/builds/js/pulse_view_components.js.map +1 -1
  6. data/app/assets/builds/pulse_view_components.css +2 -1
  7. data/app/assets/stylesheets/pulse_view_components/application.tailwind.css +44 -4
  8. data/app/assets/stylesheets/pulse_view_components/tokens.primitives.css +68 -0
  9. data/app/assets/stylesheets/pulse_view_components/tokens.system.css +95 -0
  10. data/app/components/pulse/action_list/divider.rb +13 -4
  11. data/app/components/pulse/action_list/form_wrapper.rb +2 -0
  12. data/app/components/pulse/action_list/heading.html.slim +7 -0
  13. data/app/components/pulse/action_list/heading.rb +2 -0
  14. data/app/components/pulse/action_list/item.html.slim +6 -6
  15. data/app/components/pulse/action_list/item.rb +11 -9
  16. data/app/components/pulse/action_list.css +109 -82
  17. data/app/components/pulse/action_list.html.slim +11 -0
  18. data/app/components/pulse/action_list.rb +5 -0
  19. data/app/components/pulse/badge.rb +20 -16
  20. data/app/components/pulse/base_component.rb +25 -1
  21. data/app/components/pulse/blank_slate.rb +4 -2
  22. data/app/components/pulse/border_box/header.rb +5 -3
  23. data/app/components/pulse/border_box.rb +21 -17
  24. data/app/components/pulse/box.rb +2 -0
  25. data/app/components/pulse/breadcrumb_item.rb +3 -1
  26. data/app/components/pulse/breadcrumbs.html.slim +2 -2
  27. data/app/components/pulse/breadcrumbs.rb +2 -0
  28. data/app/components/pulse/button.rb +28 -25
  29. data/app/components/pulse/component.rb +2 -1
  30. data/app/components/pulse/conditional_wrapper.rb +2 -0
  31. data/app/components/pulse/content.rb +2 -0
  32. data/app/components/pulse/counter.css +3 -3
  33. data/app/components/pulse/counter.rb +4 -0
  34. data/app/components/pulse/flash.rb +10 -6
  35. data/app/components/pulse/forms/error_explanation.html.slim +3 -3
  36. data/app/components/pulse/forms/error_explanation.rb +30 -13
  37. data/app/components/pulse/forms/error_explanation.yml +4 -0
  38. data/app/components/pulse/icon.rb +2 -0
  39. data/app/components/pulse/label.rb +5 -3
  40. data/app/components/pulse/layout.css +53 -52
  41. data/app/components/pulse/layout.rb +14 -6
  42. data/app/components/pulse/link.rb +10 -8
  43. data/app/components/pulse/modal.html.slim +20 -20
  44. data/app/components/pulse/modal.rb +10 -8
  45. data/app/components/pulse/modal_controller.js +3 -3
  46. data/app/components/pulse/nav_link.html.slim +7 -7
  47. data/app/components/pulse/nav_link.rb +4 -2
  48. data/app/components/pulse/nav_list/heading.rb +3 -1
  49. data/app/components/pulse/nav_list/item.html.slim +8 -0
  50. data/app/components/pulse/nav_list/item.rb +2 -2
  51. data/app/components/pulse/nav_list.rb +5 -0
  52. data/app/components/pulse/popover.html.slim +1 -1
  53. data/app/components/pulse/popover.rb +65 -26
  54. data/app/components/pulse/progress_bar.html.slim +2 -2
  55. data/app/components/pulse/progress_bar.rb +5 -3
  56. data/app/components/pulse/table/cell.html.slim +2 -2
  57. data/app/components/pulse/table/cell.rb +14 -10
  58. data/app/components/pulse/table/column.rb +2 -0
  59. data/app/components/pulse/table.html.slim +3 -3
  60. data/app/components/pulse/table.rb +19 -16
  61. data/app/components/pulse/text.rb +26 -24
  62. data/app/components/pulse/toast.html.slim +8 -8
  63. data/app/components/pulse/toast.rb +25 -23
  64. data/app/lib/pulse/status.rb +48 -0
  65. data/lib/pulse/view_components/version.rb +1 -1
  66. data/previews/foundations/colors_preview/palettes.html.slim +12 -0
  67. data/previews/foundations/colors_preview/semantic.html.slim +16 -0
  68. data/previews/foundations/colors_preview.rb +55 -0
  69. data/previews/pulse/action_list_preview.rb +77 -0
  70. data/previews/pulse/border_box_preview.rb +1 -1
  71. data/previews/pulse/counter_preview.rb +1 -1
  72. data/previews/pulse/flash_preview.rb +1 -1
  73. data/previews/pulse/forms/error_explanation_preview.rb +49 -0
  74. data/previews/pulse/layout_preview.rb +5 -5
  75. data/previews/pulse/nav_list_preview.rb +1 -1
  76. data/previews/pulse/popover_preview.rb +48 -22
  77. data/previews/pulse/progress_bar_preview.rb +1 -1
  78. data/previews/pulse/table_preview.rb +6 -6
  79. metadata +17 -11
@@ -23,14 +23,38 @@ module Pulse
23
23
  # ```
24
24
  #
25
25
  class BaseComponent < Pulse::Component
26
- def initialize(tag:, classes: nil, **system_arguments)
26
+ status :stable
27
+
28
+ # @param system_arguments_denylist [Hash] Maps arrays of denied argument
29
+ # keys to help text. Consumed here and never rendered as an attribute.
30
+ def initialize(tag:, classes: nil, system_arguments_denylist: {},
31
+ **system_arguments)
27
32
  @tag = tag
28
33
  @classes = classes
29
34
  @system_arguments = system_arguments
35
+ validate_denylist(system_arguments_denylist)
30
36
  end
31
37
 
32
38
  def call
33
39
  content_tag(@tag, content, class: @classes, **@system_arguments)
34
40
  end
41
+
42
+ private
43
+
44
+ def validate_denylist(denylist)
45
+ denylist.each do |keys, help_text|
46
+ denied = keys & @system_arguments.keys
47
+ next if denied.empty?
48
+
49
+ if should_raise_error?
50
+ raise ArgumentError,
51
+ "#{denied.map { |key| "`#{key}`" }.to_sentence} " \
52
+ "#{denied.one? ? 'is an' : 'are'} invalid " \
53
+ "#{denied.one? ? 'argument' : 'arguments'}. #{help_text}"
54
+ end
55
+
56
+ @system_arguments.except!(*denied)
57
+ end
58
+ end
35
59
  end
36
60
  end
@@ -3,6 +3,8 @@
3
3
  module Pulse
4
4
  # Use as placeholder when content is missing
5
5
  class BlankSlate < Pulse::Component
6
+ status :stable
7
+
6
8
  renders_one :title
7
9
  renders_one :description
8
10
 
@@ -10,8 +12,8 @@ module Pulse
10
12
  @system_arguments = system_arguments
11
13
  @system_arguments[:tag] ||= :div
12
14
  @system_arguments[:classes] = merge_classes(
13
- 'pulse-container pulse-text-center pulse-pt-16 pulse-pb-20',
14
- 'pulse-bg-secondary-50',
15
+ 'pulse:container pulse:text-center pulse:pt-16 pulse:pb-20',
16
+ 'pulse:bg-secondary-50',
15
17
  system_arguments[:classes]
16
18
  )
17
19
  end
@@ -6,6 +6,8 @@ module Pulse
6
6
  #
7
7
  # @accessibility When using `header.with_title`, set `tag` to one of `h1`, `h2`, `h3`, etc. based on what is appropriate for the page context. <%= link_to_heading_practices %>
8
8
  class Header < Pulse::Component
9
+ status :beta
10
+
9
11
  attr_reader :id
10
12
 
11
13
  TITLE_TAG_FALLBACK = :h2
@@ -35,9 +37,9 @@ module Pulse
35
37
  @system_arguments[:id] ||= self.class.generate_id
36
38
  @system_arguments[:tag] = :div
37
39
  @system_arguments[:classes] = class_names(
38
- 'pulse-px-4 pulse-py-5 pulse-bg-gray-50 sm:pulse-p-6',
39
- 'pulse-rounded-t-md pulse-border-b pulse-border-gray-300',
40
- 'pulse-border-solid',
40
+ 'pulse:px-4 pulse:py-5 pulse:bg-neutral-50 pulse:sm:p-6',
41
+ 'pulse:rounded-t-md pulse:border-b pulse:border-neutral-300',
42
+ 'pulse:border-solid',
41
43
  'Box-header',
42
44
  system_arguments[:classes]
43
45
  )
@@ -3,6 +3,10 @@
3
3
  module Pulse
4
4
  # `BorderBox` is a Box component with a border.
5
5
  class BorderBox < Pulse::Component
6
+ # Header, body, rows and footer work. The `padding:` option and row
7
+ # `scheme:` values emit Primer classes that have no styles.
8
+ status :beta
9
+
6
10
  DEFAULT_PADDING = :default
7
11
  PADDING_MAPPINGS = {
8
12
  DEFAULT_PADDING => '',
@@ -33,11 +37,11 @@ module Pulse
33
37
  renders_one :body, lambda { |**system_arguments|
34
38
  system_arguments[:tag] = :div
35
39
  system_arguments[:classes] = class_names(
36
- 'pulse-px-4 pulse-py-5 pulse-bg-white sm:pulse-p-6',
37
- 'pulse-border-b pulse-border-gray-300 pulse-border-solid',
38
- 'first-of-type:pulse-rounded-t-md',
39
- 'last-of-type:pulse-rounded-b-md',
40
- 'last-of-type:pulse-border-b-0',
40
+ 'pulse:px-4 pulse:py-5 pulse:bg-white pulse:sm:p-6',
41
+ 'pulse:border-b pulse:border-neutral-300 pulse:border-solid',
42
+ 'pulse:first-of-type:rounded-t-md',
43
+ 'pulse:last-of-type:rounded-b-md',
44
+ 'pulse:last-of-type:border-b-0',
41
45
  system_arguments[:classes]
42
46
  )
43
47
 
@@ -50,9 +54,9 @@ module Pulse
50
54
  renders_one :footer, lambda { |**system_arguments|
51
55
  system_arguments[:tag] = :div
52
56
  system_arguments[:classes] = class_names(
53
- 'pulse-px-4 pulse-py-5 pulse-bg-white sm:pulse-p-6',
54
- 'pulse-border-t pulse-border-gray-300 pulse-border-solid',
55
- '-pulse-mt-1 pulse-rounded-b-md',
57
+ 'pulse:px-4 pulse:py-5 pulse:bg-white pulse:sm:p-6',
58
+ 'pulse:border-t pulse:border-neutral-300 pulse:border-solid',
59
+ 'pulse:-mt-1 pulse:rounded-b-md',
56
60
  'Box-footer',
57
61
  system_arguments[:classes]
58
62
  )
@@ -60,7 +64,7 @@ module Pulse
60
64
  Pulse::BaseComponent.new(**system_arguments)
61
65
  }
62
66
 
63
- # @apply pulse-pt-[var(--stack-padding-normal)] pulse-pr-[var(--stack-padding-normal)] pulse-pb-[var(--stack-padding-normal)] pulse-pl-[var(--stack-padding-normal)] pulse-mt-[calc(var(--borderWidth-thin)_*_-1)] pulse-list-none pulse-border-t-[color:var(--borderColor-muted)] pulse-border-t-[length:var(--borderWidth-thin)] first-of-type:pulse-rounded-tl-[var(--borderRadius-medium)] first-of-type:pulse-rounded-tr-[var(--borderRadius-medium)] last-of-type:pulse-rounded-br-[var(--borderRadius-medium)] last-of-type:pulse-rounded-bl-[var(--borderRadius-medium)]; /* To account for applying Box component to a list */
67
+ # @apply pulse:pt-[var(--stack-padding-normal)] pulse:pr-[var(--stack-padding-normal)] pulse:pb-[var(--stack-padding-normal)] pulse:pl-[var(--stack-padding-normal)] pulse:mt-[calc(var(--borderWidth-thin)_*_-1)] pulse:list-none pulse:border-t-[color:var(--borderColor-muted)] pulse:border-t-[length:var(--borderWidth-thin)] pulse:first-of-type:rounded-tl-[var(--borderRadius-medium)] pulse:first-of-type:rounded-tr-[var(--borderRadius-medium)] pulse:last-of-type:rounded-br-[var(--borderRadius-medium)] pulse:last-of-type:rounded-bl-[var(--borderRadius-medium)]; /* To account for applying Box component to a list */
64
68
  # border-top-style: solid;
65
69
 
66
70
  # Use Rows to add rows with borders and maintain the same padding.
@@ -71,10 +75,10 @@ module Pulse
71
75
  **system_arguments|
72
76
  system_arguments[:tag] = :li
73
77
  system_arguments[:classes] = class_names(
74
- 'pulse-px-4 pulse-py-5 pulse-bg-white sm:pulse-p-6',
75
- 'pulse-border-b pulse-border-gray-300 pulse-border-solid',
76
- 'last:pulse-border-b-0',
77
- 'first-of-type:pulse-rounded-t-md last-of-type:pulse-rounded-b-md',
78
+ 'pulse:px-4 pulse:py-5 pulse:bg-white pulse:sm:p-6',
79
+ 'pulse:border-b pulse:border-neutral-300 pulse:border-solid',
80
+ 'pulse:last:border-b-0',
81
+ 'pulse:first-of-type:rounded-t-md pulse:last-of-type:rounded-b-md',
78
82
  ROW_SCHEME_MAPPINGS[fetch_or_fallback(ROW_SCHEME_MAPPINGS.keys,
79
83
  scheme, DEFAULT_ROW_SCHEME)],
80
84
  system_arguments[:classes]
@@ -89,8 +93,8 @@ module Pulse
89
93
  @system_arguments = deny_tag_argument(**system_arguments)
90
94
  @system_arguments[:tag] = :div
91
95
  @system_arguments[:classes] = class_names(
92
- 'pulse-bg-white pulse-border pulse-border-gray-300 pulse-border-solid',
93
- 'pulse-border-[1px] pulse-rounded-md',
96
+ 'pulse:bg-white pulse:border pulse:border-neutral-300',
97
+ 'pulse:border-solid pulse:rounded-md',
94
98
  PADDING_MAPPINGS[fetch_or_fallback(PADDING_MAPPINGS.keys, padding,
95
99
  DEFAULT_PADDING)],
96
100
  system_arguments[:classes]
@@ -101,8 +105,8 @@ module Pulse
101
105
  @list_arguments = {
102
106
  tag: :ul,
103
107
  class: class_names
104
- # 'pulse-divide-y pulse-divide-gray-300',
105
- # 'pulse-border-b pulse-border-gray-300 pulse-border-solid'
108
+ # 'pulse:divide-y pulse:divide-neutral-300',
109
+ # 'pulse:border-b pulse:border-neutral-300 pulse:border-solid'
106
110
 
107
111
  }
108
112
  end
@@ -3,6 +3,8 @@
3
3
  module Pulse
4
4
  # `Box` is a basic wrapper component for most layout related needs.
5
5
  class Box < Pulse::Component
6
+ status :stable
7
+
6
8
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
7
9
  def initialize(**system_arguments)
8
10
  @system_arguments = deny_tag_argument(**system_arguments)
@@ -3,7 +3,9 @@
3
3
  module Pulse
4
4
  # Renders a breadcrumb item
5
5
  class BreadcrumbItem < Pulse::Component
6
- DEFAULT_LINK_CLASSES = 'pulse-underline'
6
+ status :stable
7
+
8
+ DEFAULT_LINK_CLASSES = 'pulse:underline'
7
9
  DEFAULT_TEXT_CLASSES = ''
8
10
 
9
11
  attr_reader :text, :href
@@ -1,9 +1,9 @@
1
1
  /! components/pulse/breadcrumbs
2
2
  = render Pulse::BaseComponent.new(**@system_arguments) do
3
- ol class="pulse-list-none pulse-list-reset pulse-flex pulse-m-0 pulse-text-xs"
3
+ ol class="pulse:list-none pulse:flex pulse:m-0 pulse:text-xs"
4
4
  - items.filter { |i| i.text.present? }.each_with_index do |item, idx|
5
5
  - if idx != 0
6
- li class="pulse-mx-1" aria-hidden="true"
6
+ li class="pulse:mx-1" aria-hidden="true"
7
7
  | >
8
8
  li
9
9
  = item
@@ -3,6 +3,8 @@
3
3
  module Pulse
4
4
  # Renders a breadcrumb navigation list
5
5
  class Breadcrumbs < Pulse::Component
6
+ status :stable
7
+
6
8
  renders_many :items, Pulse::BreadcrumbItem
7
9
 
8
10
  def initialize(**system_arguments)
@@ -3,6 +3,8 @@
3
3
  module Pulse
4
4
  # Renders a button or link button
5
5
  class Button < Pulse::Component
6
+ status :stable
7
+
6
8
  attr_reader :kwargs, :label, :url, :tag, :button_to_helper,
7
9
  :disabled
8
10
 
@@ -10,28 +12,29 @@ module Pulse
10
12
 
11
13
  DEFAULT_SCHEME = :secondary
12
14
  SCHEME_MAPPINGS = {
13
- primary: 'pulse-border pulse-border-solid pulse-border-gray-300 ' \
14
- 'pulse-bg-green-700 pulse-text-white hover:pulse-bg-green-800 ' \
15
- 'focus-visible:pulse-bg-green-800 hover:pulse-text-white ' \
16
- 'focus-visible:pulse-text-white ' \
17
- 'focus-visible:pulse-shadow-btn-inset',
18
- secondary: 'pulse-border pulse-border-solid pulse-border-gray-300 ' \
19
- 'pulse-bg-gray-50 pulse-text-gray hover:pulse-bg-gray-100 ' \
20
- 'focus-visible:pulse-bg-gray-100',
21
- danger: 'pulse-border pulse-border-solid pulse-border-gray-300 ' \
22
- 'pulse-bg-gray-50 pulse-text-danger-800 ' \
23
- 'hover:pulse-bg-danger-800 focus-visible:pulse-bg-danger-800 ' \
24
- 'hover:pulse-text-white focus-visible:pulse-text-white ' \
25
- 'focus-visible:pulse-shadow-btn-inset'
15
+ primary: 'pulse:border pulse:border-solid pulse:border-neutral-300 ' \
16
+ 'pulse:bg-green-700 pulse:text-white pulse:hover:bg-green-800 ' \
17
+ 'pulse:focus-visible:bg-green-800 pulse:hover:text-white ' \
18
+ 'pulse:focus-visible:text-white ' \
19
+ 'pulse:focus-visible:shadow-btn-inset',
20
+ secondary: 'pulse:border pulse:border-solid pulse:border-neutral-300 ' \
21
+ 'pulse:bg-neutral-50 pulse:text-neutral-700 ' \
22
+ 'pulse:hover:bg-neutral-100 ' \
23
+ 'pulse:focus-visible:bg-neutral-100',
24
+ danger: 'pulse:border pulse:border-solid pulse:border-neutral-300 ' \
25
+ 'pulse:bg-neutral-50 pulse:text-danger-800 ' \
26
+ 'pulse:hover:bg-danger-800 pulse:focus-visible:bg-danger-800 ' \
27
+ 'pulse:hover:text-white pulse:focus-visible:text-white ' \
28
+ 'pulse:focus-visible:shadow-btn-inset'
26
29
  }.freeze
27
30
  SCHEME_OPTIONS = SCHEME_MAPPINGS.keys
28
31
 
29
32
  DEFAULT_SIZE = :md
30
33
  SIZE_MAPPINGS = {
31
- xs: 'pulse-text-xs pulse-leading-5 pulse-font-medium pulse-py-0.5 ' \
32
- 'pulse-px-4',
33
- sm: 'pulse-text-sm pulse-font-medium pulse-py-1 pulse-px-5',
34
- md: 'pulse-text-base pulse-font-bold pulse-py-1.5 pulse-px-6'
34
+ xs: 'pulse:text-xs pulse:leading-5 pulse:font-medium pulse:py-0.5 ' \
35
+ 'pulse:px-4',
36
+ sm: 'pulse:text-sm pulse:font-medium pulse:py-1 pulse:px-5',
37
+ md: 'pulse:text-base pulse:font-bold pulse:py-1.5 pulse:px-6'
35
38
  }.freeze
36
39
  SIZE_OPTIONS = SIZE_MAPPINGS.keys
37
40
 
@@ -45,7 +48,7 @@ module Pulse
45
48
  renders_one :leading_visual, types: {
46
49
  icon: lambda { |**system_arguments|
47
50
  system_arguments[:classes] = merge_classes(
48
- 'pulse-me-2',
51
+ 'pulse:me-2',
49
52
  system_arguments[:classes]
50
53
  )
51
54
 
@@ -82,13 +85,13 @@ module Pulse
82
85
 
83
86
  def btn_class
84
87
  merge_classes(
85
- 'pulse-appearance-none pulse-inline-flex pulse-justify-center',
86
- 'pulse-items-center pulse-rounded-md pulse-no-underline',
87
- 'hover:pulse-no-underline focus-visible:pulse-no-underline',
88
- 'focus-visible:-pulse-outline-offset-2',
89
- 'focus-visible:pulse-outline-[2px] focus-visible:pulse-outline-solid',
90
- 'focus-visible:pulse-outline-[var(--pulse-focus-outlineColor)]',
91
- 'disabled:pulse-opacity-25 disabled:pulse-cursor-not-allowed',
88
+ 'pulse:appearance-none pulse:inline-flex pulse:justify-center',
89
+ 'pulse:items-center pulse:rounded-md pulse:no-underline',
90
+ 'pulse:hover:no-underline pulse:focus-visible:no-underline',
91
+ 'pulse:focus-visible:-outline-offset-2',
92
+ 'pulse:focus-visible:outline-2 pulse:focus-visible:outline-solid',
93
+ 'pulse:focus-visible:outline-(--pulse-focus-outlineColor)',
94
+ 'pulse:disabled:opacity-25 pulse:disabled:cursor-not-allowed',
92
95
  SIZE_MAPPINGS[@size],
93
96
  SCHEME_MAPPINGS[@scheme],
94
97
  kwargs.fetch(:classes, '')
@@ -6,11 +6,12 @@ module Pulse
6
6
  # This core `Component` class provides a place to include shared modules
7
7
  # and helper methods that can be used by any child component.
8
8
  class Component < ViewComponent::Base
9
- TW_MERGE = TailwindMerge::Merger.new(config: { prefix: 'pulse-' })
9
+ TW_MERGE = TailwindMerge::Merger.new(config: { prefix: 'pulse' })
10
10
 
11
11
  include AttributesHelper
12
12
  include FetchOrFallbackHelper
13
13
  include SvgHelper
14
+ include Status
14
15
 
15
16
  # include Pulse::ViewHelper
16
17
 
@@ -5,6 +5,8 @@ module Pulse
5
5
  # is true, a `BaseComponent` will render around the content. If the condition is false, only
6
6
  # the content is rendered.
7
7
  class ConditionalWrapper < Pulse::Component
8
+ status :stable
9
+
8
10
  # @param condition [Boolean] Whether or not to wrap the content in a `BaseComponent`.
9
11
  # @param base_component_arguments [Hash] The arguments to pass to `BaseComponent`.
10
12
  def initialize(condition:, **base_component_arguments)
@@ -4,6 +4,8 @@ module Pulse
4
4
  # Use `Content` as a helper to render content passed to a slot without adding
5
5
  # any tags.
6
6
  class Content < Pulse::Component
7
+ status :stable
8
+
7
9
  def call
8
10
  content
9
11
  end
@@ -1,10 +1,10 @@
1
1
  /* Counter */
2
2
  .Counter {
3
- @apply pulse-inline-block pulse-min-w-[var(--base-size-20)] pulse-text-[length:var(--text-body-size-small)] pulse-font-[number:var(--base-text-weight-medium)] pulse-leading-[calc(var(--base-size-20)_-_var(--borderWidth-thin)_*_2)] pulse-text-[color:var(--fgColor-default)] pulse-text-center bg-[color:var(--bgColor-neutral-muted,var(--color-neutral-muted))] pulse-border-[length:var(--borderWidth-thin)] pulse-border-[color:var(--counter-borderColor)] pulse-px-1.5 pulse-py-0 pulse-rounded-[2em] pulse-border-solid empty:pulse-hidden; /* makes sure it's a circle with just one digit */ /* 20px - 2px for the borders */
3
+ @apply pulse:inline-block pulse:min-w-[var(--base-size-20)] pulse:text-[length:var(--text-body-size-small)] pulse:font-[number:var(--base-text-weight-medium)] pulse:leading-[calc(var(--base-size-20)_-_var(--borderWidth-thin)_*_2)] pulse:text-[color:var(--fgColor-default)] pulse:text-center pulse:bg-[color:var(--bgColor-neutral-muted,var(--color-neutral-muted))] pulse:border-[length:var(--borderWidth-thin)] pulse:border-[color:var(--counter-borderColor)] pulse:px-1.5 pulse:py-0 pulse:rounded-[2em] pulse:border-solid pulse:empty:hidden; /* makes sure it's a circle with just one digit */ /* 20px - 2px for the borders */
4
4
  }
5
5
  .Counter--primary {
6
- @apply pulse-text-[color:var(--pulse-fgColor-onEmphasis)] pulse-bg-[color:var(--bgColor-neutral-emphasis)];
6
+ @apply pulse:text-[color:var(--pulse-fgColor-onEmphasis)] pulse:bg-[color:var(--bgColor-neutral-emphasis)];
7
7
  }
8
8
  .Counter--secondary {
9
- @apply pulse-text-[color:var(--fgColor-muted)] pulse-bg-[color:var(--bgColor-neutral-muted)];
9
+ @apply pulse:text-[color:var(--fgColor-muted)] pulse:bg-[color:var(--bgColor-neutral-muted)];
10
10
  }
@@ -8,6 +8,10 @@ module Pulse
8
8
  # should be accompanied with text such as `issues` or `pull requests`.
9
9
  #
10
10
  class Counter < Pulse::Component
11
+ # Incomplete: relies on Primer's `Counter` CSS, which is not in the built
12
+ # stylesheet, so the counter renders as unstyled text.
13
+ status :alpha
14
+
11
15
  DEFAULT_SCHEME = :default
12
16
  SCHEME_MAPPINGS = {
13
17
  DEFAULT_SCHEME => '',
@@ -3,6 +3,10 @@
3
3
  module Pulse
4
4
  # Use `Flash` to inform users of successful or pending actions.
5
5
  class Flash < Pulse::Component
6
+ # Messages, schemes, icons and actions work. `dismissible: true` raises
7
+ # because `Pulse::IconButton` does not exist, and `full: true` has no styles.
8
+ status :beta
9
+
6
10
  # Optional action content showed on the right side of the component.
7
11
  #
8
12
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
@@ -20,10 +24,10 @@ module Pulse
20
24
  # TODO: Style icon in scheme color
21
25
  DEFAULT_SCHEME = :default
22
26
  SCHEME_MAPPINGS = {
23
- default: 'pulse-bg-[#ddf4ff] pulse-border pulse-border-[#54aeff]',
24
- warning: 'pulse-bg-[#fff8c5] pulse-border pulse-border-[#f6e05e]',
25
- danger: 'pulse-bg-[#ffebe9] pulse-border pulse-border-[#ff8182]',
26
- success: 'pulse-bg-[#dafbe1] pulse-border pulse-border-[#4ac26b]'
27
+ default: 'pulse:bg-[#ddf4ff] pulse:border pulse:border-[#54aeff]',
28
+ warning: 'pulse:bg-[#fff8c5] pulse:border pulse:border-[#f6e05e]',
29
+ danger: 'pulse:bg-[#ffebe9] pulse:border pulse:border-[#ff8182]',
30
+ success: 'pulse:bg-[#dafbe1] pulse:border pulse:border-[#4ac26b]'
27
31
  }.freeze
28
32
 
29
33
  # @param full [Boolean] Whether the component should take up the full width of the screen.
@@ -39,8 +43,8 @@ module Pulse
39
43
  @system_arguments = deny_tag_argument(**system_arguments)
40
44
  @system_arguments[:tag] = :div
41
45
  @system_arguments[:classes] = class_names(
42
- 'pulse-px-3 pulse-py-2 pulse-font-medium pulse-rounded-lg pulse-my-3',
43
- 'pulse-flex pulse-gap-2 pulse-items-center pulse-justify-start',
46
+ 'pulse:px-3 pulse:py-2 pulse:font-medium pulse:rounded-lg pulse:my-3',
47
+ 'pulse:flex pulse:gap-2 pulse:items-center pulse:justify-start',
44
48
  SCHEME_MAPPINGS[fetch_or_fallback(SCHEME_MAPPINGS.keys, scheme,
45
49
  DEFAULT_SCHEME)],
46
50
  system_arguments[:classes],
@@ -1,8 +1,8 @@
1
1
  /! components/pulse/forms/error_explanation
2
- = render Pulse::BaseComponent.new(tag: :div, **@options) do
2
+ = render Pulse::BaseComponent.new(**@system_arguments) do
3
3
  = render Pulse::Text.new(type: :body1, tag: :h2) do
4
- = t('errors.template.header', count: resource.errors.count, model: resource.class.model_name.human.downcase)
4
+ = header
5
5
 
6
- ul class="pulse-list-disc pulse-list-inside"
6
+ ul class="pulse:list-disc pulse:list-inside"
7
7
  - errors.full_messages.each do |msg|
8
8
  li = msg
@@ -3,25 +3,42 @@
3
3
  module Pulse
4
4
  module Forms
5
5
  # Render error message box for an ActiveModel resource
6
- class ErrorExplanation < ViewComponent::Base
7
- def initialize(resource:, **options)
6
+ class ErrorExplanation < Pulse::Component
7
+ status :stable
8
+
9
+ # @param resource [ActiveModel::Validations] The record whose errors are listed.
10
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
11
+ def initialize(resource:, **system_arguments)
8
12
  @resource = resource
9
- @options = options
10
- @options[:classes] = class_names(
11
- 'pulse-bg-error-50 pulse-text-error pulse-px-3 pulse-py-2',
12
- 'pulse-font-medium pulse-rounded-lg pulse-my-3',
13
- options[:classes]
13
+ @system_arguments = deny_tag_argument(**system_arguments)
14
+ @system_arguments[:tag] = :div
15
+ @system_arguments[:classes] = merge_classes(
16
+ 'pulse:bg-error-50 pulse:text-error pulse:px-3 pulse:py-2',
17
+ 'pulse:font-medium pulse:rounded-lg pulse:my-3',
18
+ system_arguments[:classes]
14
19
  )
15
20
  end
16
21
 
17
22
  def errors
18
- @errors = ActiveModel::Errors.new(resource)
19
- # Only display the last of each attribute error, to avoid needless
20
- # duplication.
21
- resource.errors.group_by(&:attribute).values.map(&:last).each do |error|
22
- @errors.add(error.attribute, error.message)
23
+ @errors ||= ActiveModel::Errors.new(resource).tap do |errors|
24
+ # Only display the last of each attribute error, to avoid needless
25
+ # duplication.
26
+ resource.errors.group_by(&:attribute).each_value do |group|
27
+ errors.add(group.last.attribute, group.last.message)
28
+ end
23
29
  end
24
- @errors
30
+ end
31
+
32
+ # Prefers the app's `errors.template.header` so existing wording is
33
+ # kept. The sidecar is English-only, so other locales fall back to its
34
+ # English text rather than raising under `raise_on_missing_translations`.
35
+ def header
36
+ options = { count: errors.count,
37
+ model: resource.class.model_name.human.downcase }
38
+
39
+ t('errors.template.header', **options,
40
+ default: t('.header', **options,
41
+ default: t('.header', **options, locale: :en)))
25
42
  end
26
43
 
27
44
  def render?
@@ -0,0 +1,4 @@
1
+ en:
2
+ header:
3
+ one: "1 error prohibited this %{model} from being saved:"
4
+ other: "%{count} errors prohibited this %{model} from being saved:"
@@ -3,6 +3,8 @@
3
3
  module Pulse
4
4
  # Render icons from the images/pulse_view_components/icons directory.
5
5
  class Icon < Pulse::Component
6
+ status :stable
7
+
6
8
  SIZE_DEFAULT = :sm
7
9
  SIZE_MAPPINGS = {
8
10
  xs: 12,
@@ -5,6 +5,8 @@ module Pulse
5
5
  # consolidated with badges?
6
6
  # Accepts Hex color value for bg_color, with or without a leading "#"
7
7
  class Label < Pulse::Component
8
+ status :stable
9
+
8
10
  attr_reader :title, :bg_color
9
11
 
10
12
  def initialize(title:, bg_color: 'ffffff', **options)
@@ -12,8 +14,8 @@ module Pulse
12
14
  @bg_color = bg_color || 'ffffff'
13
15
  @options = options
14
16
  @options[:classes] = merge_classes(
15
- 'pulse-px-2 pulse-inline-flex pulse-text-xs print:pulse-text-2xs',
16
- 'pulse-leading-5 pulse-font-semibold pulse-rounded-full',
17
+ 'pulse:px-2 pulse:inline-flex pulse:text-xs pulse:print:text-2xs',
18
+ 'pulse:leading-5 pulse:font-semibold pulse:rounded-full',
17
19
  text_color,
18
20
  options[:classes]
19
21
  )
@@ -36,7 +38,7 @@ module Pulse
36
38
  luminance = (0.2126 * r_norm) + (0.7152 * g_norm) + (0.0722 * b_norm)
37
39
 
38
40
  # Choose text color based on luminance
39
- luminance > 0.5 ? 'pulse-text-black' : 'pulse-text-white'
41
+ luminance > 0.5 ? 'pulse:text-black' : 'pulse:text-white'
40
42
  end
41
43
  end
42
44
  end