anchor_view_components 0.43.0 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +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
|