nitro_kit 0.5.0 → 0.5.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b8300c577f175e7d5d72554924f11f38dc66ca00b4dee13d0ec68aa5a3e1d0cf
4
- data.tar.gz: ea1bf5a4b4d259a41c5979dd1f380631deb658ab68feb98d83afc978a340fe58
3
+ metadata.gz: 141423c594eb4edd2084765d1d9852750019ed2f017a604694e250024e96905b
4
+ data.tar.gz: 0bcd6aa1a87e02c7e2d09ffaa06e9dec1e83c764b73fe71a0ffd460dbfe23647
5
5
  SHA512:
6
- metadata.gz: 0a1f06668d4e2aae4ad2863bcb3c5925d96ca62567e7bc0a3c7bddc6f2dcd36131bcc6c3c6834b4a30f88dbe5c22e695261a1047cd2d50169846bdc944496df5
7
- data.tar.gz: 3ecb781fcbf9e5c8dd270c68d07dfc54517ecb32ba6096b65a91d497562edda9f2dfa99255deb75e99262971272f1198108e6cf214cabb65ce0b1048f4853abc
6
+ metadata.gz: 69ec22e2e77d95551809913d4704abae453ffe5a21760d594aea5757bcdb15521c7a04835c0d28708bd411039aff1fdf14bc60f1ae1faf24851fc72e4ea666fb
7
+ data.tar.gz: a941e992a0171a82198d8d0fe877954facae984fef073338b6045a546905562ec2a93dcff77c95a216256f335393d754736e49e639d8bac82d7ff4cbf1496189
@@ -2,8 +2,6 @@
2
2
 
3
3
  module NitroKit
4
4
  class Avatar < Component
5
- include ActionView::Helpers::AssetUrlHelper
6
-
7
5
  def initialize(src_arg = nil, src: nil, size: :md, **attrs)
8
6
  @src = src_arg || src
9
7
  @size = size
@@ -23,7 +21,7 @@ module NitroKit
23
21
  end
24
22
 
25
23
  def image
26
- helpers.image_tag(src, class: image_class)
24
+ safe(helpers.image_tag(src, class: image_class))
27
25
  end
28
26
 
29
27
  private
@@ -22,7 +22,7 @@ module NitroKit
22
22
  @options = attrs[:options]
23
23
 
24
24
  @field_attrs = attrs
25
- @field_label = label || field_name.to_s.humanize
25
+ @field_label = label.nil? ? field_name.to_s.humanize : label
26
26
  @field_description = description
27
27
  @field_error_messages = errors
28
28
 
@@ -8,10 +8,14 @@ module NitroKit
8
8
  @template.render(NitroKit::Fieldset.new(**attrs), &block)
9
9
  end
10
10
 
11
- def field(field_name, **attrs, &block)
12
- label = attrs.fetch(:label, field_name.to_s.humanize)
11
+ def field(field_name, label: nil, errors: nil, **attrs, &block)
12
+ if label.nil?
13
+ label = attrs.fetch(:label, field_name.to_s.humanize)
14
+ end
13
15
 
14
- errors = object && object.errors.include?(field_name) ? object.errors.full_messages_for(field_name) : nil
16
+ if errors.nil?
17
+ errors = (object && object.errors.include?(field_name) ? object.errors.full_messages_for(field_name) : nil)
18
+ end
15
19
 
16
20
  @template.render(NitroKit::Field.new(self, field_name, label:, errors:, **attrs), &block)
17
21
  end
@@ -20,7 +24,7 @@ module NitroKit
20
24
  @template.render(FieldGroup.new(**attrs), &block)
21
25
  end
22
26
 
23
- # Inputs
27
+ # Input types
24
28
 
25
29
  %i[
26
30
  checkbox
@@ -47,7 +51,8 @@ module NitroKit
47
51
  ]
48
52
  .each do |method|
49
53
  define_method(method) do |*args, **attrs, &block|
50
- @template.send("nk_#{method}", *args, **attrs, &block)
54
+ type = method.to_s.gsub(/_field$/, "")
55
+ field(*args, **attrs, type:, label: false, &block)
51
56
  end
52
57
  end
53
58
 
@@ -2,8 +2,6 @@
2
2
 
3
3
  module NitroKit
4
4
  class RadioButton < Component
5
- include ActionView::Helpers::FormTagHelper
6
-
7
5
  def initialize(label: nil, **attrs)
8
6
  @label = label
9
7
  @id = id || SecureRandom.hex(4)
@@ -27,7 +27,9 @@ module NitroKit
27
27
  week
28
28
  ]
29
29
  .each do |type|
30
- define_method("nk_#{type}_field") do |**attrs|
30
+ define_method("nk_#{type}_field_tag") do |compat_name = nil, compat_value = nil, **attrs|
31
+ attrs[:name] ||= compat_name
32
+ attrs[:value] ||= compat_value
31
33
  nk_input(type:, **attrs)
32
34
  end
33
35
  end
@@ -0,0 +1,18 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = ["trigger", "content"];
5
+
6
+ toggle(event) {
7
+ const trigger = event.target;
8
+ const content = trigger.parentElement.querySelector(
9
+ "[data-nk--accordion-target=content]",
10
+ );
11
+
12
+ const isExpanded = trigger.getAttribute("aria-expanded") === "true";
13
+
14
+ // Toggle current item
15
+ trigger.setAttribute("aria-expanded", (!isExpanded).toString());
16
+ content.setAttribute("aria-hidden", isExpanded.toString());
17
+ }
18
+ }
@@ -0,0 +1,130 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import Combobox from "@github/combobox-nav";
3
+ import {
4
+ computePosition,
5
+ offset,
6
+ flip,
7
+ shift,
8
+ autoUpdate,
9
+ } from "@floating-ui/dom";
10
+
11
+ export default class extends Controller {
12
+ static targets = ["input", "list", "hiddenField", "clearButton"];
13
+ static values = {
14
+ open: { type: Boolean, default: false },
15
+ // Options for floating-ui
16
+ placement: { type: String },
17
+ // Options for combobox-nav
18
+ tabInsertsSuggestions: { type: Boolean },
19
+ firstOptionSelectionMode: { type: String },
20
+ scrollIntoViewOptions: { type: Object },
21
+ };
22
+
23
+ connect() {
24
+ this.combobox = new Combobox(this.inputTarget, this.listTarget, {
25
+ tabInsertsSuggestions: this.tabInsertsSuggestionsValue,
26
+ firstOptionSelectionMode: this.firstOptionSelectionModeValue,
27
+ scrollIntoViewOptions: this.scrollIntoViewOptionsValue,
28
+ });
29
+
30
+ this.updatePosition();
31
+
32
+ this.listTarget.addEventListener("combobox-commit", (event) => {
33
+ this.select(event);
34
+ this.close();
35
+ });
36
+ }
37
+
38
+ disconnect() {
39
+ this.combobox.destroy();
40
+ }
41
+
42
+ select(event) {
43
+ this.inputTarget.value = event.target.textContent;
44
+ this.hiddenFieldTarget.value =
45
+ event.target.dataset.value || event.target.textContent;
46
+ }
47
+
48
+ open() {
49
+ this.openValue = true;
50
+ }
51
+
52
+ close() {
53
+ this.openValue = false;
54
+ }
55
+
56
+ focusShift({ target }) {
57
+ if (!this.openValue) return;
58
+ if (this.element.contains(target)) return;
59
+
60
+ this.close();
61
+ }
62
+
63
+ windowClick({ target }) {
64
+ if (!this.openValue) return;
65
+ if (this.element.contains(target)) return;
66
+
67
+ this.close();
68
+ }
69
+
70
+ clear() {
71
+ this.combobox.resetSelection();
72
+ this.inputTarget.value = "";
73
+ this.input();
74
+ this.hiddenFieldTarget.value = "";
75
+ }
76
+
77
+ input(_event) {
78
+ if (!this.isOpen) this.open();
79
+
80
+ const filter = this.inputTarget.value.toLowerCase();
81
+
82
+ Array.from(this.listTarget.children).forEach((item) => {
83
+ const value = item.dataset.value?.toLowerCase();
84
+ const text = item.textContent.toLowerCase();
85
+
86
+ if (value?.includes(filter) || text.includes(filter)) {
87
+ item.setAttribute("role", "option");
88
+ } else {
89
+ item.removeAttribute("role");
90
+ }
91
+ });
92
+
93
+ this.hiddenFieldTarget.value = this.inputTarget.value;
94
+ }
95
+
96
+ openValueChanged(state, _previous) {
97
+ if (!this.combobox) return;
98
+
99
+ if (state) {
100
+ this.combobox.start();
101
+
102
+ this.listTarget.dataset.state = "open";
103
+
104
+ this.clearAutoUpdate = autoUpdate(
105
+ this.inputTarget,
106
+ this.listTarget,
107
+ this.updatePosition,
108
+ );
109
+ } else {
110
+ this.combobox.stop();
111
+
112
+ this.listTarget.dataset.state = "closed";
113
+
114
+ if (this.clearAutoUpdate) {
115
+ this.clearAutoUpdate();
116
+ }
117
+ }
118
+ }
119
+
120
+ updatePosition = () => {
121
+ computePosition(this.inputTarget, this.listTarget, {
122
+ placement: this.placementValue,
123
+ middleware: [offset(5), flip(), shift({ padding: 5 })],
124
+ }).then(({ x, y }) => {
125
+ this.listTarget.style.left = `${x}px`;
126
+ this.listTarget.style.top = `${y}px`;
127
+ this.listTarget.style.width = `${this.inputTarget.clientWidth}px`;
128
+ });
129
+ };
130
+ }
@@ -0,0 +1,5 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = ["input"];
5
+ }
@@ -0,0 +1,19 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = ["trigger", "dialog"];
5
+
6
+ open() {
7
+ this.dialogTarget.showModal();
8
+ }
9
+
10
+ close() {
11
+ this.dialogTarget.close();
12
+ }
13
+
14
+ clickOutside(event) {
15
+ if (event.target === this.dialogTarget) {
16
+ this.close();
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,78 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import {
3
+ computePosition,
4
+ offset,
5
+ flip,
6
+ shift,
7
+ autoUpdate,
8
+ } from "@floating-ui/dom";
9
+
10
+ export default class extends Controller {
11
+ static targets = ["trigger", "content"];
12
+ static values = {
13
+ placement: { type: String, default: "bottom" },
14
+ };
15
+
16
+ connect() {
17
+ this.updatePosition();
18
+ }
19
+
20
+ disconnect() {
21
+ this.close();
22
+ }
23
+
24
+ get isExpanded() {
25
+ return this.triggerTarget.getAttribute("aria-expanded") === "true";
26
+ }
27
+
28
+ updatePosition = () => {
29
+ computePosition(this.triggerTarget, this.contentTarget, {
30
+ placement: this.placementValue,
31
+ middleware: [offset(5), flip(), shift({ padding: 5 })],
32
+ }).then(({ x, y }) => {
33
+ this.contentTarget.style.left = `${x}px`;
34
+ this.contentTarget.style.top = `${y}px`;
35
+ });
36
+ };
37
+
38
+ open = () => {
39
+ this.triggerTarget.setAttribute("aria-expanded", "true");
40
+ this.contentTarget.setAttribute("aria-hidden", "false");
41
+
42
+ document.addEventListener("click", this.clickOutside);
43
+
44
+ this.clearAutoUpdate = autoUpdate(
45
+ this.triggerTarget,
46
+ this.contentTarget,
47
+ this.updatePosition,
48
+ );
49
+ };
50
+
51
+ close = () => {
52
+ this.triggerTarget.setAttribute("aria-expanded", "false");
53
+ this.contentTarget.setAttribute("aria-hidden", "true");
54
+
55
+ document.removeEventListener("click", this.clickOutside);
56
+
57
+ if (this.clearAutoUpdate) {
58
+ this.clearAutoUpdate();
59
+ }
60
+ };
61
+
62
+ toggle = () => {
63
+ if (this.isExpanded) {
64
+ this.close();
65
+ } else {
66
+ this.open();
67
+ }
68
+ };
69
+
70
+ clickOutside = (event) => {
71
+ if (
72
+ !this.contentTarget.contains(event.target) &&
73
+ !this.triggerTarget.contains(event.target)
74
+ ) {
75
+ this.close();
76
+ }
77
+ };
78
+ }
@@ -0,0 +1,32 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ connect() {
5
+ if (!this.element.hasAttribute("tabindex")) {
6
+ this.element.setAttribute("tabindex", "0");
7
+ }
8
+ }
9
+
10
+ click() {
11
+ if (this.element.hasAttribute("disabled")) return;
12
+
13
+ this.toggle();
14
+ }
15
+
16
+ keydown(event) {
17
+ if (this.element.hasAttribute("disabled")) return;
18
+
19
+ if (event.code === "Space" || event.code === "Enter") {
20
+ event.preventDefault();
21
+ this.toggle();
22
+ }
23
+ }
24
+
25
+ get checked() {
26
+ return this.element.getAttribute("aria-checked") === "true";
27
+ }
28
+
29
+ toggle() {
30
+ this.element.setAttribute("aria-checked", (!this.checked).toString());
31
+ }
32
+ }
@@ -0,0 +1,46 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = ["tab", "panel"];
5
+ static values = { active: String };
6
+
7
+ setActiveTab(event) {
8
+ this.activeValue = event.params.key;
9
+ }
10
+
11
+ prevTab(event) {
12
+ const prevTab = event.target.previousElementSibling;
13
+ if (prevTab) {
14
+ prevTab.click();
15
+ prevTab.focus();
16
+ }
17
+ }
18
+
19
+ nextTab(event) {
20
+ const nextTab = event.target.nextElementSibling;
21
+ if (nextTab) {
22
+ nextTab.click();
23
+ nextTab.focus();
24
+ }
25
+ }
26
+
27
+ activeValueChanged() {
28
+ const value = this.activeValue;
29
+
30
+ this.panelTargets.forEach((panel) => {
31
+ if (panel.dataset.key === value) {
32
+ panel.ariaHidden = false;
33
+ } else {
34
+ panel.ariaHidden = true;
35
+ }
36
+ });
37
+
38
+ this.tabTargets.forEach((tab) => {
39
+ if (tab.dataset.key === value) {
40
+ tab.ariaSelected = true;
41
+ } else {
42
+ tab.ariaSelected = false;
43
+ }
44
+ });
45
+ }
46
+ }
@@ -0,0 +1,68 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = ["list", "template", "sink"];
5
+ static values = {
6
+ duration: { type: Number, default: 5000 },
7
+ };
8
+
9
+ connect() {
10
+ this.#flushSink();
11
+
12
+ if (this.hasSinkTarget) {
13
+ this.mutationObserver = new MutationObserver(([event]) => {
14
+ if (event.addedNodes.length === 0) return;
15
+ this.#flushSink();
16
+ });
17
+ this.mutationObserver.observe(this.sinkTarget, { childList: true });
18
+ }
19
+ }
20
+
21
+ disconnect() {
22
+ if (this.mutationObserver) this.mutationObserver.disconnect();
23
+ }
24
+
25
+ toast({ params }) {
26
+ const { title, description } = params;
27
+ const item = this.templateTarget.content.cloneNode(true);
28
+
29
+ item.querySelector("[data-slot=title]").textContent = title;
30
+ item.querySelector("[data-slot=description]").textContent = description;
31
+
32
+ this.show(item);
33
+ }
34
+
35
+ show(item) {
36
+ this.clear();
37
+ this.listTarget.appendChild(item);
38
+
39
+ requestAnimationFrame(() => {
40
+ this.listTarget.children[0].dataset.state = "open";
41
+ });
42
+
43
+ if (this.timer) clearTimeout(this.timer);
44
+
45
+ this.timer = setTimeout(() => {
46
+ this.hide();
47
+ }, this.durationValue);
48
+ }
49
+
50
+ hide() {
51
+ this.listTarget.children[0].dataset.state = "closed";
52
+
53
+ setTimeout(() => {
54
+ this.clear();
55
+ }, 250);
56
+ }
57
+
58
+ clear() {
59
+ this.listTarget.innerHTML = "";
60
+ }
61
+
62
+ #flushSink() {
63
+ for (const li of this.sinkTarget.children) {
64
+ this.show(li.cloneNode(true));
65
+ li.remove();
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,59 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import {
3
+ computePosition,
4
+ offset,
5
+ flip,
6
+ shift,
7
+ autoUpdate,
8
+ } from "@floating-ui/dom";
9
+
10
+ export default class extends Controller {
11
+ static targets = ["content"];
12
+ static values = {
13
+ open: { type: Boolean, default: false },
14
+ // Options for floating-ui
15
+ placement: { type: String, default: "top" },
16
+ };
17
+
18
+ connect() {
19
+ this.updatePosition();
20
+ }
21
+
22
+ disconnect() {
23
+ this.close();
24
+ }
25
+
26
+ open() {
27
+ this.openValue = true;
28
+ }
29
+
30
+ close() {
31
+ this.openValue = false;
32
+ }
33
+
34
+ openValueChanged(state, _previous) {
35
+ this.contentTarget.dataset.state = state ? "open" : "closed";
36
+
37
+ if (state) {
38
+ this.clearAutoUpdate = autoUpdate(
39
+ this.element,
40
+ this.contentTarget,
41
+ this.updatePosition,
42
+ );
43
+ } else {
44
+ if (this.clearAutoUpdate) {
45
+ this.clearAutoUpdate();
46
+ }
47
+ }
48
+ }
49
+
50
+ updatePosition = () => {
51
+ computePosition(this.element, this.contentTarget, {
52
+ placement: this.placementValue,
53
+ middleware: [offset(5), flip(), shift({ padding: 5 })],
54
+ }).then(({ x, y }) => {
55
+ this.contentTarget.style.left = `${x}px`;
56
+ this.contentTarget.style.top = `${y}px`;
57
+ });
58
+ };
59
+ }
@@ -1,3 +1,3 @@
1
1
  module NitroKit
2
- VERSION = "0.5.0"
2
+ VERSION = "0.5.2"
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nitro_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikkel Malmberg
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-02-10 00:00:00.000000000 Z
10
+ date: 2025-02-14 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -120,6 +120,15 @@ files:
120
120
  - app/helpers/nitro_kit/textarea_helper.rb
121
121
  - app/helpers/nitro_kit/toast_helper.rb
122
122
  - app/helpers/nitro_kit/tooltip_helper.rb
123
+ - app/javascript/controllers/nk/accordion_controller.js
124
+ - app/javascript/controllers/nk/combobox_controller.js
125
+ - app/javascript/controllers/nk/datepicker_controller.js
126
+ - app/javascript/controllers/nk/dialog_controller.js
127
+ - app/javascript/controllers/nk/dropdown_controller.js
128
+ - app/javascript/controllers/nk/switch_controller.js
129
+ - app/javascript/controllers/nk/tabs_controller.js
130
+ - app/javascript/controllers/nk/toast_controller.js
131
+ - app/javascript/controllers/nk/tooltip_controller.js
123
132
  - lib/generators/nitro_kit/component_generator.rb
124
133
  - lib/nitro_kit.rb
125
134
  - lib/nitro_kit/engine.rb