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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +98 -1
- data/README.md +26 -0
- data/app/assets/builds/js/pulse_view_components.js +3 -3
- data/app/assets/builds/js/pulse_view_components.js.map +1 -1
- data/app/assets/builds/pulse_view_components.css +2 -1
- data/app/assets/stylesheets/pulse_view_components/application.tailwind.css +44 -4
- data/app/assets/stylesheets/pulse_view_components/tokens.primitives.css +68 -0
- data/app/assets/stylesheets/pulse_view_components/tokens.system.css +95 -0
- data/app/components/pulse/action_list/divider.rb +13 -4
- data/app/components/pulse/action_list/form_wrapper.rb +2 -0
- data/app/components/pulse/action_list/heading.html.slim +7 -0
- data/app/components/pulse/action_list/heading.rb +2 -0
- data/app/components/pulse/action_list/item.html.slim +6 -6
- data/app/components/pulse/action_list/item.rb +11 -9
- data/app/components/pulse/action_list.css +109 -82
- data/app/components/pulse/action_list.html.slim +11 -0
- data/app/components/pulse/action_list.rb +5 -0
- data/app/components/pulse/badge.rb +20 -16
- data/app/components/pulse/base_component.rb +25 -1
- data/app/components/pulse/blank_slate.rb +4 -2
- data/app/components/pulse/border_box/header.rb +5 -3
- data/app/components/pulse/border_box.rb +21 -17
- data/app/components/pulse/box.rb +2 -0
- data/app/components/pulse/breadcrumb_item.rb +3 -1
- data/app/components/pulse/breadcrumbs.html.slim +2 -2
- data/app/components/pulse/breadcrumbs.rb +2 -0
- data/app/components/pulse/button.rb +28 -25
- data/app/components/pulse/component.rb +2 -1
- data/app/components/pulse/conditional_wrapper.rb +2 -0
- data/app/components/pulse/content.rb +2 -0
- data/app/components/pulse/counter.css +3 -3
- data/app/components/pulse/counter.rb +4 -0
- data/app/components/pulse/flash.rb +10 -6
- data/app/components/pulse/forms/error_explanation.html.slim +3 -3
- data/app/components/pulse/forms/error_explanation.rb +30 -13
- data/app/components/pulse/forms/error_explanation.yml +4 -0
- data/app/components/pulse/icon.rb +2 -0
- data/app/components/pulse/label.rb +5 -3
- data/app/components/pulse/layout.css +53 -52
- data/app/components/pulse/layout.rb +14 -6
- data/app/components/pulse/link.rb +10 -8
- data/app/components/pulse/modal.html.slim +20 -20
- data/app/components/pulse/modal.rb +10 -8
- data/app/components/pulse/modal_controller.js +3 -3
- data/app/components/pulse/nav_link.html.slim +7 -7
- data/app/components/pulse/nav_link.rb +4 -2
- data/app/components/pulse/nav_list/heading.rb +3 -1
- data/app/components/pulse/nav_list/item.html.slim +8 -0
- data/app/components/pulse/nav_list/item.rb +2 -2
- data/app/components/pulse/nav_list.rb +5 -0
- data/app/components/pulse/popover.html.slim +1 -1
- data/app/components/pulse/popover.rb +65 -26
- data/app/components/pulse/progress_bar.html.slim +2 -2
- data/app/components/pulse/progress_bar.rb +5 -3
- data/app/components/pulse/table/cell.html.slim +2 -2
- data/app/components/pulse/table/cell.rb +14 -10
- data/app/components/pulse/table/column.rb +2 -0
- data/app/components/pulse/table.html.slim +3 -3
- data/app/components/pulse/table.rb +19 -16
- data/app/components/pulse/text.rb +26 -24
- data/app/components/pulse/toast.html.slim +8 -8
- data/app/components/pulse/toast.rb +25 -23
- data/app/lib/pulse/status.rb +48 -0
- data/lib/pulse/view_components/version.rb +1 -1
- data/previews/foundations/colors_preview/palettes.html.slim +12 -0
- data/previews/foundations/colors_preview/semantic.html.slim +16 -0
- data/previews/foundations/colors_preview.rb +55 -0
- data/previews/pulse/action_list_preview.rb +77 -0
- data/previews/pulse/border_box_preview.rb +1 -1
- data/previews/pulse/counter_preview.rb +1 -1
- data/previews/pulse/flash_preview.rb +1 -1
- data/previews/pulse/forms/error_explanation_preview.rb +49 -0
- data/previews/pulse/layout_preview.rb +5 -5
- data/previews/pulse/nav_list_preview.rb +1 -1
- data/previews/pulse/popover_preview.rb +48 -22
- data/previews/pulse/progress_bar_preview.rb +1 -1
- data/previews/pulse/table_preview.rb +6 -6
- metadata +17 -11
|
@@ -9,22 +9,52 @@ module Pulse
|
|
|
9
9
|
# positioned properly. To render the popover with relative positioning, use
|
|
10
10
|
# the relative property.
|
|
11
11
|
class Popover < Pulse::Component
|
|
12
|
+
status :stable
|
|
13
|
+
|
|
14
|
+
# The caret is a white square rotated 45 degrees and drawn over the edge of
|
|
15
|
+
# the body, with a border on the two sides that face outward.
|
|
16
|
+
CARET_BASE = 'pulse:before:absolute pulse:before:size-3 ' \
|
|
17
|
+
'pulse:before:rotate-45 pulse:before:bg-white ' \
|
|
18
|
+
'pulse:before:border-neutral-300'
|
|
19
|
+
CARET_TOP = 'pulse:before:-top-1.5 pulse:before:border-t ' \
|
|
20
|
+
'pulse:before:border-l'
|
|
21
|
+
CARET_BOTTOM = 'pulse:before:-bottom-1.5 pulse:before:border-b ' \
|
|
22
|
+
'pulse:before:border-r'
|
|
23
|
+
CARET_LEFT = 'pulse:before:-left-1.5 pulse:before:border-b ' \
|
|
24
|
+
'pulse:before:border-l'
|
|
25
|
+
CARET_RIGHT = 'pulse:before:-right-1.5 pulse:before:border-t ' \
|
|
26
|
+
'pulse:before:border-r'
|
|
27
|
+
CARET_CENTER_X = 'pulse:before:left-1/2 pulse:before:-translate-x-1/2'
|
|
28
|
+
CARET_CENTER_Y = 'pulse:before:top-1/2 pulse:before:-translate-y-1/2'
|
|
29
|
+
|
|
12
30
|
CARET_DEFAULT = :top
|
|
13
31
|
CARET_MAPPINGS = {
|
|
14
|
-
CARET_DEFAULT =>
|
|
15
|
-
:
|
|
16
|
-
:
|
|
17
|
-
:
|
|
18
|
-
:
|
|
19
|
-
:
|
|
20
|
-
:
|
|
21
|
-
:
|
|
22
|
-
:
|
|
23
|
-
:
|
|
24
|
-
:
|
|
25
|
-
:
|
|
32
|
+
CARET_DEFAULT => "#{CARET_TOP} #{CARET_CENTER_X}",
|
|
33
|
+
:top_left => "#{CARET_TOP} pulse:before:left-6",
|
|
34
|
+
:top_right => "#{CARET_TOP} pulse:before:right-6",
|
|
35
|
+
:bottom => "#{CARET_BOTTOM} #{CARET_CENTER_X}",
|
|
36
|
+
:bottom_left => "#{CARET_BOTTOM} pulse:before:left-6",
|
|
37
|
+
:bottom_right => "#{CARET_BOTTOM} pulse:before:right-6",
|
|
38
|
+
:left => "#{CARET_LEFT} #{CARET_CENTER_Y}",
|
|
39
|
+
:left_top => "#{CARET_LEFT} pulse:before:top-6",
|
|
40
|
+
:left_bottom => "#{CARET_LEFT} pulse:before:bottom-6",
|
|
41
|
+
:right => "#{CARET_RIGHT} #{CARET_CENTER_Y}",
|
|
42
|
+
:right_top => "#{CARET_RIGHT} pulse:before:top-6",
|
|
43
|
+
:right_bottom => "#{CARET_RIGHT} pulse:before:bottom-6"
|
|
44
|
+
}.freeze
|
|
45
|
+
CARET_OPTIONS = CARET_MAPPINGS.keys.freeze
|
|
46
|
+
|
|
47
|
+
# Leaves room for the caret on the side it points from.
|
|
48
|
+
CARET_MARGIN_MAPPINGS = {
|
|
49
|
+
top: 'pulse:mt-2',
|
|
50
|
+
bottom: 'pulse:mb-2',
|
|
51
|
+
left: 'pulse:ml-2',
|
|
52
|
+
right: 'pulse:mr-2'
|
|
26
53
|
}.freeze
|
|
27
54
|
|
|
55
|
+
DEFAULT_WIDTH = 'pulse:w-64'
|
|
56
|
+
LARGE_WIDTH = 'pulse:w-80'
|
|
57
|
+
|
|
28
58
|
DEFAULT_HEADING_TAG = :h4
|
|
29
59
|
|
|
30
60
|
# The heading
|
|
@@ -36,7 +66,7 @@ module Pulse
|
|
|
36
66
|
system_arguments[:type] = :body3
|
|
37
67
|
system_arguments[:tag] = tag
|
|
38
68
|
system_arguments[:classes] = merge_classes(
|
|
39
|
-
'pulse
|
|
69
|
+
'pulse:mb-2',
|
|
40
70
|
system_arguments[:classes]
|
|
41
71
|
)
|
|
42
72
|
|
|
@@ -50,18 +80,19 @@ module Pulse
|
|
|
50
80
|
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
|
|
51
81
|
renders_one :body, lambda { |caret: CARET_DEFAULT, large: false,
|
|
52
82
|
**system_arguments|
|
|
83
|
+
caret = fetch_or_fallback(CARET_OPTIONS, caret, CARET_DEFAULT)
|
|
84
|
+
side = caret.to_s.split('_').first.to_sym
|
|
85
|
+
@large = large
|
|
86
|
+
|
|
53
87
|
@body_arguments = system_arguments
|
|
54
88
|
@body_arguments['data-caret'] = caret
|
|
55
89
|
@body_arguments[:classes] = merge_classes(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
@body_arguments[:classes],
|
|
63
|
-
'pulse-bg-white pulse-border pulse-border-gray-300 pulse-rounded-lg',
|
|
64
|
-
'pulse-p-4 pulse-mt-2 pulse-mx-auto pulse-text-left pulse-shadow-lg'
|
|
90
|
+
'pulse:relative pulse:bg-white pulse:border pulse:border-neutral-300',
|
|
91
|
+
'pulse:rounded-lg pulse:p-4 pulse:text-left pulse:shadow-lg',
|
|
92
|
+
CARET_MARGIN_MAPPINGS[side],
|
|
93
|
+
CARET_BASE,
|
|
94
|
+
CARET_MAPPINGS[caret],
|
|
95
|
+
system_arguments[:classes]
|
|
65
96
|
)
|
|
66
97
|
|
|
67
98
|
Pulse::Content.new
|
|
@@ -71,16 +102,24 @@ module Pulse
|
|
|
71
102
|
def initialize(**system_arguments)
|
|
72
103
|
@system_arguments = deny_tag_argument(**system_arguments)
|
|
73
104
|
@system_arguments[:tag] = :div
|
|
74
|
-
@system_arguments[:classes] = merge_classes(
|
|
75
|
-
'pulse-relative pulse-z-10 pulse-w-64',
|
|
76
|
-
system_arguments[:classes]
|
|
77
|
-
)
|
|
78
105
|
end
|
|
79
106
|
|
|
80
107
|
def render?
|
|
81
108
|
body.present?
|
|
82
109
|
end
|
|
83
110
|
|
|
111
|
+
# Built from the template because the body slot, which sets `large`, is
|
|
112
|
+
# only evaluated once rendering starts.
|
|
113
|
+
def wrapper_arguments
|
|
114
|
+
@system_arguments.merge(
|
|
115
|
+
classes: merge_classes(
|
|
116
|
+
'pulse:relative pulse:z-10',
|
|
117
|
+
@large ? LARGE_WIDTH : DEFAULT_WIDTH,
|
|
118
|
+
@system_arguments[:classes]
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
84
123
|
def body_component
|
|
85
124
|
Pulse::Box.new(**@body_arguments)
|
|
86
125
|
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/! components/pulse/progress_bar
|
|
2
2
|
= render Pulse::BaseComponent.new(**@system_attributes) do
|
|
3
|
-
div class="pulse
|
|
3
|
+
div class="pulse:h-1 pulse:bg-success" style="width: #{[@percentage, 100].min}%"
|
|
4
4
|
- if @percentage > 100
|
|
5
|
-
div class="pulse
|
|
5
|
+
div class="pulse:h-1 pulse:bg-error" style="width: #{@percentage - 100}%"
|
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
module Pulse
|
|
4
4
|
# Renders a simple progress bar
|
|
5
5
|
class ProgressBar < Pulse::Component
|
|
6
|
+
status :stable
|
|
7
|
+
|
|
6
8
|
def initialize(percentage: 0, **system_attributes)
|
|
7
9
|
@percentage = percentage.to_i
|
|
8
10
|
@system_attributes = system_attributes
|
|
9
11
|
@system_attributes[:tag] ||= :div
|
|
10
|
-
@system_attributes[:
|
|
11
|
-
'pulse
|
|
12
|
-
'dark:
|
|
12
|
+
@system_attributes[:classes] = merge_classes(
|
|
13
|
+
'pulse:flex pulse:flex-row pulse:h-1 pulse:w-full pulse:bg-neutral-300',
|
|
14
|
+
'pulse:dark:bg-neutral-600',
|
|
13
15
|
system_attributes[:classes]
|
|
14
16
|
)
|
|
15
17
|
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/! components/pulse/tables/cell
|
|
2
2
|
= render Pulse::BaseComponent.new(**options) do
|
|
3
3
|
- if sortable?
|
|
4
|
-
= link_to request.params.merge(**url_options), class: merge_classes('pulse
|
|
4
|
+
= link_to request.params.merge(**url_options), class: merge_classes('pulse:inline-flex pulse:items-center pulse:gap-1', title_classes) do
|
|
5
5
|
= content
|
|
6
|
-
= render Pulse::Icon.new(icon: icon, width: 9, height: 8, classes: 'pulse
|
|
6
|
+
= render Pulse::Icon.new(icon: icon, width: 9, height: 8, classes: 'pulse:shrink-0', title: t(".#{icon}"))
|
|
7
7
|
|
|
8
8
|
- else
|
|
9
9
|
span class=title_classes
|
|
@@ -4,24 +4,28 @@ module Pulse
|
|
|
4
4
|
class Table
|
|
5
5
|
# Renders a table cell
|
|
6
6
|
class Cell < Pulse::Component
|
|
7
|
+
status :stable
|
|
8
|
+
|
|
7
9
|
attr_reader :sort_by, :current_sort_column, :current_sort_direction,
|
|
8
10
|
:additional_params, :footer
|
|
9
11
|
|
|
10
12
|
HEAD_DEFAULT_SCHEME = :xs
|
|
11
13
|
HEAD_SCHEME_MAPPINGS = {
|
|
12
|
-
sm: 'pulse
|
|
13
|
-
'pulse
|
|
14
|
-
xs: 'pulse
|
|
15
|
-
'print:
|
|
14
|
+
sm: 'pulse:px-3 pulse:py-4 pulse:print:px-2 pulse:print:py-3 ' \
|
|
15
|
+
'pulse:text-sm pulse:print:text-xs pulse:font-normal',
|
|
16
|
+
xs: 'pulse:px-2 pulse:py-4 pulse:print:py-2 pulse:text-xs ' \
|
|
17
|
+
'pulse:print:text-2xs pulse:font-medium'
|
|
16
18
|
}.freeze
|
|
17
19
|
HEAD_SCHEME_OPTIONS = HEAD_SCHEME_MAPPINGS.keys.freeze
|
|
18
20
|
|
|
19
|
-
DEFAULT_CELL_CLASSES = 'pulse
|
|
20
|
-
'last:
|
|
21
|
-
'
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
DEFAULT_CELL_CLASSES = 'pulse:text-start pulse:first:ps-4 ' \
|
|
22
|
+
'pulse:last:pe-4 pulse:px-2 ' \
|
|
23
|
+
'pulse:text-neutral-700 ' \
|
|
24
|
+
'pulse:print:first:ps-2 pulse:print:last:pe-2'
|
|
25
|
+
DEFAULT_TH_CLASSES = 'pulse:uppercase pulse:tracking-wider'
|
|
26
|
+
DEFAULT_TD_CLASSES = 'pulse:py-2 pulse:whitespace-nowrap ' \
|
|
27
|
+
'pulse:text-sm pulse:print:text-xs ' \
|
|
28
|
+
'pulse:text-neutral-700'
|
|
25
29
|
|
|
26
30
|
def initialize(sort_by: nil, current_sort_column: nil,
|
|
27
31
|
current_sort_direction: nil, additional_params: {},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/! components/pulse/table
|
|
2
2
|
- if collection.present?
|
|
3
|
-
div class="pulse
|
|
4
|
-
div class=class_names("pulse
|
|
3
|
+
div class="pulse:flex pulse:flex-col pulse:gap-4"
|
|
4
|
+
div class=class_names("pulse:w-full pulse:shadow pulse:print:shadow-none pulse:sm:rounded-lg", 'pulse:overflow-x-scroll': scroll)
|
|
5
5
|
= render Pulse::BaseComponent.new(**table_options) do
|
|
6
6
|
- if header
|
|
7
7
|
= render Pulse::BaseComponent.new(tag: :thead, classes: head_classes) do
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
- if pagination? && collection.respond_to?(:total_pages)
|
|
42
|
-
div class="pulse
|
|
42
|
+
div class="pulse:text-center pulse:text-neutral-700"
|
|
43
43
|
= paginate(collection, window: 9)
|
|
44
44
|
|
|
45
45
|
- else
|
|
@@ -5,18 +5,21 @@ module Pulse
|
|
|
5
5
|
# Inspiration taken from
|
|
6
6
|
# https://github.com/baoagency/polaris_view_components/
|
|
7
7
|
class Table < Pulse::Component
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
status :stable
|
|
9
|
+
|
|
10
|
+
DEFAULT_TABLE_CLASSES = 'pulse:min-w-full pulse:divide-y ' \
|
|
11
|
+
'pulse:divide-neutral-300'
|
|
12
|
+
DEFAULT_HEAD_CLASSES = 'pulse:bg-neutral-100'
|
|
13
|
+
DEFAULT_TH_CLASSES = 'pulse:text-start pulse:text-neutral-700 ' \
|
|
14
|
+
'pulse:uppercase ' \
|
|
15
|
+
'pulse:tracking-wider pulse:px-4 pulse:print:px-2 ' \
|
|
16
|
+
'pulse:py-4 pulse:print:py-3 pulse:text-xs ' \
|
|
17
|
+
'pulse:font-medium'
|
|
18
|
+
DEFAULT_BODY_CLASSES = 'pulse:bg-white pulse:divide-y pulse:text-sm ' \
|
|
19
|
+
'pulse:leading-snug pulse:divide-neutral-300 ' \
|
|
20
|
+
'pulse:print:divide-neutral-400 pulse:tracking-tight'
|
|
21
|
+
DEFAULT_ROW_CLASSES = 'pulse:border-secondary-300'
|
|
22
|
+
DEFAULT_TD_CLASSES = 'pulse:px-4 pulse:print:px-2 pulse:py-4'
|
|
20
23
|
|
|
21
24
|
delegate :paginate, to: :helpers
|
|
22
25
|
|
|
@@ -58,7 +61,7 @@ module Pulse
|
|
|
58
61
|
{ tag: 'table' }.deep_merge(@options).tap do |args|
|
|
59
62
|
args[:classes] = merge_classes(
|
|
60
63
|
DEFAULT_TABLE_CLASSES,
|
|
61
|
-
'pulse
|
|
64
|
+
'pulse:container pulse:mx-auto',
|
|
62
65
|
args[:classes]
|
|
63
66
|
)
|
|
64
67
|
args[:data] ||= {}
|
|
@@ -81,8 +84,8 @@ module Pulse
|
|
|
81
84
|
def body_classes
|
|
82
85
|
merge_classes(
|
|
83
86
|
DEFAULT_BODY_CLASSES,
|
|
84
|
-
'[&>tr:nth-child(even)]:
|
|
85
|
-
'pulse
|
|
87
|
+
'pulse:[&>tr:nth-child(even)]:bg-neutral-50': striped,
|
|
88
|
+
'pulse:border-b pulse:[&>tr]:border-t pulse:[&>tr:first-child]:border-t-0': !striped # rubocop:disable Layout/LineLength
|
|
86
89
|
)
|
|
87
90
|
end
|
|
88
91
|
|
|
@@ -90,7 +93,7 @@ module Pulse
|
|
|
90
93
|
{ tag: 'tr' }.tap do |args|
|
|
91
94
|
args[:classes] = merge_classes(
|
|
92
95
|
DEFAULT_ROW_CLASSES,
|
|
93
|
-
{ 'hover:
|
|
96
|
+
{ 'pulse:hover:bg-neutral-50': hoverable },
|
|
94
97
|
row_classes
|
|
95
98
|
)
|
|
96
99
|
args[:id] = dom_id(row) if row.respond_to?(:to_key)
|
|
@@ -3,31 +3,33 @@
|
|
|
3
3
|
module Pulse
|
|
4
4
|
# Base Text component
|
|
5
5
|
class Text < Pulse::Component
|
|
6
|
+
status :stable
|
|
7
|
+
|
|
6
8
|
STYLE_MAPPINGS = {
|
|
7
|
-
h1: 'pulse
|
|
8
|
-
h2: 'pulse
|
|
9
|
-
h3: 'pulse
|
|
10
|
-
h4: 'pulse
|
|
11
|
-
subtitle: 'pulse
|
|
12
|
-
'pulse
|
|
13
|
-
body1: 'pulse
|
|
14
|
-
'pulse
|
|
15
|
-
body2: 'pulse
|
|
16
|
-
'pulse
|
|
17
|
-
body3: 'pulse
|
|
18
|
-
'pulse
|
|
19
|
-
body4: 'pulse
|
|
20
|
-
'pulse
|
|
21
|
-
body5: 'pulse
|
|
22
|
-
'pulse
|
|
23
|
-
body6: 'pulse
|
|
24
|
-
'pulse
|
|
25
|
-
body7: 'pulse
|
|
26
|
-
body8: 'pulse
|
|
27
|
-
caption1: 'pulse
|
|
28
|
-
'pulse
|
|
29
|
-
caption2: 'pulse
|
|
30
|
-
'pulse
|
|
9
|
+
h1: 'pulse:text-3xl pulse:font-bold pulse:print:text-2xl',
|
|
10
|
+
h2: 'pulse:text-2xl pulse:font-bold pulse:print:text-xl',
|
|
11
|
+
h3: 'pulse:text-xl pulse:font-bold pulse:print:text-lg',
|
|
12
|
+
h4: 'pulse:text-lg pulse:font-bold pulse:print:text-base',
|
|
13
|
+
subtitle: 'pulse:text-xl pulse:print:text-lg pulse:leading-tight ' \
|
|
14
|
+
'pulse:font-bold pulse:tracking-tight',
|
|
15
|
+
body1: 'pulse:text-lg pulse:print:text-base pulse:font-bold ' \
|
|
16
|
+
'pulse:leading-6 pulse:tracking-tight',
|
|
17
|
+
body2: 'pulse:text-lg pulse:print:text-base pulse:font-normal ' \
|
|
18
|
+
'pulse:leading-6 pulse:tracking-tight',
|
|
19
|
+
body3: 'pulse:text-base pulse:print:text-sm pulse:font-bold ' \
|
|
20
|
+
'pulse:leading-normal',
|
|
21
|
+
body4: 'pulse:text-base pulse:print:text-sm pulse:font-normal ' \
|
|
22
|
+
'pulse:leading-normal',
|
|
23
|
+
body5: 'pulse:text-sm pulse:print:text-xs pulse:font-bold ' \
|
|
24
|
+
'pulse:leading-snug',
|
|
25
|
+
body6: 'pulse:text-sm pulse:print:text-xs pulse:font-normal ' \
|
|
26
|
+
'pulse:leading-snug',
|
|
27
|
+
body7: 'pulse:text-xs pulse:font-bold pulse:leading-5',
|
|
28
|
+
body8: 'pulse:text-xs pulse:font-normal pulse:leading-5',
|
|
29
|
+
caption1: 'pulse:text-2xs pulse:font-bold pulse:tracking-normal ' \
|
|
30
|
+
'pulse:leading-tight',
|
|
31
|
+
caption2: 'pulse:text-2xs pulse:font-normal pulse:tracking-normal ' \
|
|
32
|
+
'pulse:leading-tight'
|
|
31
33
|
}.freeze
|
|
32
34
|
|
|
33
35
|
TAG_MAPPINGS = {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/! components/pulse/toast
|
|
2
2
|
= render Pulse::BaseComponent.new(**@system_arguments) do
|
|
3
|
-
div class="pulse
|
|
4
|
-
div class="pulse
|
|
5
|
-
div class="pulse
|
|
6
|
-
= render Pulse::Text.new(type: :body6, classes: 'pulse
|
|
3
|
+
div class="pulse:flex pulse:w-full" data-theme=(@theme)
|
|
4
|
+
div class="pulse:flex-1 pulse:flex pulse:justify-between pulse:items-start"
|
|
5
|
+
div class="pulse:flex-1 pulse:flex pulse:justify-between pulse:items-center pulse:gap-4"
|
|
6
|
+
= render Pulse::Text.new(type: :body6, classes: 'pulse:mb-0 pulse:min-w-62.5') do
|
|
7
7
|
= content
|
|
8
|
-
div class="pulse
|
|
9
|
-
div class="pulse
|
|
8
|
+
div class="pulse:mt-0"
|
|
9
|
+
div class="pulse:flex pulse:items-center pulse:gap-3.5 pulse:-my-2"
|
|
10
10
|
- if action?
|
|
11
|
-
= render Pulse::Text.new(type: :body5, classes: merge_classes(link_classes, 'pulse
|
|
11
|
+
= render Pulse::Text.new(type: :body5, classes: merge_classes(link_classes, 'pulse:uppercase pulse:mb-0')) do
|
|
12
12
|
= action
|
|
13
|
-
button class="pulse
|
|
13
|
+
button class="pulse:appearance-none pulse:bg-transparent pulse:border-0 pulse:p-2" data-action="click->pulse--toast#dismiss"
|
|
14
14
|
= render Pulse::Icon.new(icon: :close, width: 9, height: 9, class: link_classes)
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module Pulse
|
|
4
4
|
# Renders a toast popup.
|
|
5
5
|
class Toast < Pulse::Component
|
|
6
|
+
status :stable
|
|
7
|
+
|
|
6
8
|
include Turbo::FramesHelper
|
|
7
9
|
|
|
8
10
|
renders_one :action, lambda { |url:, **system_arguments, &block|
|
|
@@ -16,15 +18,15 @@ module Pulse
|
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
LINK_MAPPINGS = {
|
|
19
|
-
default: 'pulse
|
|
20
|
-
error: 'pulse
|
|
21
|
-
success: 'pulse
|
|
21
|
+
default: 'pulse:text-white pulse:hover:text-white',
|
|
22
|
+
error: 'pulse:text-neutral-700 pulse:hover:text-neutral-700',
|
|
23
|
+
success: 'pulse:text-white pulse:hover:text-white'
|
|
22
24
|
}.freeze
|
|
23
25
|
|
|
24
26
|
THEME_MAPPINGS = {
|
|
25
|
-
default: 'pulse
|
|
26
|
-
error: 'pulse
|
|
27
|
-
success: 'pulse
|
|
27
|
+
default: 'pulse:bg-neutral-700 pulse:text-white',
|
|
28
|
+
error: 'pulse:bg-error-100 pulse:text-neutral-900',
|
|
29
|
+
success: 'pulse:bg-neutral-700 pulse:text-white'
|
|
28
30
|
}.freeze
|
|
29
31
|
DEFAULT_THEME = :default
|
|
30
32
|
|
|
@@ -47,14 +49,14 @@ module Pulse
|
|
|
47
49
|
def class_attributes
|
|
48
50
|
merge_classes(
|
|
49
51
|
@system_arguments[:classes],
|
|
50
|
-
# 'pulse
|
|
51
|
-
'pulse
|
|
52
|
-
'pulse
|
|
53
|
-
'pulse
|
|
54
|
-
'pulse
|
|
55
|
-
'pulse
|
|
56
|
-
'pulse
|
|
57
|
-
'pulse
|
|
52
|
+
# 'pulse:hidden',
|
|
53
|
+
'pulse:pl-8',
|
|
54
|
+
'pulse:pr-4',
|
|
55
|
+
'pulse:py-2.5',
|
|
56
|
+
'pulse:mb-4',
|
|
57
|
+
'pulse:rounded',
|
|
58
|
+
'pulse:shadow-md',
|
|
59
|
+
'pulse:z-100',
|
|
58
60
|
THEME_MAPPINGS[@theme]
|
|
59
61
|
)
|
|
60
62
|
end
|
|
@@ -65,15 +67,15 @@ module Pulse
|
|
|
65
67
|
{
|
|
66
68
|
controller: 'pulse--toast',
|
|
67
69
|
pulse__toast_auto_hide_value: @auto_hide,
|
|
68
|
-
transition_enter: 'pulse
|
|
69
|
-
'pulse
|
|
70
|
-
transition_enter_start: 'pulse
|
|
71
|
-
'pulse
|
|
72
|
-
transition_enter_end: 'pulse
|
|
73
|
-
'pulse
|
|
74
|
-
transition_leave: 'pulse
|
|
75
|
-
transition_leave_start: 'pulse
|
|
76
|
-
transition_leave_end: 'pulse
|
|
70
|
+
transition_enter: 'pulse:transition pulse:ease-out ' \
|
|
71
|
+
'pulse:duration-500',
|
|
72
|
+
transition_enter_start: 'pulse:transform pulse:opacity-0 ' \
|
|
73
|
+
'pulse:translate-y-full',
|
|
74
|
+
transition_enter_end: 'pulse:transform pulse:opacity-100 ' \
|
|
75
|
+
'pulse:translate-y-0',
|
|
76
|
+
transition_leave: 'pulse:transition pulse:ease-in pulse:duration-200',
|
|
77
|
+
transition_leave_start: 'pulse:transform pulse:opacity-100',
|
|
78
|
+
transition_leave_end: 'pulse:transform pulse:opacity-0'
|
|
77
79
|
}
|
|
78
80
|
)
|
|
79
81
|
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pulse
|
|
4
|
+
# Declares how complete a component is, following Primer's component
|
|
5
|
+
# lifecycle. Components are `:alpha` until they declare otherwise, so a new
|
|
6
|
+
# component is never presented as safe by accident.
|
|
7
|
+
#
|
|
8
|
+
# - `:alpha` Incomplete. Options or features may be broken, unstyled, or
|
|
9
|
+
# missing, and the API may change without notice.
|
|
10
|
+
# - `:beta` Core usage works. Some documented options are not implemented.
|
|
11
|
+
# - `:stable` Complete and safe to use.
|
|
12
|
+
# - `:deprecated` Scheduled for removal.
|
|
13
|
+
#
|
|
14
|
+
# ```ruby
|
|
15
|
+
# class Pulse::Thing < Pulse::Component
|
|
16
|
+
# status :beta
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# Pulse::Thing.status # => :beta
|
|
20
|
+
# ```
|
|
21
|
+
module Status
|
|
22
|
+
extend ActiveSupport::Concern
|
|
23
|
+
|
|
24
|
+
STATUSES = [:alpha, :beta, :stable, :deprecated].freeze
|
|
25
|
+
DEFAULT_STATUS = :alpha
|
|
26
|
+
|
|
27
|
+
included do
|
|
28
|
+
# Set once while each class body loads, never at request time.
|
|
29
|
+
# rubocop:disable-next ThreadSafety/ClassAndModuleAttributes
|
|
30
|
+
class_attribute :component_status, instance_writer: false,
|
|
31
|
+
default: DEFAULT_STATUS
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class_methods do
|
|
35
|
+
def status(value = nil)
|
|
36
|
+
return component_status if value.nil?
|
|
37
|
+
|
|
38
|
+
unless STATUSES.include?(value)
|
|
39
|
+
raise ArgumentError,
|
|
40
|
+
"Unknown status #{value.inspect}. Use one of " \
|
|
41
|
+
"#{STATUSES.to_sentence}."
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
self.component_status = value
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
div class="pulse:flex pulse:flex-col pulse:gap-6"
|
|
2
|
+
p class="pulse:text-sm pulse:text-neutral-600"
|
|
3
|
+
' Primitive palettes have no utility classes. Reach them through a semantic name.
|
|
4
|
+
- palettes.each do |name, swatches|
|
|
5
|
+
section
|
|
6
|
+
h2 class="pulse:mb-2 pulse:text-lg pulse:font-bold pulse:text-neutral-900"
|
|
7
|
+
code class="pulse:font-mono" = name
|
|
8
|
+
div class="pulse:grid pulse:grid-cols-10 pulse:gap-1"
|
|
9
|
+
- swatches.each do |swatch|
|
|
10
|
+
div class="pulse:flex pulse:flex-col pulse:justify-end pulse:h-20 pulse:p-2 pulse:rounded pulse:text-xs pulse:font-mono" style="background-color: #{swatch.hex}; color: #{swatch.ink}" title="#{name}-#{swatch.shade}"
|
|
11
|
+
span = swatch.shade
|
|
12
|
+
span = swatch.hex
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
div class="pulse:flex pulse:flex-col pulse:gap-6"
|
|
2
|
+
- semantic.each do |name, (palette, default_shade)|
|
|
3
|
+
section
|
|
4
|
+
h2 class="pulse:text-lg pulse:font-bold pulse:text-neutral-900"
|
|
5
|
+
= name
|
|
6
|
+
p class="pulse:mb-2 pulse:text-sm pulse:text-neutral-600"
|
|
7
|
+
' Maps to
|
|
8
|
+
code class="pulse:font-mono" = palette
|
|
9
|
+
' . Bare
|
|
10
|
+
code class="pulse:font-mono" = "pulse:bg-#{name}"
|
|
11
|
+
| is shade #{default_shade}.
|
|
12
|
+
div class="pulse:grid pulse:grid-cols-10 pulse:gap-1"
|
|
13
|
+
- palettes.fetch(palette).each do |swatch|
|
|
14
|
+
div class=class_names('pulse:flex pulse:flex-col pulse:justify-end pulse:h-20 pulse:p-2 pulse:rounded pulse:text-xs pulse:font-mono', 'pulse:ring-2 pulse:ring-offset-2 pulse:ring-neutral-900' => swatch.shade == default_shade) style="background-color: #{swatch.hex}; color: #{swatch.ink}" title="#{name}-#{swatch.shade}"
|
|
15
|
+
span = "#{name}-#{swatch.shade}"
|
|
16
|
+
span = swatch.hex
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Foundations
|
|
4
|
+
# Reads the token stylesheets rather than the built CSS, because Tailwind
|
|
5
|
+
# only emits the theme variables that some class uses.
|
|
6
|
+
#
|
|
7
|
+
# @label Colors
|
|
8
|
+
class ColorsPreview < ViewComponent::Preview
|
|
9
|
+
Swatch = Data.define(:shade, :hex) do
|
|
10
|
+
# Dark text on light swatches, using relative luminance.
|
|
11
|
+
def ink
|
|
12
|
+
r, g, b = hex.delete('#').scan(/../).map { it.to_i(16) / 255.0 }
|
|
13
|
+
luminance = (0.2126 * r) + (0.7152 * g) + (0.0722 * b)
|
|
14
|
+
luminance > 0.55 ? '#111827' : '#ffffff'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
TOKENS_PATH = Pulse::ViewComponents::Engine.root.join(
|
|
19
|
+
'app/assets/stylesheets/pulse_view_components'
|
|
20
|
+
)
|
|
21
|
+
PALETTE_PATTERN = /--palette-([a-z-]+)-(\d+):\s*(#\h{6});/
|
|
22
|
+
SEMANTIC_PATTERN = /--color-([a-z]+):\s*--theme\(--palette-([a-z-]+)-(\d+)/
|
|
23
|
+
|
|
24
|
+
# @label Semantic
|
|
25
|
+
def semantic
|
|
26
|
+
render_with_template(
|
|
27
|
+
locals: { semantic: semantic_tokens, palettes: palette_tokens }
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @label Palettes
|
|
32
|
+
def palettes
|
|
33
|
+
render_with_template(locals: { palettes: palette_tokens })
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
# { 'coral-red' => [Swatch('50', '#ffecec'), ...], ... }
|
|
39
|
+
def palette_tokens
|
|
40
|
+
TOKENS_PATH.join('tokens.primitives.css').read
|
|
41
|
+
.scan(PALETTE_PATTERN)
|
|
42
|
+
.group_by(&:first)
|
|
43
|
+
.transform_values do |rows|
|
|
44
|
+
rows.map { |_, shade, hex| Swatch.new(shade:, hex:) }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# { 'primary' => ['coral-red', '700'], ... }
|
|
49
|
+
def semantic_tokens
|
|
50
|
+
TOKENS_PATH.join('tokens.system.css').read
|
|
51
|
+
.scan(SEMANTIC_PATTERN)
|
|
52
|
+
.to_h { |name, palette, shade| [name, [palette, shade]] }
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|