anchor_view_components 0.43.0 → 0.45.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/app/assets/images/icons/percentage.svg +6 -0
- data/app/components/anchor/anchor_view_components.ts +2 -0
- data/app/components/anchor/assistive_tech_notifications_component.html.erb +23 -0
- data/app/components/anchor/assistive_tech_notifications_component.rb +4 -0
- data/app/components/anchor/assistive_tech_notifications_component.ts +15 -0
- data/app/components/anchor/autocomplete/results_component.html.erb +2 -3
- data/app/components/anchor/autocomplete/results_component.rb +2 -2
- data/app/components/anchor/autocomplete_component.html.erb +5 -5
- data/app/components/anchor/banner_component.rb +4 -6
- data/app/components/anchor/button_component.rb +0 -7
- data/app/components/anchor/component.rb +1 -1
- data/app/components/anchor/copy_to_clipboard_component.en.yml +1 -0
- data/app/components/anchor/copy_to_clipboard_component.html.erb +2 -2
- data/app/components/anchor/copy_to_clipboard_controller.ts +10 -8
- data/app/components/anchor/dialog_component.html.erb +1 -0
- data/app/components/anchor/dialog_controller.ts +8 -0
- data/app/components/anchor/error_message_component.rb +5 -7
- data/app/components/anchor/input_component.rb +1 -5
- data/app/components/anchor/label_component.rb +3 -7
- data/app/components/anchor/link_component.rb +1 -1
- data/app/components/anchor/page/footer_component.html.erb +2 -1
- data/app/components/anchor/page/footer_component.rb +8 -0
- data/app/components/anchor/radio_button_collection_component.rb +1 -34
- data/app/components/anchor/radio_button_component.html.erb +1 -1
- data/app/components/anchor/radio_button_component.rb +3 -7
- data/app/components/anchor/select_component.rb +1 -5
- data/app/components/anchor/toast_component.html.erb +5 -1
- data/app/components/anchor/toast_component.rb +4 -0
- data/app/components/anchor/toast_controller.ts +5 -0
- data/app/helpers/anchor/form_builder.rb +11 -25
- data/app/helpers/anchor/model_validators.rb +10 -5
- data/app/helpers/anchor/view_helper.rb +1 -0
- data/lib/anchor/view_components/version.rb +1 -1
- data/lib/cops/anchor/avoid_implicit_super.rb +23 -0
- data/previews/anchor/assistive_tech_notifications_component_preview/default.html.erb +10 -0
- data/previews/anchor/assistive_tech_notifications_component_preview.rb +5 -0
- data/previews/anchor/dialog_component_preview/with_form.html.erb +21 -0
- data/previews/anchor/dialog_component_preview.rb +2 -0
- data/previews/anchor/page_component_preview/with_supporting_text_and_buttons.html.erb +24 -0
- data/previews/anchor/page_component_preview.rb +2 -0
- data/previews/forms/with_icons.html.erb +10 -19
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 02e14b9cbe3088a320100d337b3f23f92c97e03f659bd16d42ac21655838cc07
|
4
|
+
data.tar.gz: 4323338bfdedf4ed58101ee366bd21726bc1f3d910d7c824a3bc8eae5ff93d59
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c89ce43a18e3072e0a24d30ac43a4f489cfeea27c318dff78d4f02769c8c2418f1d179b2a71158eb44bc680cf1bd4b2f7d7cde0fe93948b2e2a55c523b7b645
|
7
|
+
data.tar.gz: 35f880936914bd255eec0d3cc77f95352aef81b0dbe60feaada58877100e3c2b45468cd61fe6c0b471bf9836f0055aaff74fa16803cfccf7bfd917fab36542b3
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,23 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.45.0
|
4
|
+
|
5
|
+
### Minor Changes
|
6
|
+
|
7
|
+
- 2cf21ed: Add more-horiz icon
|
8
|
+
- 2d2c3e6: Added a AssistiveTechNotifications component for notifying assistive tech users of changes that might otherwise only be presented visually. The output is two [ARIA live regions][aria-live-regions]—one `polite`, the other `assertive`—and is meant to be placed in an application’s layout so that it’s rendered on every page. Also provided, is the component’s public JavaScript function, `notifyAssistiveTech`, so that applications can easily send messages to these two live regions.
|
9
|
+
|
10
|
+
[aria-live-regions]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions
|
11
|
+
|
12
|
+
## 0.44.0
|
13
|
+
|
14
|
+
### Minor Changes
|
15
|
+
|
16
|
+
- a9e927c: Removed the deprecated `tag` parameter from the Button component.
|
17
|
+
- 3f6ff7b: When a dialog is closed, any inner form error messages will be removed.
|
18
|
+
- e2941f0: Added a `supporting_text` slot to the `Page::Footer` component, which renders
|
19
|
+
text next to the buttons.
|
20
|
+
|
3
21
|
## 0.43.0
|
4
22
|
|
5
23
|
### Minor Changes
|
@@ -0,0 +1,6 @@
|
|
1
|
+
<!-- Source: Iconoir, added via `bin/add_icon` -->
|
2
|
+
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
3
|
+
<path d="M17 19C15.8954 19 15 18.1046 15 17C15 15.8954 15.8954 15 17 15C18.1046 15 19 15.8954 19 17C19 18.1046 18.1046 19 17 19Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
|
4
|
+
<path d="M7 9C5.89543 9 5 8.10457 5 7C5 5.89543 5.89543 5 7 5C8.10457 5 9 5.89543 9 7C9 8.10457 8.10457 9 7 9Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
|
5
|
+
<path d="M19 5L5 19" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
|
6
|
+
</svg>
|
@@ -10,6 +10,7 @@ import ToastController from "./toast_controller";
|
|
10
10
|
import ToggleController from "./toggle_controller";
|
11
11
|
import TypeaheadSelectController from "./typeahead_select_controller";
|
12
12
|
import { Application } from "@hotwired/stimulus";
|
13
|
+
import { notifyAssistiveTech } from "./assistive_tech_notifications_component";
|
13
14
|
|
14
15
|
export function registerAnchorControllers(application: Application) {
|
15
16
|
application.register("autocomplete", AutocompleteController);
|
@@ -31,4 +32,5 @@ export {
|
|
31
32
|
ToastController,
|
32
33
|
ToggleController,
|
33
34
|
TypeaheadSelectController,
|
35
|
+
notifyAssistiveTech,
|
34
36
|
};
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<%= tag.div(
|
2
|
+
**merge_options(
|
3
|
+
wrapper_options,
|
4
|
+
aria: {
|
5
|
+
atomic: true,
|
6
|
+
live: "polite",
|
7
|
+
},
|
8
|
+
class: "sr-only",
|
9
|
+
id: "js-assistive-tech-notifications",
|
10
|
+
),
|
11
|
+
) %>
|
12
|
+
|
13
|
+
<%= tag.div(
|
14
|
+
**merge_options(
|
15
|
+
wrapper_options,
|
16
|
+
aria: {
|
17
|
+
atomic: true,
|
18
|
+
live: "assertive",
|
19
|
+
},
|
20
|
+
class: "sr-only",
|
21
|
+
id: "js-assistive-tech-notifications-assertive",
|
22
|
+
),
|
23
|
+
) %>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
export function notifyAssistiveTech(message: string, assertive = false) {
|
2
|
+
const liveRegionId = assertive
|
3
|
+
? "js-assistive-tech-notifications-assertive"
|
4
|
+
: "js-assistive-tech-notifications";
|
5
|
+
const liveRegion = document.getElementById(liveRegionId);
|
6
|
+
|
7
|
+
if (liveRegion) {
|
8
|
+
liveRegion.textContent = message;
|
9
|
+
} else {
|
10
|
+
console.warn(
|
11
|
+
`Could not find #${liveRegionId}.`,
|
12
|
+
"Make sure to include `anchor_assistive_tech_notifications` in your Rails layout.",
|
13
|
+
);
|
14
|
+
}
|
15
|
+
}
|
@@ -1,9 +1,8 @@
|
|
1
1
|
<% results.each do |result| %>
|
2
2
|
<%= tag.li(
|
3
|
-
**merge_options(
|
4
|
-
{ data: result.data },
|
3
|
+
**merge_options(wrapper_options,
|
5
4
|
class: "list-group-item",
|
6
|
-
data:
|
5
|
+
data: result.data.merge(autocomplete_value: result.value),
|
7
6
|
role: "option",
|
8
7
|
)
|
9
8
|
) do %>
|
@@ -1,25 +1,25 @@
|
|
1
|
-
<%= tag.div(
|
1
|
+
<%= tag.div(**merge_options(wrapper_options,
|
2
2
|
data: { controller: "autocomplete", autocomplete_url_value: src },
|
3
3
|
role: "combobox",
|
4
4
|
class: "group relative"
|
5
|
-
) do %>
|
5
|
+
)) do %>
|
6
6
|
<% if form_builder.class.module_parent_name == "Anchor" %>
|
7
7
|
<%= form_builder.text_field(
|
8
8
|
name,
|
9
9
|
**merge_options(input_options, default_input_options)
|
10
10
|
) %>
|
11
11
|
<%= form_builder.text_field(
|
12
|
-
"#{name}_id"
|
12
|
+
:"#{name}_id",
|
13
13
|
{ class: "hidden", data: { autocomplete_target: "hidden" }}
|
14
14
|
) %>
|
15
15
|
<% else %>
|
16
16
|
<%= form_builder.input(
|
17
|
-
"#{name}_search"
|
17
|
+
:"#{name}_search",
|
18
18
|
input_html: merge_options(input_options, default_input_options)
|
19
19
|
) %>
|
20
20
|
<div class="hidden">
|
21
21
|
<%= form_builder.input(
|
22
|
-
"#{name}_id"
|
22
|
+
:"#{name}_id",
|
23
23
|
input_html: { data: { autocomplete_target: "hidden" } }
|
24
24
|
) %>
|
25
25
|
</div>
|
@@ -34,13 +34,11 @@ module Anchor
|
|
34
34
|
warning: "text-warning",
|
35
35
|
}.freeze
|
36
36
|
|
37
|
-
def initialize(**kwargs)
|
38
|
-
@icon = ICON_MAPPINGS[
|
39
|
-
|
40
|
-
@icon_variant = ICON_VARIANT_MAPPINGS[kwargs[:variant]] ||
|
41
|
-
ICON_VARIANT_MAPPINGS[VARIANT_DEFAULT]
|
37
|
+
def initialize(variant: VARIANT_DEFAULT, **kwargs)
|
38
|
+
@icon = ICON_MAPPINGS[variant]
|
39
|
+
@icon_variant = ICON_VARIANT_MAPPINGS[variant]
|
42
40
|
|
43
|
-
super
|
41
|
+
super(variant:, **kwargs)
|
44
42
|
end
|
45
43
|
|
46
44
|
private
|
@@ -35,7 +35,6 @@ module Anchor
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def initialize(
|
38
|
-
tag: nil,
|
39
38
|
type: TYPE_DEFAULT,
|
40
39
|
size: SIZE_DEFAULT,
|
41
40
|
href: nil,
|
@@ -43,12 +42,6 @@ module Anchor
|
|
43
42
|
full_width: false,
|
44
43
|
**kwargs
|
45
44
|
)
|
46
|
-
if tag
|
47
|
-
ActiveSupport::Deprecation.warn(
|
48
|
-
"`tag` is now set automatically and should no longer be used."
|
49
|
-
)
|
50
|
-
end
|
51
|
-
|
52
45
|
@type = fetch_or_fallback(TYPE_OPTIONS, type, TYPE_DEFAULT)
|
53
46
|
@size = SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size,
|
54
47
|
SIZE_DEFAULT)]
|
@@ -4,7 +4,7 @@
|
|
4
4
|
action: "click->copy-to-clipboard#copy",
|
5
5
|
controller: "copy-to-clipboard",
|
6
6
|
copy_to_clipboard_text_to_copy_value: value,
|
7
|
-
|
7
|
+
copy_to_clipboard_assistive_tech_notification_value: t(".assistive_tech_notification"),
|
8
8
|
},
|
9
9
|
class: "bg-transparent",
|
10
10
|
type: "button",
|
@@ -15,7 +15,7 @@
|
|
15
15
|
) %>
|
16
16
|
<%= anchor_icon(
|
17
17
|
icon: "check",
|
18
|
-
|
18
|
+
hidden: true,
|
19
19
|
data: { copy_to_clipboard_target: "successIcon"},
|
20
20
|
) %>
|
21
21
|
<% end %>
|
@@ -1,26 +1,28 @@
|
|
1
1
|
import { Controller } from "@hotwired/stimulus";
|
2
|
+
import { notifyAssistiveTech } from "./assistive_tech_notifications_component";
|
2
3
|
|
3
4
|
export default class extends Controller<HTMLDivElement> {
|
4
5
|
static targets = [ "initialIcon", "successIcon"];
|
5
|
-
static
|
6
|
+
static values = {
|
7
|
+
assistiveTechNotification: String,
|
8
|
+
notificationDelay: { type: Number, default: 1500 },
|
9
|
+
textToCopy: String,
|
10
|
+
};
|
6
11
|
|
7
12
|
declare readonly initialIconTarget: SVGElement;
|
8
13
|
declare readonly successIconTarget: SVGElement;
|
9
14
|
declare readonly notificationDelayValue: number;
|
10
15
|
declare readonly hiddenClass: string;
|
11
16
|
declare readonly textToCopyValue: string;
|
17
|
+
declare readonly assistiveTechNotificationValue: string;
|
12
18
|
declare readonly hasTextToCopyValue: boolean;
|
13
19
|
|
14
|
-
static values = {
|
15
|
-
notificationDelay: { type: Number, default: 1500 },
|
16
|
-
textToCopy: String,
|
17
|
-
};
|
18
|
-
|
19
20
|
copy(): void {
|
20
21
|
if (this.hasTextToCopyValue) {
|
21
22
|
navigator.clipboard.writeText(this.textToCopyValue);
|
22
23
|
}
|
23
24
|
this.toggleIcons();
|
25
|
+
notifyAssistiveTech(this.assistiveTechNotificationValue);
|
24
26
|
|
25
27
|
setTimeout(() => {
|
26
28
|
this.toggleIcons();
|
@@ -28,7 +30,7 @@ export default class extends Controller<HTMLDivElement> {
|
|
28
30
|
}
|
29
31
|
|
30
32
|
toggleIcons(): void {
|
31
|
-
this.initialIconTarget.
|
32
|
-
this.successIconTarget.
|
33
|
+
this.initialIconTarget.toggleAttribute("hidden");
|
34
|
+
this.successIconTarget.toggleAttribute("hidden");
|
33
35
|
}
|
34
36
|
}
|
@@ -8,4 +8,12 @@ export default class extends Controller<HTMLDialogElement> {
|
|
8
8
|
close(): void {
|
9
9
|
this.element.close();
|
10
10
|
}
|
11
|
+
|
12
|
+
reset(): void {
|
13
|
+
this.#resetErrorMessages();
|
14
|
+
}
|
15
|
+
|
16
|
+
#resetErrorMessages(): void {
|
17
|
+
this.element.querySelectorAll("[data-error='true']").forEach((element) => element.remove());
|
18
|
+
}
|
11
19
|
}
|
@@ -13,8 +13,8 @@ module Anchor
|
|
13
13
|
text-sm
|
14
14
|
).freeze
|
15
15
|
|
16
|
-
def initialize(
|
17
|
-
@
|
16
|
+
def initialize(object:, attribute:, **kwargs)
|
17
|
+
@object = object
|
18
18
|
@attribute = attribute
|
19
19
|
|
20
20
|
super(**kwargs)
|
@@ -22,16 +22,14 @@ module Anchor
|
|
22
22
|
|
23
23
|
private
|
24
24
|
|
25
|
-
attr_reader :attribute
|
26
|
-
|
27
|
-
delegate :object, to: :form_builder
|
25
|
+
attr_reader :attribute
|
28
26
|
|
29
27
|
def error_message
|
30
|
-
object.errors.full_messages_for(attribute).to_sentence
|
28
|
+
@object.errors.full_messages_for(attribute).to_sentence
|
31
29
|
end
|
32
30
|
|
33
31
|
def render?
|
34
|
-
object.errors.has_key?(attribute)
|
32
|
+
@object.errors.has_key?(attribute)
|
35
33
|
end
|
36
34
|
end
|
37
35
|
end
|
@@ -20,23 +20,19 @@ module Anchor
|
|
20
20
|
).freeze
|
21
21
|
|
22
22
|
def initialize(
|
23
|
-
form_builder:,
|
24
23
|
attribute:,
|
25
|
-
type:,
|
26
24
|
starting_icon: nil,
|
27
25
|
ending_icon: nil,
|
28
26
|
**kwargs
|
29
27
|
)
|
30
|
-
@form_builder = form_builder
|
31
28
|
@attribute = attribute
|
32
|
-
@type = type
|
33
29
|
@starting_icon = starting_icon
|
34
30
|
@ending_icon = ending_icon
|
35
31
|
|
36
32
|
super(**kwargs)
|
37
33
|
end
|
38
34
|
|
39
|
-
attr_reader :attribute, :
|
35
|
+
attr_reader :attribute, :starting_icon, :ending_icon
|
40
36
|
|
41
37
|
def options
|
42
38
|
{
|
@@ -8,14 +8,10 @@ module Anchor
|
|
8
8
|
|
9
9
|
attr_accessor :options
|
10
10
|
|
11
|
-
def initialize(
|
12
|
-
@
|
13
|
-
@attribute = attribute
|
14
|
-
@options = options.merge(
|
15
|
-
class: Array(options.delete(:class)) + LABEL_CLASSES
|
16
|
-
)
|
11
|
+
def initialize(object:, attribute:, required: nil)
|
12
|
+
@options = { class: LABEL_CLASSES }
|
17
13
|
@required = ModelValidators
|
18
|
-
.new(
|
14
|
+
.new(object, required:)
|
19
15
|
.attribute_required?(attribute)
|
20
16
|
|
21
17
|
super()
|
@@ -1,10 +1,11 @@
|
|
1
1
|
<%= tag.footer(**merge_options(wrapper_options,
|
2
2
|
class: class_names(
|
3
3
|
Anchor::PageComponent::HORIZONTAL_PADDING_CLASS,
|
4
|
-
"py-5 flex justify-end gap-4 border-t bg-white border-subdued sticky bottom-0",
|
4
|
+
"py-5 flex items-center justify-end gap-4 border-t bg-white border-subdued sticky bottom-0",
|
5
5
|
),
|
6
6
|
data: { testid: "page-footer" }),
|
7
7
|
) do %>
|
8
|
+
<%= supporting_text %>
|
8
9
|
<%= delete_button unless done_button? %>
|
9
10
|
<%= cancel_button unless done_button? %>
|
10
11
|
<%= save_button unless done_button? %>
|
@@ -1,40 +1,7 @@
|
|
1
1
|
module Anchor
|
2
2
|
class RadioButtonCollectionComponent < Component
|
3
|
-
attr_reader :attribute, :descriptions, :form_builder
|
4
|
-
|
5
|
-
def initialize(
|
6
|
-
form_builder:,
|
7
|
-
attribute:,
|
8
|
-
collection:,
|
9
|
-
value_method:,
|
10
|
-
text_method:,
|
11
|
-
descriptions: nil,
|
12
|
-
**options
|
13
|
-
)
|
14
|
-
@form_builder = form_builder
|
15
|
-
@attribute = attribute
|
16
|
-
@collection = collection
|
17
|
-
@value_method = value_method
|
18
|
-
@text_method = text_method
|
19
|
-
@descriptions = descriptions
|
20
|
-
@options = options
|
21
|
-
|
22
|
-
super()
|
23
|
-
end
|
24
|
-
|
25
3
|
def options
|
26
|
-
|
27
|
-
class: Array(@options.delete(:class)) +
|
28
|
-
RadioButtonComponent::INPUT_CLASSES
|
29
|
-
)
|
30
|
-
end
|
31
|
-
|
32
|
-
def radio(radio:)
|
33
|
-
RadioButtonComponent.new(
|
34
|
-
radio:,
|
35
|
-
attribute:,
|
36
|
-
form_builder:
|
37
|
-
)
|
4
|
+
{ class: RadioButtonComponent::INPUT_CLASSES }
|
38
5
|
end
|
39
6
|
end
|
40
7
|
end
|
@@ -5,7 +5,7 @@
|
|
5
5
|
<% if description.present? %>
|
6
6
|
<div class="flex flex-col gap-1">
|
7
7
|
<%= radio.label %>
|
8
|
-
<%=
|
8
|
+
<%= tag.span description, id: description_id, class: DESCRIPTION_CLASSES %>
|
9
9
|
</div>
|
10
10
|
<% else %>
|
11
11
|
<%= tag.div radio.label, class: "self-center" %>
|
@@ -36,24 +36,20 @@ module Anchor
|
|
36
36
|
text-sm
|
37
37
|
).freeze
|
38
38
|
|
39
|
-
def initialize(radio:, attribute:,
|
39
|
+
def initialize(form_builder:, radio:, attribute:, **kwargs)
|
40
|
+
@form_builder = form_builder
|
40
41
|
@radio = radio
|
41
42
|
@attribute = attribute
|
42
|
-
@form_builder = form_builder
|
43
43
|
|
44
44
|
super(**kwargs)
|
45
45
|
end
|
46
46
|
|
47
47
|
private
|
48
48
|
|
49
|
-
attr_reader :
|
49
|
+
attr_reader :form_builder, :radio, :attribute
|
50
50
|
|
51
51
|
delegate :value, :object, to: :radio
|
52
52
|
|
53
|
-
def description_span
|
54
|
-
tag.span description, id: description_id, class: DESCRIPTION_CLASSES
|
55
|
-
end
|
56
|
-
|
57
53
|
def radio_options
|
58
54
|
{
|
59
55
|
aria: description.present? && aria_description,
|
@@ -1,15 +1,11 @@
|
|
1
1
|
module Anchor
|
2
2
|
class SelectComponent < Component
|
3
|
-
attr_reader :attribute
|
4
|
-
|
5
3
|
def initialize(
|
6
|
-
form_builder:,
|
7
4
|
attribute:,
|
8
5
|
choices:,
|
9
6
|
show_marker: true,
|
10
7
|
**kwargs
|
11
8
|
)
|
12
|
-
@form_builder = form_builder
|
13
9
|
@attribute = attribute
|
14
10
|
@choices = choices
|
15
11
|
@show_marker = show_marker
|
@@ -24,7 +20,7 @@ module Anchor
|
|
24
20
|
def html_options
|
25
21
|
{
|
26
22
|
class: InputComponent::INPUT_CLASSES,
|
27
|
-
data: { testid: "select-#{attribute.to_s.dasherize}" },
|
23
|
+
data: { testid: "select-#{@attribute.to_s.dasherize}" },
|
28
24
|
}
|
29
25
|
end
|
30
26
|
|
@@ -1,7 +1,11 @@
|
|
1
1
|
<%= tag.div(
|
2
2
|
**merge_options(wrapper_options, {
|
3
3
|
class: "toast",
|
4
|
-
data: {
|
4
|
+
data: {
|
5
|
+
controller: "toast",
|
6
|
+
toast_assistive_tech_notification_value: assistive_tech_notification,
|
7
|
+
testid: test_id,
|
8
|
+
},
|
5
9
|
popover: "manual",
|
6
10
|
})
|
7
11
|
) do %>
|
@@ -1,11 +1,14 @@
|
|
1
1
|
import { Controller } from "@hotwired/stimulus";
|
2
|
+
import { notifyAssistiveTech } from "./assistive_tech_notifications_component";
|
2
3
|
|
3
4
|
export default class extends Controller<HTMLDivElement> {
|
4
5
|
static values = {
|
6
|
+
assistiveTechNotification: String,
|
5
7
|
hideDelay: { type: Number, default: 3000 },
|
6
8
|
showDelay: { type: Number, default: 200 },
|
7
9
|
};
|
8
10
|
|
11
|
+
declare assistiveTechNotificationValue: string;
|
9
12
|
declare hideDelayValue: number
|
10
13
|
declare showDelayValue: number
|
11
14
|
|
@@ -17,6 +20,8 @@ export default class extends Controller<HTMLDivElement> {
|
|
17
20
|
setTimeout(() => {
|
18
21
|
this.element.remove();
|
19
22
|
}, this.hideDelayValue);
|
23
|
+
|
24
|
+
notifyAssistiveTech(this.assistiveTechNotificationValue);
|
20
25
|
}
|
21
26
|
|
22
27
|
disconnect(): void {
|
@@ -32,15 +32,7 @@ module Anchor
|
|
32
32
|
options = {},
|
33
33
|
html_options = {}
|
34
34
|
)
|
35
|
-
render RadioButtonCollectionComponent.new
|
36
|
-
form_builder: self,
|
37
|
-
attribute:,
|
38
|
-
collection:,
|
39
|
-
value_method:,
|
40
|
-
text_method:,
|
41
|
-
**options,
|
42
|
-
**html_options
|
43
|
-
) do |component|
|
35
|
+
render RadioButtonCollectionComponent.new do |component|
|
44
36
|
super(
|
45
37
|
attribute,
|
46
38
|
collection,
|
@@ -49,7 +41,11 @@ module Anchor
|
|
49
41
|
options,
|
50
42
|
component.options.merge(html_options),
|
51
43
|
) do |radio|
|
52
|
-
render
|
44
|
+
render RadioButtonComponent.new(
|
45
|
+
radio:,
|
46
|
+
attribute:,
|
47
|
+
form_builder: self
|
48
|
+
)
|
53
49
|
end
|
54
50
|
end
|
55
51
|
end
|
@@ -86,9 +82,7 @@ module Anchor
|
|
86
82
|
|
87
83
|
def email_field(attribute, options = {})
|
88
84
|
render InputComponent.new(
|
89
|
-
form_builder: self,
|
90
85
|
attribute:,
|
91
|
-
type: :email,
|
92
86
|
starting_icon: options.delete(:starting_icon),
|
93
87
|
ending_icon: options.delete(:ending_icon)
|
94
88
|
) do |component|
|
@@ -98,7 +92,7 @@ module Anchor
|
|
98
92
|
|
99
93
|
def error_message_for(attribute, options = {})
|
100
94
|
render ErrorMessageComponent.new(
|
101
|
-
|
95
|
+
object:,
|
102
96
|
attribute:,
|
103
97
|
**options
|
104
98
|
)
|
@@ -106,10 +100,9 @@ module Anchor
|
|
106
100
|
|
107
101
|
def label(attribute, text = nil, options = {}, &block)
|
108
102
|
render LabelComponent.new(
|
109
|
-
|
103
|
+
object:,
|
110
104
|
attribute:,
|
111
|
-
|
112
|
-
**options
|
105
|
+
required: options.delete(:required) { :if_has_validators }
|
113
106
|
) do |component|
|
114
107
|
if block.present?
|
115
108
|
super
|
@@ -126,31 +119,26 @@ module Anchor
|
|
126
119
|
|
127
120
|
def number_field(attribute, options = {})
|
128
121
|
render InputComponent.new(
|
129
|
-
form_builder: self,
|
130
122
|
attribute:,
|
131
|
-
type: :number,
|
132
123
|
starting_icon: options.delete(:starting_icon),
|
133
124
|
ending_icon: options.delete(:ending_icon)
|
134
125
|
) do |component|
|
135
|
-
super
|
126
|
+
super(attribute, component.options.merge(options))
|
136
127
|
end
|
137
128
|
end
|
138
129
|
|
139
130
|
def search_field(attribute, options = {})
|
140
131
|
render InputComponent.new(
|
141
|
-
form_builder: self,
|
142
132
|
attribute:,
|
143
|
-
type: :search,
|
144
133
|
starting_icon: options.delete(:starting_icon),
|
145
134
|
ending_icon: options.delete(:ending_icon)
|
146
135
|
) do |component|
|
147
|
-
super
|
136
|
+
super(attribute, component.options.merge(options))
|
148
137
|
end
|
149
138
|
end
|
150
139
|
|
151
140
|
def select(attribute, choices = nil, options = {}, html_options = {}, &)
|
152
141
|
render SelectComponent.new(
|
153
|
-
form_builder: self,
|
154
142
|
attribute:,
|
155
143
|
choices:,
|
156
144
|
show_marker: options.fetch(:show_marker, true),
|
@@ -168,9 +156,7 @@ module Anchor
|
|
168
156
|
|
169
157
|
def text_field(attribute, options = {})
|
170
158
|
render InputComponent.new(
|
171
|
-
form_builder: self,
|
172
159
|
attribute:,
|
173
|
-
type: :text,
|
174
160
|
starting_icon: options.delete(:starting_icon),
|
175
161
|
ending_icon: options.delete(:ending_icon)
|
176
162
|
) do |component|
|
@@ -1,13 +1,18 @@
|
|
1
1
|
module Anchor
|
2
2
|
class ModelValidators
|
3
|
-
|
3
|
+
# Pass the value :if_has_validators to the required keyword argument to
|
4
|
+
# indicate an unspecified value. Passing any other value will result in
|
5
|
+
# `required`'s truthiness being used to determine whether the attribute is
|
6
|
+
# required, while passing :if_has_validators will check the model's
|
7
|
+
# validators.
|
8
|
+
def initialize(model, required: :if_has_validators)
|
4
9
|
@model = model
|
5
|
-
@
|
10
|
+
@required = required
|
6
11
|
end
|
7
12
|
|
8
13
|
def attribute_required?(attribute)
|
9
|
-
if
|
10
|
-
|
14
|
+
if required != :if_has_validators
|
15
|
+
required
|
11
16
|
elsif has_validators?
|
12
17
|
attribute_required_by_validators?(attribute)
|
13
18
|
else
|
@@ -17,7 +22,7 @@ module Anchor
|
|
17
22
|
|
18
23
|
private
|
19
24
|
|
20
|
-
attr_reader :model, :
|
25
|
+
attr_reader :model, :required
|
21
26
|
|
22
27
|
def has_validators?
|
23
28
|
model.class.respond_to?(:validators_on)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Anchor
|
2
|
+
class AvoidImplicitSuper < RuboCop::Cop::Cop
|
3
|
+
MSG = "Avoid implicit `super` calls. Prefer `super(**kwargs)`, " \
|
4
|
+
"`super()`, or a mix of explicit/implicit arguments, e.g. " \
|
5
|
+
"`super(id:, title:, **kwargs)`.".freeze
|
6
|
+
|
7
|
+
def_node_matcher :initialize_with_implicit_super?, <<~PATTERN
|
8
|
+
(def :initialize _ (begin ... $`zsuper ...))
|
9
|
+
PATTERN
|
10
|
+
|
11
|
+
def on_def(node)
|
12
|
+
return unless (zsuper = initialize_with_implicit_super?(node))
|
13
|
+
|
14
|
+
add_offense(zsuper, message:)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def message
|
20
|
+
MSG
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<%= anchor_text do %>
|
2
|
+
This component is visually hidden, by design. Inspect the page source and look
|
3
|
+
for the two
|
4
|
+
<%= anchor_link(
|
5
|
+
"ARIA live regions",
|
6
|
+
href: "https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions",
|
7
|
+
) %>:
|
8
|
+
<code>div#js-assistive-tech-notifications</code>
|
9
|
+
and <code>div#js-assistive-tech-notifications-assertive</code>.
|
10
|
+
<% end %>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<% invalid_user = FakeUser.new(name: "").tap(&:validate) %>
|
2
|
+
|
3
|
+
<%= anchor_dialog(
|
4
|
+
id: "my-dialog",
|
5
|
+
title: "Dialog Title",
|
6
|
+
) do |dialog| %>
|
7
|
+
<% dialog.with_show_button_content("Open dialog") %>
|
8
|
+
|
9
|
+
<% dialog.with_body do %>
|
10
|
+
<%= anchor_form_with(model: invalid_user, url: false, id: 'a-form') do |form| %>
|
11
|
+
<%= form.label(:name) %>
|
12
|
+
<%= form.text_field(:name, required: true) %>
|
13
|
+
<%= form.error_message_for(:name) %>
|
14
|
+
<% end %>
|
15
|
+
<% end %>
|
16
|
+
|
17
|
+
<% dialog.with_footer do %>
|
18
|
+
<%= anchor_button("Cancel", data: { action: "dialog#close" }) %>
|
19
|
+
<%= anchor_button("Save", variant: :primary, type: :submit, form: 'a-form') %>
|
20
|
+
<% end %>
|
21
|
+
<% end %>
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<%= anchor_page do |page| %>
|
2
|
+
<% page.with_header do |header| %>
|
3
|
+
<% header.with_breadcrumbs do |breadcrumbs| %>
|
4
|
+
<% breadcrumbs.with_item(href: "#").with_content("Breadcrumb 1") %>
|
5
|
+
<% breadcrumbs.with_item(href: "").with_content("Breadcrumb 2") %>
|
6
|
+
<% end %>
|
7
|
+
<% header.with_title("Page title") %>
|
8
|
+
<% end %>
|
9
|
+
<% page.with_body do |body| %>
|
10
|
+
<%= anchor_form_with(url: false, id: "form") do |form| %>
|
11
|
+
<%= tag.div class: "max-w-sm" do %>
|
12
|
+
<%= form.label :name, "Name" %>
|
13
|
+
<%= form.text_field(
|
14
|
+
:name,
|
15
|
+
) %>
|
16
|
+
<% end %>
|
17
|
+
<% end %>
|
18
|
+
<% end %>
|
19
|
+
<% page.with_footer do |footer| %>
|
20
|
+
<% footer.with_cancel_button(href: "#") %>
|
21
|
+
<% footer.with_save_button(form: "form") %>
|
22
|
+
<% footer.with_supporting_text("Optional text") %>
|
23
|
+
<% end %>
|
24
|
+
<% end %>
|
@@ -1,20 +1,11 @@
|
|
1
|
-
<%= anchor_form_with url: false
|
2
|
-
<%=
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
:
|
10
|
-
|
11
|
-
starting_icon: :dollar,
|
12
|
-
step: 0.01
|
13
|
-
) %>
|
14
|
-
<%= form.number_field(
|
15
|
-
:amount,
|
16
|
-
placeholder: "0",
|
17
|
-
ending_icon: :dollar,
|
18
|
-
step: 1
|
19
|
-
) %>
|
1
|
+
<%= anchor_form_with url: false do |form| %>
|
2
|
+
<%= tag.div do %>
|
3
|
+
<%= form.label :price %>
|
4
|
+
<%= form.text_field :price, inputmode: :numeric, starting_icon: "dollar" %>
|
5
|
+
<% end %>
|
6
|
+
|
7
|
+
<%= tag.div do %>
|
8
|
+
<%= form.label :discount %>
|
9
|
+
<%= form.text_field :discount, inputmode: :numeric, ending_icon: "percentage" %>
|
10
|
+
<% end %>
|
20
11
|
<% end %>
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: anchor_view_components
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.45.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Buoy Software
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-01-
|
11
|
+
date: 2024-01-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -69,6 +69,7 @@ files:
|
|
69
69
|
- app/assets/images/icons/nav-arrow-left.svg
|
70
70
|
- app/assets/images/icons/nav-arrow-right.svg
|
71
71
|
- app/assets/images/icons/paste-clipboard.svg
|
72
|
+
- app/assets/images/icons/percentage.svg
|
72
73
|
- app/assets/images/icons/plus.svg
|
73
74
|
- app/assets/images/icons/search.svg
|
74
75
|
- app/assets/images/icons/warning-circle.svg
|
@@ -82,6 +83,9 @@ files:
|
|
82
83
|
- app/components/anchor/action_menu_component.html.erb
|
83
84
|
- app/components/anchor/action_menu_component.rb
|
84
85
|
- app/components/anchor/anchor_view_components.ts
|
86
|
+
- app/components/anchor/assistive_tech_notifications_component.html.erb
|
87
|
+
- app/components/anchor/assistive_tech_notifications_component.rb
|
88
|
+
- app/components/anchor/assistive_tech_notifications_component.ts
|
85
89
|
- app/components/anchor/autocomplete/results_component.html.erb
|
86
90
|
- app/components/anchor/autocomplete/results_component.rb
|
87
91
|
- app/components/anchor/autocomplete_component.en.yml
|
@@ -191,10 +195,13 @@ files:
|
|
191
195
|
- lib/anchor/view_components/svg_resolver.rb
|
192
196
|
- lib/anchor/view_components/version.rb
|
193
197
|
- lib/anchor_view_components.rb
|
198
|
+
- lib/cops/anchor/avoid_implicit_super.rb
|
194
199
|
- previews/anchor/action_menu_component_preview.rb
|
195
200
|
- previews/anchor/action_menu_component_preview/autoplacement.html.erb
|
196
201
|
- previews/anchor/action_menu_component_preview/custom_trigger.html.erb
|
197
202
|
- previews/anchor/action_menu_component_preview/opens_a_dialog.html.erb
|
203
|
+
- previews/anchor/assistive_tech_notifications_component_preview.rb
|
204
|
+
- previews/anchor/assistive_tech_notifications_component_preview/default.html.erb
|
198
205
|
- previews/anchor/badge_component_preview.rb
|
199
206
|
- previews/anchor/banner_component_preview.rb
|
200
207
|
- previews/anchor/banner_component_preview/body_only.html.erb
|
@@ -209,6 +216,7 @@ files:
|
|
209
216
|
- previews/anchor/dialog_component_preview/positioned_right.html.erb
|
210
217
|
- previews/anchor/dialog_component_preview/with_custom_show_button.html.erb
|
211
218
|
- previews/anchor/dialog_component_preview/with_footer.html.erb
|
219
|
+
- previews/anchor/dialog_component_preview/with_form.html.erb
|
212
220
|
- previews/anchor/filter_component_preview.rb
|
213
221
|
- previews/anchor/forms_preview.rb
|
214
222
|
- previews/anchor/icon_component_preview.rb
|
@@ -223,6 +231,7 @@ files:
|
|
223
231
|
- previews/anchor/page_component_preview/with_done_button.html.erb
|
224
232
|
- previews/anchor/page_component_preview/with_page_header_custom_content.html.erb
|
225
233
|
- previews/anchor/page_component_preview/with_search.html.erb
|
234
|
+
- previews/anchor/page_component_preview/with_supporting_text_and_buttons.html.erb
|
226
235
|
- previews/anchor/panel_component_preview.rb
|
227
236
|
- previews/anchor/panel_component_preview/with_banner.html.erb
|
228
237
|
- previews/anchor/preview.rb
|