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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -25
  3. data/app/assets/javascripts/ariadne_view_components.js +2 -2
  4. data/app/assets/javascripts/ariadne_view_components.js.map +1 -1
  5. data/app/assets/javascripts/components/ariadne/accumulator_controller/accumulator_controller.d.ts +22 -0
  6. data/app/assets/javascripts/components/ariadne/dropdown/menu_component.d.ts +1 -0
  7. data/app/assets/javascripts/components/ariadne/options_controller/options_controller.d.ts +21 -0
  8. data/app/assets/javascripts/components/ariadne/toggleable_controller/toggleable_controller.d.ts +34 -0
  9. data/app/assets/stylesheets/ariadne_view_components.css +3 -3
  10. data/app/components/ariadne/accumulator_controller/accumulator_controller.d.ts +22 -0
  11. data/app/components/ariadne/accumulator_controller/accumulator_controller.js +39 -0
  12. data/app/components/ariadne/accumulator_controller/accumulator_controller.ts +48 -0
  13. data/app/components/ariadne/action_card_component.html.erb +11 -0
  14. data/app/components/ariadne/action_card_component.rb +45 -0
  15. data/app/components/ariadne/ariadne.js +6 -0
  16. data/app/components/ariadne/ariadne.ts +6 -0
  17. data/app/components/ariadne/bottom_tab_component.html.erb +4 -0
  18. data/app/components/ariadne/bottom_tab_component.rb +44 -0
  19. data/app/components/ariadne/bottom_tab_nav_component.html.erb +5 -0
  20. data/app/components/ariadne/bottom_tab_nav_component.rb +33 -0
  21. data/app/components/ariadne/breadcrumbs_component.html.erb +13 -0
  22. data/app/components/ariadne/breadcrumbs_component.rb +31 -0
  23. data/app/components/ariadne/checkbox_component.html.erb +5 -0
  24. data/app/components/ariadne/checkbox_component.rb +43 -0
  25. data/app/components/ariadne/close_button_component.html.erb +4 -0
  26. data/app/components/ariadne/close_button_component.rb +32 -0
  27. data/app/components/ariadne/dropdown/menu_component.d.ts +1 -0
  28. data/app/components/ariadne/dropdown/menu_component.js +1 -0
  29. data/app/components/ariadne/options_controller/options_controller.d.ts +21 -0
  30. data/app/components/ariadne/options_controller/options_controller.js +50 -0
  31. data/app/components/ariadne/options_controller/options_controller.ts +57 -0
  32. data/app/components/ariadne/popover_component.html.erb +11 -0
  33. data/app/components/ariadne/popover_component.rb +81 -0
  34. data/app/components/ariadne/progress_bar_component.html.erb +5 -0
  35. data/app/components/ariadne/progress_bar_component.rb +63 -0
  36. data/app/components/ariadne/relative_time_component.html.erb +3 -0
  37. data/app/components/ariadne/relative_time_component.rb +61 -0
  38. data/app/components/ariadne/show_more_button_component.html.erb +11 -0
  39. data/app/components/ariadne/show_more_button_component.rb +47 -0
  40. data/app/components/ariadne/spinner_component.html.erb +16 -0
  41. data/app/components/ariadne/spinner_component.rb +45 -0
  42. data/app/components/ariadne/subheader_component.html.erb +11 -0
  43. data/app/components/ariadne/subheader_component.rb +65 -0
  44. data/app/components/ariadne/toggle_component/toggle_component.html.erb +15 -0
  45. data/app/components/ariadne/toggle_component.rb +95 -0
  46. data/app/components/ariadne/toggleable_controller/toggleable_controller.d.ts +34 -0
  47. data/app/components/ariadne/toggleable_controller/toggleable_controller.js +74 -0
  48. data/app/components/ariadne/toggleable_controller/toggleable_controller.ts +87 -0
  49. data/lib/ariadne/view_components/version.rb +1 -1
  50. data/static/arguments.yml +50 -0
  51. data/static/audited_at.json +14 -0
  52. data/static/classes.yml +209 -173
  53. data/static/constants.json +282 -0
  54. data/static/statuses.json +14 -0
  55. data/tailwind.config.js +7 -7
  56. metadata +53 -12
  57. /data/app/assets/javascripts/{ariadne-form.d.ts → components/ariadne/ariadne-form.d.ts} +0 -0
  58. /data/app/assets/javascripts/{ariadne.d.ts → components/ariadne/ariadne.d.ts} +0 -0
  59. /data/app/assets/javascripts/{clipboard_copy_component → components/ariadne/clipboard_copy_component}/clipboard-copy-component.d.ts +0 -0
  60. /data/app/assets/javascripts/{rich_text_area_component → components/ariadne/rich_text_area_component}/rich-text-area-component.d.ts +0 -0
  61. /data/app/assets/javascripts/{slideover_component → components/ariadne/slideover_component}/slideover-component.d.ts +0 -0
  62. /data/app/assets/javascripts/{tab_container_component → components/ariadne/tab_container_component}/tab-container-component.d.ts +0 -0
  63. /data/app/assets/javascripts/{tab_nav_component → components/ariadne/tab_nav_component}/tab-nav-component.d.ts +0 -0
  64. /data/app/assets/javascripts/{time_ago_component → components/ariadne/time_ago_component}/time-ago-component.d.ts +0 -0
  65. /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,5 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |c| %>
2
+ <% items.each do |item| %>
3
+ <%= item %>
4
+ <% end %>
5
+ <% 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,3 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |component| %>
2
+ <%= @formatted_date %>
3
+ <% 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;