ariadne_view_components 0.0.47-arm64-darwin → 0.0.48-arm64-darwin
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 +27 -25
- data/app/assets/javascripts/ariadne_view_components.js +2 -2
- data/app/assets/javascripts/ariadne_view_components.js.map +1 -1
- data/app/assets/javascripts/components/ariadne/accumulator_controller/accumulator_controller.d.ts +22 -0
- data/app/assets/javascripts/components/ariadne/dropdown/menu_component.d.ts +1 -0
- data/app/assets/javascripts/components/ariadne/options_controller/options_controller.d.ts +21 -0
- data/app/assets/javascripts/components/ariadne/toggleable_controller/toggleable_controller.d.ts +34 -0
- data/app/assets/stylesheets/ariadne_view_components.css +3 -3
- data/app/components/ariadne/accumulator_controller/accumulator_controller.d.ts +22 -0
- data/app/components/ariadne/accumulator_controller/accumulator_controller.js +39 -0
- data/app/components/ariadne/accumulator_controller/accumulator_controller.ts +48 -0
- data/app/components/ariadne/action_card_component.html.erb +11 -0
- data/app/components/ariadne/action_card_component.rb +45 -0
- data/app/components/ariadne/ariadne.js +6 -0
- data/app/components/ariadne/ariadne.ts +6 -0
- data/app/components/ariadne/bottom_tab_component.html.erb +4 -0
- data/app/components/ariadne/bottom_tab_component.rb +44 -0
- data/app/components/ariadne/bottom_tab_nav_component.html.erb +5 -0
- data/app/components/ariadne/bottom_tab_nav_component.rb +33 -0
- data/app/components/ariadne/breadcrumbs_component.html.erb +13 -0
- data/app/components/ariadne/breadcrumbs_component.rb +31 -0
- data/app/components/ariadne/checkbox_component.html.erb +5 -0
- data/app/components/ariadne/checkbox_component.rb +43 -0
- data/app/components/ariadne/close_button_component.html.erb +4 -0
- data/app/components/ariadne/close_button_component.rb +32 -0
- data/app/components/ariadne/dropdown/menu_component.d.ts +1 -0
- data/app/components/ariadne/dropdown/menu_component.js +1 -0
- data/app/components/ariadne/options_controller/options_controller.d.ts +21 -0
- data/app/components/ariadne/options_controller/options_controller.js +50 -0
- data/app/components/ariadne/options_controller/options_controller.ts +57 -0
- data/app/components/ariadne/popover_component.html.erb +11 -0
- data/app/components/ariadne/popover_component.rb +81 -0
- data/app/components/ariadne/progress_bar_component.html.erb +5 -0
- data/app/components/ariadne/progress_bar_component.rb +63 -0
- data/app/components/ariadne/relative_time_component.html.erb +3 -0
- data/app/components/ariadne/relative_time_component.rb +61 -0
- data/app/components/ariadne/show_more_button_component.html.erb +11 -0
- data/app/components/ariadne/show_more_button_component.rb +47 -0
- data/app/components/ariadne/spinner_component.html.erb +16 -0
- data/app/components/ariadne/spinner_component.rb +45 -0
- data/app/components/ariadne/subheader_component.html.erb +11 -0
- data/app/components/ariadne/subheader_component.rb +65 -0
- data/app/components/ariadne/toggle_component/toggle_component.html.erb +15 -0
- data/app/components/ariadne/toggle_component.rb +95 -0
- data/app/components/ariadne/toggleable_controller/toggleable_controller.d.ts +34 -0
- data/app/components/ariadne/toggleable_controller/toggleable_controller.js +74 -0
- data/app/components/ariadne/toggleable_controller/toggleable_controller.ts +87 -0
- data/lib/ariadne/view_components/version.rb +1 -1
- data/static/arguments.yml +50 -0
- data/static/audited_at.json +14 -0
- data/static/classes.yml +209 -173
- data/static/constants.json +282 -0
- data/static/statuses.json +14 -0
- data/tailwind.config.js +7 -7
- metadata +53 -12
- /data/app/assets/javascripts/{ariadne-form.d.ts → components/ariadne/ariadne-form.d.ts} +0 -0
- /data/app/assets/javascripts/{ariadne.d.ts → components/ariadne/ariadne.d.ts} +0 -0
- /data/app/assets/javascripts/{clipboard_copy_component → components/ariadne/clipboard_copy_component}/clipboard-copy-component.d.ts +0 -0
- /data/app/assets/javascripts/{rich_text_area_component → components/ariadne/rich_text_area_component}/rich-text-area-component.d.ts +0 -0
- /data/app/assets/javascripts/{slideover_component → components/ariadne/slideover_component}/slideover-component.d.ts +0 -0
- /data/app/assets/javascripts/{tab_container_component → components/ariadne/tab_container_component}/tab-container-component.d.ts +0 -0
- /data/app/assets/javascripts/{tab_nav_component → components/ariadne/tab_nav_component}/tab-nav-component.d.ts +0 -0
- /data/app/assets/javascripts/{time_ago_component → components/ariadne/time_ago_component}/time-ago-component.d.ts +0 -0
- /data/app/assets/javascripts/{tooltip_component → components/ariadne/tooltip_component}/tooltip-component.d.ts +0 -0
@@ -0,0 +1,11 @@
|
|
1
|
+
<%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |component| %>
|
2
|
+
<div class="<%= @base_classes %>">
|
3
|
+
<%= base %>
|
4
|
+
</div>
|
5
|
+
<div class="hidden ariadne-my-20 ariadne-w-20"></div>
|
6
|
+
<div class="<%= @items_wrapper_classes %>" data-action="click->toggleable#toggle">
|
7
|
+
<% items.each do |item| %>
|
8
|
+
<%= item %>
|
9
|
+
<% end %>
|
10
|
+
</div>
|
11
|
+
<% end %>
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ariadne
|
4
|
+
# Displays content in a box that hovers over other content
|
5
|
+
class PopoverComponent < Ariadne::Component
|
6
|
+
DEFAULT_TAG = :div
|
7
|
+
TAG_OPTIONS = [DEFAULT_TAG].freeze
|
8
|
+
|
9
|
+
DIRECTIONS = [:top, :right, :bottom, :left]
|
10
|
+
|
11
|
+
# Menu: List of selectable items (ie: navigation)
|
12
|
+
# Dialog: Essentiall a modal
|
13
|
+
# Listbox: Something like a dropdown for a <select> element
|
14
|
+
# Tooltip: plain text
|
15
|
+
ROLES = [:menu, :dialog, :listbox, :tooltip]
|
16
|
+
HORIZONTAL_ALIGNMENTS = [:left, :right, :center]
|
17
|
+
|
18
|
+
DEFAULT_CLASSES = {
|
19
|
+
wrapper: "ariadne-group ariadne-relative ariadne-w-fit",
|
20
|
+
items: "ariadne-w-fit ariadne-absolute ariadne-shadow-lg ariadne-z-10 ariadne-p-2 ariadne-bg-white ariadne-rounded-md",
|
21
|
+
base: "ariadne-inline-block",
|
22
|
+
}
|
23
|
+
|
24
|
+
ITEMS_DIRECITON_CLASSES = "group-data-[popover-direction=top]:ariadne-bottom-full group-data-[popover-direction=top]:-ariadne-translate-y-2 group-data-[popover-direction=bottom]:ariadne-top-full group-data-[popover-direction=bottom]:ariadne-translate-y-2"
|
25
|
+
ITEMS_HORIZONTAL_ALIGNMENT_CLASSES = "group-data-[popover-horizontal-alignment=left]:ariadne-left-0 group-data-[popover-horizontal-alignment=right]:ariadne-right-0 group-data-[popover-horizontal-alignment=center]:ariadne-left-1/2 group-data-[popover-horizontal-alignment=center]:-ariadne-translate-x-1/2"
|
26
|
+
ITEMS_VISIBILITY_CLASSES = "group-data-[popover-visible=false]:ariadne-hidden"
|
27
|
+
|
28
|
+
DEFAULT_ATTRIBUTES = {
|
29
|
+
"data-controller": "toggleable",
|
30
|
+
"data-action": "click->toggleable#toggle",
|
31
|
+
"data-toggleable-synced-attrs-value": '["data-popover-visible"]',
|
32
|
+
}
|
33
|
+
|
34
|
+
renders_one :base
|
35
|
+
|
36
|
+
renders_many :items
|
37
|
+
|
38
|
+
# @example Default
|
39
|
+
#
|
40
|
+
# <%= render(Ariadne::PopoverComponent.new) { "Example" } %>
|
41
|
+
#
|
42
|
+
# @param tag [Symbol, String] The rendered tag name.
|
43
|
+
# @param classes [String] <%= link_to_classes_docs %>
|
44
|
+
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
45
|
+
def initialize(
|
46
|
+
tag: DEFAULT_TAG,
|
47
|
+
direction: :bottom,
|
48
|
+
classes: "",
|
49
|
+
role:,
|
50
|
+
attributes: {},
|
51
|
+
horizontal_alignment: :center,
|
52
|
+
initial_visible: false,
|
53
|
+
base_wrapper_classes: "",
|
54
|
+
items_wrapper_classes: ""
|
55
|
+
)
|
56
|
+
@tag = check_incoming_tag(DEFAULT_TAG, tag)
|
57
|
+
@direction = fetch_or_raise(DIRECTIONS, direction)
|
58
|
+
@horizontal_alignment = fetch_or_raise(HORIZONTAL_ALIGNMENTS, horizontal_alignment)
|
59
|
+
@role = fetch_or_raise(ROLES, role)
|
60
|
+
@classes = merge_class_names(DEFAULT_CLASSES[:wrapper], classes)
|
61
|
+
|
62
|
+
@attributes = DEFAULT_ATTRIBUTES
|
63
|
+
.merge({
|
64
|
+
"data-popover-direction": @direction,
|
65
|
+
"data-popover-horizontal-alignment": @horizontal_alignment,
|
66
|
+
"data-popover-visible": initial_visible,
|
67
|
+
"data-toggleable-close-on-outside-click-value": true,
|
68
|
+
})
|
69
|
+
.merge(attributes)
|
70
|
+
|
71
|
+
@base_classes = merge_class_names(DEFAULT_CLASSES[:base], base_wrapper_classes)
|
72
|
+
@items_wrapper_classes = merge_class_names(
|
73
|
+
DEFAULT_CLASSES[:items],
|
74
|
+
ITEMS_DIRECITON_CLASSES,
|
75
|
+
ITEMS_HORIZONTAL_ALIGNMENT_CLASSES,
|
76
|
+
ITEMS_VISIBILITY_CLASSES,
|
77
|
+
items_wrapper_classes,
|
78
|
+
)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ariadne
|
4
|
+
# Bar used to show a progress in an activity, supports single bars or multiple bars within one component
|
5
|
+
# use aria-labelledby and aria-describedby to describe the progress bar's purpose
|
6
|
+
class ProgressBarComponent < Ariadne::Component
|
7
|
+
DEFAULT_TAGS = {
|
8
|
+
wrapper: :div,
|
9
|
+
item: :div,
|
10
|
+
}
|
11
|
+
|
12
|
+
DEFAULT_CLASSES = {
|
13
|
+
wrapper: "ariadne-flex ariadne-gap-1 ariadne-bg-slate-200 ariadne-rounded-md ariadne-overflow-hidden",
|
14
|
+
item: "",
|
15
|
+
}
|
16
|
+
|
17
|
+
DEFAULT_ATTRIBUTES = {
|
18
|
+
wrapper: {
|
19
|
+
role: "progressbar",
|
20
|
+
"data-controller": "accumulator",
|
21
|
+
"aria-valuenow": 0,
|
22
|
+
},
|
23
|
+
item: { "data-accumulator-target": "sum" },
|
24
|
+
}
|
25
|
+
|
26
|
+
BAR_SIZES = {
|
27
|
+
sm: "ariadne-h-2",
|
28
|
+
md: "ariadne-h-3",
|
29
|
+
lg: "ariadne-h-4",
|
30
|
+
}
|
31
|
+
|
32
|
+
renders_many :items, lambda { |tag: DEFAULT_TAGS[:item], classes: "", value: 0, attributes: {}|
|
33
|
+
Ariadne::BaseComponent.new(
|
34
|
+
tag: check_incoming_tag(DEFAULT_TAGS[:item], tag),
|
35
|
+
classes: merge_class_names(DEFAULT_CLASSES[:item], BAR_SIZES[@size], classes),
|
36
|
+
attributes: DEFAULT_ATTRIBUTES[:item].merge({
|
37
|
+
style: "width: #{(value - @min).to_f / (@max - @min) * 100}%",
|
38
|
+
"data-value": value,
|
39
|
+
}).merge(attributes),
|
40
|
+
)
|
41
|
+
}
|
42
|
+
|
43
|
+
# @example Default
|
44
|
+
#
|
45
|
+
# <%= render(Ariadne::ProgressBarComponent.new) { "Example" } %>
|
46
|
+
#
|
47
|
+
# @param tag [Symbol, String] The rendered tag name.
|
48
|
+
# @param classes [String] <%= link_to_classes_docs %>
|
49
|
+
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
50
|
+
def initialize(tag: DEFAULT_TAGS[:wrapper], min: 0, max: 100, classes: "", size: :md, attributes: {})
|
51
|
+
@tag = check_incoming_tag(DEFAULT_TAGS[:wrapper], tag)
|
52
|
+
@classes = merge_class_names(DEFAULT_CLASSES[:wrapper], classes)
|
53
|
+
|
54
|
+
@min = min
|
55
|
+
@max = max
|
56
|
+
@size = size
|
57
|
+
@attributes = DEFAULT_ATTRIBUTES[:wrapper].merge({
|
58
|
+
"aria-valuemin": @min,
|
59
|
+
"aria-valuemax": @max,
|
60
|
+
}).merge(attributes)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ariadne
|
4
|
+
# Converts time into a display-friendly format in a relative time (3d) or absolute date after a default of 30 days
|
5
|
+
class RelativeTimeComponent < Ariadne::Component
|
6
|
+
DEFAULT_TAG = :span
|
7
|
+
TAG_OPTIONS = [DEFAULT_TAG].freeze
|
8
|
+
DEFAULT_DATE_FORMAT = "%b %-d, %Y"
|
9
|
+
DEFAULT_DATE_FORMAT_SWITCH = 30 * 24 * 60 * 60
|
10
|
+
|
11
|
+
DEFAULT_CLASSES = ""
|
12
|
+
|
13
|
+
# @example Default
|
14
|
+
#
|
15
|
+
# <%= render(Ariadne::RelativeTimeComponent.new) { "Example" } %>
|
16
|
+
#
|
17
|
+
# @param tag [Symbol, String] The rendered tag name.
|
18
|
+
# @param classes [String] <%= link_to_classes_docs %>
|
19
|
+
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
20
|
+
def initialize(
|
21
|
+
tag: DEFAULT_TAG,
|
22
|
+
classes: "",
|
23
|
+
time: nil,
|
24
|
+
format: DEFAULT_DATE_FORMAT,
|
25
|
+
date_format_switch: DEFAULT_DATE_FORMAT_SWITCH,
|
26
|
+
force_relative: false,
|
27
|
+
attributes: {}
|
28
|
+
)
|
29
|
+
raise ArgumentError, "A 'time' argument is required to use the Relative Time component." if time.blank?
|
30
|
+
|
31
|
+
@now = 1.second.ago
|
32
|
+
@tag = check_incoming_tag(DEFAULT_TAG, tag)
|
33
|
+
@classes = merge_class_names(DEFAULT_CLASSES, classes)
|
34
|
+
@time = time
|
35
|
+
@date_format_switch = date_format_switch
|
36
|
+
@format = format
|
37
|
+
@force_relative = force_relative
|
38
|
+
@attributes = attributes
|
39
|
+
|
40
|
+
@formatted_date = formatted
|
41
|
+
end
|
42
|
+
|
43
|
+
private def formatted_time
|
44
|
+
@time.localtime.strftime(@format)
|
45
|
+
end
|
46
|
+
|
47
|
+
private def time_difference
|
48
|
+
(@now - @time).to_i.abs
|
49
|
+
end
|
50
|
+
|
51
|
+
private def formatted
|
52
|
+
diff = time_difference
|
53
|
+
|
54
|
+
if @force_relative || diff < @date_format_switch
|
55
|
+
time_ago_in_words(@time)
|
56
|
+
else
|
57
|
+
@time.localtime.strftime(@format)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |component| %>
|
2
|
+
<%= render Ariadne::ButtonComponent.new(classes: @button_classes, attributes: @button_attributes) do |button| %>
|
3
|
+
<%= @button_text %>
|
4
|
+
<% end %>
|
5
|
+
<div
|
6
|
+
class="ariadne-max-h-full aria-hidden:ariadne-max-h-0 ariadne-overflow-hidden ariadne-my-2" data-controller="toggleable" aria-hidden="true" data-toggleable-anti-attrs-value='["aria-hidden"]' data-toggleable-outlet="<%= @outlet %>">
|
7
|
+
<% hiddens.each do |hidden| %>
|
8
|
+
<%= hidden %>
|
9
|
+
<% end %>
|
10
|
+
</div>
|
11
|
+
<% end %>
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ariadne
|
4
|
+
# Add a general description of component here
|
5
|
+
# Add additional usage considerations or best practices that may aid the user to use the component correctly.
|
6
|
+
# @accessibility Add any accessibility considerations
|
7
|
+
class ShowMoreButtonComponent < Ariadne::Component
|
8
|
+
DEFAULT_TAG = :div
|
9
|
+
TAG_OPTIONS = [DEFAULT_TAG].freeze
|
10
|
+
|
11
|
+
DEFAULT_CLASSES = { wrapper: "ariadne-group", button: "ariadne-px-2 ariadne-py-0" }
|
12
|
+
|
13
|
+
DEFAULT_ATTRIBUTES = {
|
14
|
+
wrapper: {
|
15
|
+
"data-controller": "toggleable",
|
16
|
+
},
|
17
|
+
button: {
|
18
|
+
"data-action": "click->toggleable#toggle",
|
19
|
+
},
|
20
|
+
}
|
21
|
+
|
22
|
+
renders_many :hiddens
|
23
|
+
|
24
|
+
# @example Default
|
25
|
+
#
|
26
|
+
# <%= render(Ariadne::ShowMoreButtonComponent.new) { "Example" } %>
|
27
|
+
#
|
28
|
+
# @param tag [Symbol, String] The rendered tag name.
|
29
|
+
# @param classes [String] <%= link_to_classes_docs %>
|
30
|
+
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
31
|
+
def initialize(tag: DEFAULT_TAG, classes: "", button_text: "...", outlet: nil, attributes: {})
|
32
|
+
raise ArgumentError, "An 'aria-label' attribute is required to use the Show More Button. Include as much detail as you can about the content to be shown to assist screen readers." if attributes.blank? || attributes["aria-label"].blank?
|
33
|
+
raise ArgumentError, "An 'outlet' attribute is required to use the Show More Button. This will ensure controllers can correctly identify states to update." if outlet.blank?
|
34
|
+
|
35
|
+
@outlet = outlet
|
36
|
+
@tag = check_incoming_tag(DEFAULT_TAG, tag)
|
37
|
+
@classes = merge_class_names(DEFAULT_CLASSES[:wrapper], classes)
|
38
|
+
@attributes = DEFAULT_ATTRIBUTES[:wrapper]
|
39
|
+
.merge({ "data-toggleable-toggleable-outlet": "[data-toggleable-outlet=#{@outlet}]" })
|
40
|
+
.merge(attributes)
|
41
|
+
|
42
|
+
@button_classes = merge_class_names(DEFAULT_CLASSES[:button], classes)
|
43
|
+
@button_attributes = DEFAULT_ATTRIBUTES[:button].merge(attributes)
|
44
|
+
@button_text = button_text
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<%= render Ariadne::BaseComponent.new(tag: :svg, classes: @classes, attributes: @attributes) do |svg| %>
|
2
|
+
<circle
|
3
|
+
cx="8"
|
4
|
+
cy="8"
|
5
|
+
r="7"
|
6
|
+
stroke="currentColor"
|
7
|
+
stroke-opacity="0.25"
|
8
|
+
stroke-width="2"
|
9
|
+
vector-effect="non-scaling-stroke" />
|
10
|
+
<path
|
11
|
+
d="M15 8a7.002 7.002 0 00-7-7"
|
12
|
+
stroke="currentColor"
|
13
|
+
stroke-width="2"
|
14
|
+
stroke-linecap="round"
|
15
|
+
vector-effect="non-scaling-stroke" />
|
16
|
+
<% end %>
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ariadne
|
4
|
+
# Renders a spinning wheel to indicate a loading state
|
5
|
+
# Add color using a tailwind text color class
|
6
|
+
class SpinnerComponent < Ariadne::Component
|
7
|
+
DEFAULT_SIZE = :md
|
8
|
+
DEFAULT_CLASSES = "ariadne-animate-spin ariadne-fill-none"
|
9
|
+
DEFAULT_ATTRIBUTES = {
|
10
|
+
fill: "none",
|
11
|
+
viewBox: "0 0 16 16",
|
12
|
+
role: "progressbar",
|
13
|
+
"aria-valuetext": "Loading...",
|
14
|
+
"aria-busy": true,
|
15
|
+
"aria-valuemin": "0",
|
16
|
+
"aria-valuemax": "100",
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
SIZE_CLASS_MAPPINGS = {
|
20
|
+
xs: "ariadne-h-4 ariadne-w-4",
|
21
|
+
sm: "ariadne-h-6 ariadne-w-6",
|
22
|
+
md: "ariadne-h-8 ariadne-w-8",
|
23
|
+
lg: "ariadne-h-10 ariadne-w-10",
|
24
|
+
xl: "ariadne-h-12 ariadne-w-12",
|
25
|
+
}.freeze
|
26
|
+
|
27
|
+
VALID_SIZES = SIZE_CLASS_MAPPINGS.keys.freeze
|
28
|
+
|
29
|
+
# @example Default
|
30
|
+
#
|
31
|
+
# <%= render(Ariadne::SpinnerComponent.new) { } %>
|
32
|
+
#
|
33
|
+
# @param classes [String] <%= link_to_classes_docs %>
|
34
|
+
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
35
|
+
def initialize(classes: "", attributes: {}, size: DEFAULT_SIZE)
|
36
|
+
@size = fetch_or_raise(VALID_SIZES, size)
|
37
|
+
@attributes = DEFAULT_ATTRIBUTES.merge(attributes)
|
38
|
+
@classes = merge_class_names(
|
39
|
+
DEFAULT_CLASSES,
|
40
|
+
SIZE_CLASS_MAPPINGS[size],
|
41
|
+
classes,
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |component| %>
|
2
|
+
<div class="ariadne-flex ariadne-justify-between">
|
3
|
+
<%= header %>
|
4
|
+
<div>
|
5
|
+
<% actions.each do |action| %>
|
6
|
+
<%= action %>
|
7
|
+
<% end %>
|
8
|
+
</div>
|
9
|
+
</div>
|
10
|
+
<%= description %>
|
11
|
+
<% end %>
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ariadne
|
4
|
+
# A component to render a subheader with optional description below it and optional action
|
5
|
+
# components on the right side
|
6
|
+
class SubheaderComponent < Ariadne::Component
|
7
|
+
DEFAULT_TAGS = {
|
8
|
+
wrapper: :div,
|
9
|
+
actions: :div,
|
10
|
+
header: :h2,
|
11
|
+
description: :span,
|
12
|
+
}
|
13
|
+
|
14
|
+
DEFAULT_CLASSES = {
|
15
|
+
wrapper: "ariadne-border-b-2 ariadne-border-solid ariadne-border-black ariadne-border-opacity-20",
|
16
|
+
header: "",
|
17
|
+
actions: "",
|
18
|
+
description: "ariadne-text-black/50",
|
19
|
+
}
|
20
|
+
|
21
|
+
DEFAULT_ATTRIBUTES = {
|
22
|
+
wrapper: {},
|
23
|
+
header: {},
|
24
|
+
description: {},
|
25
|
+
actions: {},
|
26
|
+
}
|
27
|
+
|
28
|
+
renders_one :header, lambda { |tag: DEFAULT_TAGS[:header], classes: "", attributes: {}|
|
29
|
+
Ariadne::HeadingComponent.new(
|
30
|
+
tag: check_incoming_tag(DEFAULT_TAGS[:header], tag),
|
31
|
+
classes: merge_class_names(DEFAULT_CLASSES[:header], classes),
|
32
|
+
attributes: DEFAULT_ATTRIBUTES[:header].merge({ id: @header_id }).merge(attributes),
|
33
|
+
)
|
34
|
+
}
|
35
|
+
|
36
|
+
renders_one :description, lambda { |tag: DEFAULT_TAGS[:description], classes: "", attributes: {}|
|
37
|
+
Ariadne::BaseComponent.new(
|
38
|
+
tag: check_incoming_tag(DEFAULT_TAGS[:description], tag),
|
39
|
+
classes: merge_class_names(DEFAULT_CLASSES[:description], classes),
|
40
|
+
attributes: DEFAULT_ATTRIBUTES[:description]
|
41
|
+
.merge({ "aria-describedby": @header_id })
|
42
|
+
.merge(attributes),
|
43
|
+
)
|
44
|
+
}
|
45
|
+
|
46
|
+
renders_many :actions
|
47
|
+
|
48
|
+
# @example Default
|
49
|
+
#
|
50
|
+
# <%= render(Ariadne::SubheaderComponent.new) { "Example" } %>
|
51
|
+
#
|
52
|
+
# @param tag [Symbol, String] The rendered tag name.
|
53
|
+
# @param classes [String] <%= link_to_classes_docs %>
|
54
|
+
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
55
|
+
def initialize(tag: DEFAULT_TAGS[:wrapper], classes: "", id: "subheader-#{SecureRandom.uuid}", attributes: {})
|
56
|
+
@tag = check_incoming_tag(DEFAULT_TAGS[:wrapper], tag)
|
57
|
+
@header_id = id
|
58
|
+
@attributes = DEFAULT_ATTRIBUTES[:wrapper].merge(attributes)
|
59
|
+
@classes = merge_class_names(
|
60
|
+
DEFAULT_CLASSES[:wrapper],
|
61
|
+
classes,
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |wrapper| %>
|
2
|
+
<label class="ariadne-flex ariadne-w-fit ariadne-items-center <%= @label_classes %>">
|
3
|
+
<% if content? %>
|
4
|
+
<span class="group-data-[label-position=start]/wrapper:ariadne-mr-1.5 group-data-[label-position=end]/wrapper:ariadne-ml-1"><%= content %></span>
|
5
|
+
<% end %>
|
6
|
+
<%= render Ariadne::BaseComponent.new(tag: :button, classes: @button_classes, attributes: @button_attributes) do |button| %>
|
7
|
+
<span class="ariadne-sr-only ariadne-translate-x-3">Use setting</span>
|
8
|
+
<span aria-hidden="true" class="ariadne-pointer-events-none ariadne-absolute ariadne-h-full ariadne-w-full ariadne-rounded-md ariadne-bg-white"></span>
|
9
|
+
<span aria-hidden="true"
|
10
|
+
class="ariadne-bg-gray-200 ariadne-pointer-events-none ariadne-absolute ariadne-mx-auto ariadne-rounded-full ariadne-transition-colors ariadne-duration-200 ariadne-ease-in-out <%= @track_classes %>"></span>
|
11
|
+
<span aria-hidden="true"
|
12
|
+
class="ariadne-pointer-events-none ariadne-absolute ariadne-left-0 ariadne-inline-block ariadne-transform ariadne-rounded-full ariadne-border ariadne-border-gray-200 ariadne-bg-white ariadne-shadow ariadne-ring-0 ariadne-transition-transform ariadne-duration-200 ariadne-ease-in-out <%= @thumb_classes %>"></span>
|
13
|
+
<% end %>
|
14
|
+
</label>
|
15
|
+
<% end %>
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ariadne
|
4
|
+
# Simple toggle switch with on/off state. The logic is controlled by toggleable_controller and styles applied through
|
5
|
+
# tailwind's group+data attributes: https://tailwindcss.com/docs/hover-focus-and-other-states#data-attributes
|
6
|
+
class ToggleComponent < Ariadne::Component
|
7
|
+
DEFAULT_TAG = :div
|
8
|
+
|
9
|
+
DEFAULT_STATUS_LABEL_POSITION = :start
|
10
|
+
STATUS_LABEL_POSITION_MAPPINGS = {
|
11
|
+
start: "ariadne-flex-row",
|
12
|
+
end: "ariadne-flex-row-reverse",
|
13
|
+
}.freeze
|
14
|
+
STATUS_LABEL_POSITION_OPTIONS = STATUS_LABEL_POSITION_MAPPINGS.keys.freeze
|
15
|
+
|
16
|
+
DEFAULT_SIZE = :md
|
17
|
+
THUMB_SIZE_MAPPINGS = { sm: "ariadne-translate-x-0 ariadne-h-3 ariadne-w-3 group-aria-checked/toggle:ariadne-translate-x-3", md: "ariadne-translate-x-0 ariadne-h-5 ariadne-w-5 group-aria-checked/toggle:ariadne-translate-x-5" }.freeze
|
18
|
+
TRACK_SIZE_MAPPINGS = { sm: "ariadne-h-2 ariadne-w-4", md: "ariadne-h-4 ariadne-w-9" }.freeze
|
19
|
+
BUTTON_SIZE_MAPPINGS = { sm: "ariadne-h-3 ariadne-w-6", md: "ariadne-h-5 ariadne-w-10" }.freeze
|
20
|
+
LABEL_SIZE_MAPPINGS = { sm: "ariadne-text-xs", md: "ariadne-text-lg" }.freeze
|
21
|
+
SIZE_OPTIONS = THUMB_SIZE_MAPPINGS.keys.freeze
|
22
|
+
|
23
|
+
DEFAULT_CLASSES = {
|
24
|
+
wrapper: "ariadne-group/wrapper data-[disabled=true]:ariadne-opacity-50 data-[disabled=true]:ariadne-cursor-auto",
|
25
|
+
button: "ariadne-flex ariadne-justify-center ariadne-group/toggle ariadne-relative ariadne-inline-flex ariadne-flex-shrink-0 group-data-[disabled=false]/wrapper:ariadne-cursor-pointer ariadne-items-center ariadne-justify-center ariadne-rounded-full ariadne-focus:outline-none ariadne-focus:ring-2 ariadne-focus:ring-indigo-600 ariadne-focus:ring-offset-2 disabled:ariadne-cursor-auto",
|
26
|
+
}
|
27
|
+
|
28
|
+
DEFAULT_ATTRIBUTES = {
|
29
|
+
wrapper: {}.freeze,
|
30
|
+
button: {
|
31
|
+
role: "switch",
|
32
|
+
"data-controller": "toggleable",
|
33
|
+
"data-toggleable-synced-attrs-value": '["aria-checked"]',
|
34
|
+
"data-action": "click->toggleable#toggle",
|
35
|
+
},
|
36
|
+
}.freeze
|
37
|
+
|
38
|
+
DEFAULT_TRACK_COLOR = :indigo
|
39
|
+
TRACK_COLOR_MAPPINGS = {
|
40
|
+
white: "group-aria-checked/toggle:ariadne-bg-white",
|
41
|
+
green: "group-aria-checked/toggle:ariadne-bg-green-600",
|
42
|
+
blue: "group-aria-checked/toggle:ariadne-bg-blue-600",
|
43
|
+
billy_purple: "group-aria-checked/toggle:ariadne-bg-billy-purple",
|
44
|
+
indigo: "group-aria-checked/toggle:ariadne-bg-indigo-600",
|
45
|
+
}
|
46
|
+
TRACK_COLOR_OPTIONS = TRACK_COLOR_MAPPINGS.keys.freeze
|
47
|
+
|
48
|
+
# @example Default
|
49
|
+
#
|
50
|
+
# <%= render(Ariadne::ToggleComponent.new) { "Example" } %>
|
51
|
+
#
|
52
|
+
# @param classes [String] <%= link_to_classes_docs %>
|
53
|
+
# @param color [String] <%= link_to_classes_docs %>
|
54
|
+
# @param checked [Boolean] <%= link_to_classes_docs %>
|
55
|
+
# @param disabled [Boolean] <%= link_to_classes_docs %>
|
56
|
+
# @param size [String] <%= link_to_classes_docs %>
|
57
|
+
# @param label [String] <%= link_to_classes_docs %>
|
58
|
+
# @param status_label_position [String] <%= link_to_classes_docs %>
|
59
|
+
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
60
|
+
def initialize(
|
61
|
+
classes: nil,
|
62
|
+
checked: false,
|
63
|
+
disabled: false,
|
64
|
+
size: DEFAULT_SIZE,
|
65
|
+
color: DEFAULT_TRACK_COLOR,
|
66
|
+
status_label_position: DEFAULT_STATUS_LABEL_POSITION,
|
67
|
+
attributes: {}
|
68
|
+
)
|
69
|
+
@tag = DEFAULT_TAG
|
70
|
+
@size = fetch_or_raise(SIZE_OPTIONS, size)
|
71
|
+
@status_label_position = fetch_or_raise(STATUS_LABEL_POSITION_OPTIONS, status_label_position)
|
72
|
+
@color = fetch_or_raise(TRACK_COLOR_OPTIONS, color)
|
73
|
+
@classes = merge_class_names(DEFAULT_CLASSES[:wrapper], classes)
|
74
|
+
@checked = checked
|
75
|
+
@disabled = disabled
|
76
|
+
@attributes = DEFAULT_ATTRIBUTES[:wrapper].merge({ "data-label-position": @status_label_position, "data-disabled": @disabled }).merge(attributes)
|
77
|
+
|
78
|
+
@label_classes = merge_class_names(STATUS_LABEL_POSITION_MAPPINGS.fetch(@status_label_position), LABEL_SIZE_MAPPINGS.fetch(@size))
|
79
|
+
|
80
|
+
@button_classes = merge_class_names(DEFAULT_CLASSES[:button], BUTTON_SIZE_MAPPINGS.fetch(@size))
|
81
|
+
@button_attributes = DEFAULT_ATTRIBUTES[:button].merge({ "aria-checked": @checked, "data-toggleable-state-value": @checked, disabled: @disabled })
|
82
|
+
|
83
|
+
@thumb_classes = THUMB_SIZE_MAPPINGS.fetch(@size)
|
84
|
+
@track_classes = merge_class_names(TRACK_SIZE_MAPPINGS.fetch(@size), TRACK_COLOR_MAPPINGS.fetch(@color))
|
85
|
+
end
|
86
|
+
|
87
|
+
def on?
|
88
|
+
@checked
|
89
|
+
end
|
90
|
+
|
91
|
+
def disabled?
|
92
|
+
@disabled
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
2
|
+
export interface ToggleableOutlet {
|
3
|
+
toggle: (event: Event, value?: boolean) => void;
|
4
|
+
}
|
5
|
+
export default class ToggleableController extends Controller implements ToggleableOutlet {
|
6
|
+
#private;
|
7
|
+
static outlets: string[];
|
8
|
+
static values: {
|
9
|
+
state: {
|
10
|
+
type: BooleanConstructor;
|
11
|
+
default: boolean;
|
12
|
+
};
|
13
|
+
syncedAttrs: ArrayConstructor;
|
14
|
+
antiAttrs: ArrayConstructor;
|
15
|
+
closeOnOutsideClick: {
|
16
|
+
type: BooleanConstructor;
|
17
|
+
default: boolean;
|
18
|
+
};
|
19
|
+
};
|
20
|
+
static removeOnFalseAttrs: {
|
21
|
+
[k: string]: boolean;
|
22
|
+
};
|
23
|
+
stateValue: boolean;
|
24
|
+
readonly toggleableOutlets: Array<ToggleableOutlet>;
|
25
|
+
readonly hasToggleableOutlet: boolean;
|
26
|
+
readonly syncedAttrsValue: string[];
|
27
|
+
readonly hasSyncedAttrsValue: boolean;
|
28
|
+
readonly antiAttrsValue: string[];
|
29
|
+
readonly hasAntiAttrsValue: boolean;
|
30
|
+
readonly closeOnOutsideClickValue: boolean;
|
31
|
+
connect(): void;
|
32
|
+
toggle(event: Event, value?: boolean): void;
|
33
|
+
clickOutside(event: Event): void;
|
34
|
+
}
|
@@ -0,0 +1,74 @@
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
3
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
5
|
+
};
|
6
|
+
var _ToggleableController_instances, _ToggleableController_updateAllAttrs, _ToggleableController_setAttrs, _ToggleableController_updateOutlets;
|
7
|
+
import { Controller } from '@hotwired/stimulus';
|
8
|
+
import { useClickOutside } from 'stimulus-use';
|
9
|
+
class ToggleableController extends Controller {
|
10
|
+
constructor() {
|
11
|
+
super(...arguments);
|
12
|
+
_ToggleableController_instances.add(this);
|
13
|
+
}
|
14
|
+
connect() {
|
15
|
+
__classPrivateFieldGet(this, _ToggleableController_instances, "m", _ToggleableController_updateAllAttrs).call(this);
|
16
|
+
__classPrivateFieldGet(this, _ToggleableController_instances, "m", _ToggleableController_updateOutlets).call(this, new Event('Init'));
|
17
|
+
useClickOutside(this, { dispatchEvent: this.closeOnOutsideClickValue });
|
18
|
+
}
|
19
|
+
toggle(event, value) {
|
20
|
+
this.stateValue = value !== null && value !== void 0 ? value : !this.stateValue;
|
21
|
+
__classPrivateFieldGet(this, _ToggleableController_instances, "m", _ToggleableController_updateAllAttrs).call(this);
|
22
|
+
__classPrivateFieldGet(this, _ToggleableController_instances, "m", _ToggleableController_updateOutlets).call(this, event);
|
23
|
+
}
|
24
|
+
clickOutside(event) {
|
25
|
+
if (this.closeOnOutsideClickValue) {
|
26
|
+
this.toggle(event, false);
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
_ToggleableController_instances = new WeakSet(), _ToggleableController_updateAllAttrs = function _ToggleableController_updateAllAttrs() {
|
31
|
+
if (this.hasSyncedAttrsValue) {
|
32
|
+
__classPrivateFieldGet(this, _ToggleableController_instances, "m", _ToggleableController_setAttrs).call(this, this.syncedAttrsValue);
|
33
|
+
}
|
34
|
+
if (this.hasAntiAttrsValue) {
|
35
|
+
__classPrivateFieldGet(this, _ToggleableController_instances, "m", _ToggleableController_setAttrs).call(this, this.antiAttrsValue, false);
|
36
|
+
}
|
37
|
+
}, _ToggleableController_setAttrs = function _ToggleableController_setAttrs(attrs, matchState = true) {
|
38
|
+
const attrState = String(matchState ? this.stateValue : !this.stateValue);
|
39
|
+
for (let index in attrs) {
|
40
|
+
const attr = attrs[index];
|
41
|
+
if (attrState === 'false' && ToggleableController.removeOnFalseAttrs[attr]) {
|
42
|
+
this.element.removeAttribute(attr);
|
43
|
+
}
|
44
|
+
else {
|
45
|
+
this.element.setAttribute(attr, attrState);
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}, _ToggleableController_updateOutlets = function _ToggleableController_updateOutlets(event) {
|
49
|
+
if (this.hasToggleableOutlet) {
|
50
|
+
for (let index in this.toggleableOutlets) {
|
51
|
+
const outlet = this.toggleableOutlets[index];
|
52
|
+
outlet.toggle(event, this.stateValue);
|
53
|
+
}
|
54
|
+
}
|
55
|
+
};
|
56
|
+
ToggleableController.outlets = ['toggleable'];
|
57
|
+
ToggleableController.values = {
|
58
|
+
state: {
|
59
|
+
type: Boolean,
|
60
|
+
default: false,
|
61
|
+
},
|
62
|
+
syncedAttrs: Array,
|
63
|
+
antiAttrs: Array,
|
64
|
+
closeOnOutsideClick: {
|
65
|
+
type: Boolean,
|
66
|
+
default: false,
|
67
|
+
},
|
68
|
+
};
|
69
|
+
// Some attributes are only false in HTML if they don't exist
|
70
|
+
// If included here, the property will be deleted on "false"
|
71
|
+
ToggleableController.removeOnFalseAttrs = {
|
72
|
+
checked: true,
|
73
|
+
};
|
74
|
+
export default ToggleableController;
|