From c429f096e1d980eefce157a46e9c94a2af16fb42 Mon Sep 17 00:00:00 2001 From: Cirdes Date: Sat, 20 Jul 2024 20:14:00 +0000 Subject: [PATCH 1/9] Adding combobox component --- lib/phlex_ui.rb | 2 + lib/phlex_ui/combobox.rb | 15 +++ lib/phlex_ui/combobox/combobox_content.rb | 23 ++++ .../combobox/combobox_content_controller.js | 104 ++++++++++++++++++ lib/phlex_ui/combobox/combobox_controller.js | 58 ++++++++++ lib/phlex_ui/combobox/combobox_empty.rb | 15 +++ lib/phlex_ui/combobox/combobox_group.rb | 39 +++++++ lib/phlex_ui/combobox/combobox_item.rb | 52 +++++++++ .../combobox/combobox_item_controller.js | 88 +++++++++++++++ lib/phlex_ui/combobox/combobox_list.rb | 20 ++++ .../combobox/combobox_search_input.rb | 57 ++++++++++ lib/phlex_ui/combobox/combobox_separator.rb | 15 +++ lib/phlex_ui/combobox/combobox_trigger.rb | 53 +++++++++ test/phlex_ui/combobox_test.rb | 48 ++++++++ 14 files changed, 589 insertions(+) create mode 100644 lib/phlex_ui/combobox.rb create mode 100644 lib/phlex_ui/combobox/combobox_content.rb create mode 100644 lib/phlex_ui/combobox/combobox_content_controller.js create mode 100644 lib/phlex_ui/combobox/combobox_controller.js create mode 100644 lib/phlex_ui/combobox/combobox_empty.rb create mode 100644 lib/phlex_ui/combobox/combobox_group.rb create mode 100644 lib/phlex_ui/combobox/combobox_item.rb create mode 100644 lib/phlex_ui/combobox/combobox_item_controller.js create mode 100644 lib/phlex_ui/combobox/combobox_list.rb create mode 100644 lib/phlex_ui/combobox/combobox_search_input.rb create mode 100644 lib/phlex_ui/combobox/combobox_separator.rb create mode 100644 lib/phlex_ui/combobox/combobox_trigger.rb create mode 100644 test/phlex_ui/combobox_test.rb diff --git a/lib/phlex_ui.rb b/lib/phlex_ui.rb index 3067747..f0eff36 100644 --- a/lib/phlex_ui.rb +++ b/lib/phlex_ui.rb @@ -19,6 +19,7 @@ loader.collapse("#{__dir__}/phlex_ui/button") loader.collapse("#{__dir__}/phlex_ui/calendar") loader.collapse("#{__dir__}/phlex_ui/card") +loader.collapse("#{__dir__}/phlex_ui/combobox") loader.collapse("#{__dir__}/phlex_ui/chart") loader.collapse("#{__dir__}/phlex_ui/checkbox") loader.collapse("#{__dir__}/phlex_ui/clipboard") @@ -64,6 +65,7 @@ module PhlexUI autoload :Clipboard, "phlex_ui/clipboard" autoload :Codeblock, "phlex_ui/codeblock" autoload :Collapsible, "phlex_ui/collapsible" + autoload :Command, "phlex_ui/combobox" autoload :Command, "phlex_ui/command" autoload :ContextMenu, "phlex_ui/context_menu" autoload :Dialog, "phlex_ui/dialog" diff --git a/lib/phlex_ui/combobox.rb b/lib/phlex_ui/combobox.rb new file mode 100644 index 0000000..1bd2b25 --- /dev/null +++ b/lib/phlex_ui/combobox.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module PhlexUI + class Combobox < Base + def view_template(&) + div(**attrs, &) + end + + private + + def default_attrs + {data: {controller: "phlexui--combobox", action: "click@window->phlexui--combobox#clickOutside", phlexui__combobox_closed_value: "true"}} + end + end +end diff --git a/lib/phlex_ui/combobox/combobox_content.rb b/lib/phlex_ui/combobox/combobox_content.rb new file mode 100644 index 0000000..030fd7c --- /dev/null +++ b/lib/phlex_ui/combobox/combobox_content.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module PhlexUI + class ComboboxContent < Base + def view_template(&) + div(**attrs) do + div( + data: {controller: "phlexui--combobox-content", action: "keydown.up->phlexui--combobox-content#handleKeyUp keydown.down->phlexui--combobox-content#handleKeyDown keydown.enter->phlexui--combobox-content#handleKeyEnter keydown.esc->phlexui--combobox-content#handleKeyEsc"}, + class: "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground rounded-lg border shadow-md", & + ) + end + end + + private + + def default_attrs + { + data: {phlexui__combobox_target: "popover"}, + class: "invisible absolute top-0 left-0 p-1.5 rounded" + } + end + end +end diff --git a/lib/phlex_ui/combobox/combobox_content_controller.js b/lib/phlex_ui/combobox/combobox_content_controller.js new file mode 100644 index 0000000..5b0c13d --- /dev/null +++ b/lib/phlex_ui/combobox/combobox_content_controller.js @@ -0,0 +1,104 @@ +import { Controller } from "@hotwired/stimulus"; + +import { POPOVER_OPENED } from "./combobox_controller"; + +export const ITEM_KEY_UP = "phlexui--combobox-content#keyUp"; +export const ITEM_KEY_DOWN = "phlexui--combobox-content#keyDown"; +export const ITEM_KEY_ENTER = "phlexui--combobox-content#keyEnter"; +export const ITEM_KEY_ESC = "phlexui--combobox-content#keyEsc"; + +export default class extends Controller { + static targets = ["list", "item", "empty", "group", "search"]; + + connect() { + document.addEventListener(POPOVER_OPENED, (event) => this.handlePopoverToggle(event), false); + this.generateItemsIds(); + } + + disconnect() { + document.removeEventListener(POPOVER_OPENED, (event) => this.handlePopoverToggle(event), false); + } + + handlePopoverToggle(event) { + const { closed } = event.detail; + this.searchTarget.value = ""; + if (!closed) { + this.searchTarget.focus(); + this.toggleVisibility(this.itemTargets, true); + this.toggleVisibility(this.groupTargets, true); + this.toggleVisibility(this.emptyTargets, false); + } + } + + handleKeyUp() { + const id = this.getSelectedItemId(); + + const event = new CustomEvent(ITEM_KEY_UP, { detail: { id } }); + document.dispatchEvent(event); + } + + handleKeyDown() { + const id = this.getSelectedItemId(); + const length = this.itemTargets.length; + + const event = new CustomEvent(ITEM_KEY_DOWN, { detail: { id, length } }); + document.dispatchEvent(event); + } + + handleKeyEnter() { + const id = this.getSelectedItemId(); + + const event = new CustomEvent(ITEM_KEY_ENTER, { detail: { id } }); + document.dispatchEvent(event); + } + + handleKeyEsc() { + document.dispatchEvent(new CustomEvent(ITEM_KEY_ESC)); + } + + filter(event) { + const query = this.sanitizeStr(event.target.value); + + this.toggleVisibility(this.itemTargets, false); + + const visibleItems = this.filterItems(query); + this.toggleVisibility(visibleItems, true); + + this.toggleVisibility(this.emptyTargets, visibleItems.length === 0); + + this.updateGroupVisibility(); + } + + updateGroupVisibility() { + this.groupTargets.forEach((group) => { + const hasVisibleItems = group.querySelectorAll("[data-phlexui--combobox-content-target='item']:not(.hidden)").length > 0; + this.toggleVisibility([group], hasVisibleItems); + }); + } + + generateItemsIds() { + const listId = this.listTarget.getAttribute("id"); + this.itemTargets.forEach((item, index) => { + if (index === 0) item.setAttribute("aria-selected", "true"); + + item.id = `${listId}-${index}`; + }); + } + + filterItems(query) { + return this.itemTargets.filter((item) => this.sanitizeStr(item.innerText).includes(query)); + } + + toggleVisibility(elements, isVisible) { + elements.forEach((el) => el.classList.toggle("hidden", !isVisible)); + } + + sanitizeStr(str) { + return str.toLowerCase().trim(); + } + + getSelectedItemId() { + const selectedItem = this.itemTargets.find((item) => item.getAttribute("aria-selected") === "true"); + return selectedItem.getAttribute("id"); + } +} \ No newline at end of file diff --git a/lib/phlex_ui/combobox/combobox_controller.js b/lib/phlex_ui/combobox/combobox_controller.js new file mode 100644 index 0000000..31a921b --- /dev/null +++ b/lib/phlex_ui/combobox/combobox_controller.js @@ -0,0 +1,58 @@ +import { Controller } from "@hotwired/stimulus"; +import { computePosition } from "@floating-ui/dom"; +import { ITEM_SELECTED } from "./combobox_item_controller"; +import { ITEM_KEY_ESC } from "./combobox_content_controller"; + +export const POPOVER_OPENED = "phlexui--combobox#popoverOpened"; + +export default class extends Controller { + static targets = ["input", "popover", "content", "search"]; + static values = { closed: Boolean } + + connect() { + computePosition(this.inputTarget, this.popoverTarget).then(({ x, y }) => { + Object.assign(this.popoverTarget.style, { + left: `${x}px`, + top: `${y}px`, + }); + }); + + document.addEventListener(ITEM_SELECTED, (e) => this.itemSelected(e.detail), false); + document.addEventListener(ITEM_KEY_ESC, () => this.toogleContent(), false); + } + + disconnect() { + document.removeEventListener(ITEM_SELECTED, (e) => this.itemSelected(e.detail), false); + document.removeEventListener(ITEM_KEY_ESC, () => this.toogleContent(), false); + } + + onClick() { + this.toogleContent(); + } + + clickOutside(event) { + if (this.closedValue) return; + if (this.element.contains(event.target)) return; + + event.preventDefault(); + this.toogleContent(); + } + + itemSelected({ value, label }) { + this.inputTarget.value = value; + this.contentTarget.innerText = label; + this.toogleContent(); + } + + toogleContent() { + this.closedValue = !this.closedValue; + + this.popoverTarget.classList.toggle("invisible"); + this.inputTarget.setAttribute("aria-expanded", !this.closedValue); + + if (!this.closedValue) { + const event = new CustomEvent(POPOVER_OPENED, { detail: { closed: this.closedValue } }); + document.dispatchEvent(event); + } + } +} diff --git a/lib/phlex_ui/combobox/combobox_empty.rb b/lib/phlex_ui/combobox/combobox_empty.rb new file mode 100644 index 0000000..f846f3a --- /dev/null +++ b/lib/phlex_ui/combobox/combobox_empty.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module PhlexUI + class ComboboxEmpty < Base + def view_template(&) + div(**attrs, &) + end + + private + + def default_attrs + {class: "hidden py-6 text-center text-sm", role: "presentation", data: {phlexui__combobox_content_target: "empty"}} + end + end +end diff --git a/lib/phlex_ui/combobox/combobox_group.rb b/lib/phlex_ui/combobox/combobox_group.rb new file mode 100644 index 0000000..373a569 --- /dev/null +++ b/lib/phlex_ui/combobox/combobox_group.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module PhlexUI + class ComboboxGroup < Base + def initialize(heading: nil, **attrs) + @heading = heading + super(**attrs) + end + + def view_template(&block) + div(**attrs) do + render_header if @heading + render_items(&block) + end + end + + private + + def render_header + div(group_heading: @heading, class: "px-2 py-1.5 text-xs font-medium text-muted-foreground") { @heading } + end + + def render_items(&) + div(group_items: "", role: "group", &) + end + + def default_attrs + { + class: + "overflow-hidden p-1 text-foreground ", + role: "presentation", + data: { + value: @heading, + phlexui__combobox_content_target: "group" + } + } + end + end +end diff --git a/lib/phlex_ui/combobox/combobox_item.rb b/lib/phlex_ui/combobox/combobox_item.rb new file mode 100644 index 0000000..623b8cb --- /dev/null +++ b/lib/phlex_ui/combobox/combobox_item.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module PhlexUI + class ComboboxItem < Base + def initialize(value: nil, **attrs) + @value = value + super(**attrs) + end + + def view_template(&block) + div(**attrs) do + div(class: "invisible", data: {phlexui__combobox_item_target: "check"}) { icon } + block.call + end + end + + private + + def icon + svg( + xmlns: "http://www.w3.org/2000/svg", + viewbox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + class: "mr-2 h-4 w-4", + stroke_width: "2", + stroke_linecap: "round", + stroke_linejoin: "round" + ) do |s| + s.path( + d: "M20 6 9 17l-5-5" + ) + end + end + + def default_attrs + { + class: + "relative flex cursor-pointer select-none items-center gap-x-2 rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", + data: { + value: @value, + selected: false, + phlexui__combobox_content_target: "item", + controller: "phlexui--combobox-item", + action: "click->phlexui--combobox-item#selectItem mouseenter->phlexui--combobox-item#mouseenter" + }, + tabindex: "0", + role: "option" + } + end + end +end diff --git a/lib/phlex_ui/combobox/combobox_item_controller.js b/lib/phlex_ui/combobox/combobox_item_controller.js new file mode 100644 index 0000000..a4eeb13 --- /dev/null +++ b/lib/phlex_ui/combobox/combobox_item_controller.js @@ -0,0 +1,88 @@ +import { Controller } from "@hotwired/stimulus"; +import { ITEM_KEY_UP, ITEM_KEY_DOWN, ITEM_KEY_ENTER } from "./combobox_content_controller"; + +export const ITEM_SELECTED = "phlexui--combobox-item#selected"; +const ITEM_MOUSEENTER = "phlexui--combobox-item#mouseenter"; + +export default class extends Controller { + static targets = ["check"]; + + connect() { + document.addEventListener(ITEM_SELECTED, (e) => this.uncheck(e.detail), false); + document.addEventListener(ITEM_MOUSEENTER, (e) => this.unselect(e.detail), false); + document.addEventListener(ITEM_KEY_UP, (e) => this.handleKeyUp(e.detail), false); + document.addEventListener(ITEM_KEY_DOWN, (e) => this.handleKeyDown(e.detail), false); + document.addEventListener(ITEM_KEY_ENTER, (e) => this.handleKeyEnter(e.detail), false); + } + + disconnect() { + document.removeEventListener(ITEM_SELECTED, (e) => this.uncheck(e.detail), false); + document.removeEventListener(ITEM_MOUSEENTER, (e) => this.unselect(e.detail), false); + document.removeEventListener(ITEM_KEY_UP, (e) => this.handleKeyUp(e.detail), false); + document.removeEventListener(ITEM_KEY_DOWN, (e) => this.handleKeyDown(e.detail), false); + document.removeEventListener(ITEM_KEY_ENTER, (e) => this.handleKeyEnter(e.detail), false); + } + + mouseenter() { + this.element.setAttribute("aria-selected", true); + + const { value } = this.element.dataset; + const event = new CustomEvent(ITEM_MOUSEENTER, { detail: { value } }); + document.dispatchEvent(event); + } + + selectItem() { + this.checkTarget.classList.toggle("invisible", false); + + const { value } = this.element.dataset; + const label = this.element.innerText; + + const event = new CustomEvent(ITEM_SELECTED, { detail: { value, label } }); + document.dispatchEvent(event); + } + + handleKeyUp({ id }) { + const [base, idx] = id.split("-"); + + if (idx === "0") return; + + const prevIdx = parseInt(idx) - 1; + + if (this.element.id === `${base}-${prevIdx}`) { + this.element.setAttribute("aria-selected", true); + } else { + this.element.removeAttribute("aria-selected"); + } + } + + handleKeyDown({ id, length }) { + const [base, idx] = id.split("-"); + const nextIdx = parseInt(idx) + 1; + + if (nextIdx === length) return; + + if (this.element.id === `${base}-${nextIdx}`) { + this.element.setAttribute("aria-selected", true); + } else { + this.element.removeAttribute("aria-selected"); + } + } + + handleKeyEnter({ id }) { + if (this.element.id !== id) return; + + this.selectItem(); + } + + uncheck({ value }) { + if (this.element.dataset.value !== value) { + this.checkTarget.classList.toggle("invisible", true); + } + } + + unselect({ value }) { + if (this.element.dataset.value !== value) { + this.element.removeAttribute("aria-selected"); + } + } +} diff --git a/lib/phlex_ui/combobox/combobox_list.rb b/lib/phlex_ui/combobox/combobox_list.rb new file mode 100644 index 0000000..364ba9b --- /dev/null +++ b/lib/phlex_ui/combobox/combobox_list.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module PhlexUI + class ComboboxList < Base + def initialize(**attrs) + @id = "list#{SecureRandom.hex(4)}" + super + end + + def view_template(&) + div(**attrs, &) + end + + private + + def default_attrs + {id: @id, data: {phlexui__combobox_content_target: "list"}, role: "listbox", tabindex: "-1", aria_activedescendant: "#{@id}-0", class: "max-h-[300px] overflow-y-auto overflow-x-hidden"} + end + end +end diff --git a/lib/phlex_ui/combobox/combobox_search_input.rb b/lib/phlex_ui/combobox/combobox_search_input.rb new file mode 100644 index 0000000..d94ad0e --- /dev/null +++ b/lib/phlex_ui/combobox/combobox_search_input.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module PhlexUI + class ComboboxSearchInput < Base + def initialize(placeholder:, **attrs) + @placeholder = placeholder + super(**attrs) + end + + def view_template + input_container do + search_icon + input(**attrs) + end + end + + private + + def search_icon + svg( + xmlns: "http://www.w3.org/2000/svg", + viewbox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + class: "mr-2 h-4 w-4 shrink-0 opacity-50", + stroke_width: "2", + stroke_linecap: "round", + stroke_linejoin: "round" + ) do |s| + s.circle(cx: "11", cy: "11", r: "8") + s.path( + d: "m21 21-4.3-4.3" + ) + end + end + + def input_container(&) + div(class: "flex items-center border-b px-3", &) + end + + def default_attrs + { + class: + "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50", + placeholder: @placeholder, + data: { + action: "input->phlexui--combobox-content#filter", + phlexui__combobox_target: "search", + phlexui__combobox_content_target: "search" + }, + autocomplete: "off", + autocorrect: "off", + spellcheck: false + } + end + end +end diff --git a/lib/phlex_ui/combobox/combobox_separator.rb b/lib/phlex_ui/combobox/combobox_separator.rb new file mode 100644 index 0000000..6df3fac --- /dev/null +++ b/lib/phlex_ui/combobox/combobox_separator.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module PhlexUI + class ComboboxSeparator < Base + def view_template(&) + div(**attrs, &) + end + + private + + def default_attrs + {class: "-mx-1 h-px bg-border"} + end + end +end diff --git a/lib/phlex_ui/combobox/combobox_trigger.rb b/lib/phlex_ui/combobox/combobox_trigger.rb new file mode 100644 index 0000000..bae484a --- /dev/null +++ b/lib/phlex_ui/combobox/combobox_trigger.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module PhlexUI + class ComboboxTrigger < Base + def initialize(placeholder:, **attrs) + @placeholder = placeholder + super(**attrs) + end + + def view_template + button(**attrs) do + span(data: {phlexui__combobox_target: "content"}) { @placeholder } + icon + end + end + + private + + def icon + svg( + xmlns: "http://www.w3.org/2000/svg", + viewbox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + class: "ml-2 h-4 w-4 shrink-0 opacity-50", + stroke_width: "2", + stroke_linecap: "round", + stroke_linejoin: "round" + ) do |s| + s.path( + d: "m7 15 5 5 5-5" + ) + s.path( + d: "m7 9 5-5 5 5" + ) + end + end + + def default_attrs + {class: "inline-flex items-center whitespace-nowrap rounded-md text-sm ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2 w-[200px] justify-between", + data: { + action: "phlexui--combobox#onClick", + phlexui__combobox_target: "input" + }, + role: "combobox", variant: "outline", + aria: { + expanded: "false", + haspopup: "listbox", + autocomplete: "none" + }} + end + end +end diff --git a/test/phlex_ui/combobox_test.rb b/test/phlex_ui/combobox_test.rb new file mode 100644 index 0000000..6a64dde --- /dev/null +++ b/test/phlex_ui/combobox_test.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require "test_helper" + +class PhlexUI::ComboboxTest < Minitest::Test + include Phlex::Testing::ViewHelper + + def test_render_with_all_items + output = phlex_context do + PhlexUI.Combobox do + PhlexUI.ComboboxTrigger(placeholder: "Select event...", aria_controls: "list") + PhlexUI.ComboboxContent(id: "list") do + PhlexUI.ComboboxSearchInput(placeholder: "Search event...") + PhlexUI.ComboboxList do + PhlexUI.ComboboxEmpty { "No results found." } + PhlexUI.ComboboxGroup(heading: "Suggestions") do + PhlexUI.ComboboxItem(value: "railsworld") do |item| + item.span { "Rails World" } + end + PhlexUI.ComboboxItem(value: "tropicalrb") do |item| + item.span { "Tropical.rb" } + end + PhlexUI.ComboboxItem(value: "friendly.rb") do |item| + item.span { "Friendly.rb" } + end + end + + PhlexUI.ComboboxSeparator() + + PhlexUI.ComboboxGroup(heading: "Others") do + PhlexUI.ComboboxItem(value: "railsconf") do |item| + item.span { "RailsConf" } + end + PhlexUI.ComboboxItem(value: "euruko") do |item| + item.span { "Euruko" } + end + PhlexUI.ComboboxItem(value: "rubykaigi") do |item| + item.span { "RubyKaigi" } + end + end + end + end + end + end + + assert_match(/Tropical.rb/, output) + end +end From e25b42883463c588626fab4f3f390d3b0e5edf9e Mon Sep 17 00:00:00 2001 From: Cirdes Henrique Date: Sun, 21 Jul 2024 11:15:47 -0300 Subject: [PATCH 2/9] fix rezise position --- lib/phlex_ui/combobox/combobox_controller.js | 26 ++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/lib/phlex_ui/combobox/combobox_controller.js b/lib/phlex_ui/combobox/combobox_controller.js index 31a921b..1eda38b 100644 --- a/lib/phlex_ui/combobox/combobox_controller.js +++ b/lib/phlex_ui/combobox/combobox_controller.js @@ -1,5 +1,5 @@ import { Controller } from "@hotwired/stimulus"; -import { computePosition } from "@floating-ui/dom"; +import { computePosition, autoUpdate } from "@floating-ui/dom"; import { ITEM_SELECTED } from "./combobox_item_controller"; import { ITEM_KEY_ESC } from "./combobox_content_controller"; @@ -9,13 +9,13 @@ export default class extends Controller { static targets = ["input", "popover", "content", "search"]; static values = { closed: Boolean } + constructor(...args) { + super(...args); + this.cleanup; + } + connect() { - computePosition(this.inputTarget, this.popoverTarget).then(({ x, y }) => { - Object.assign(this.popoverTarget.style, { - left: `${x}px`, - top: `${y}px`, - }); - }); + this.setFloatingElement(); document.addEventListener(ITEM_SELECTED, (e) => this.itemSelected(e.detail), false); document.addEventListener(ITEM_KEY_ESC, () => this.toogleContent(), false); @@ -24,6 +24,7 @@ export default class extends Controller { disconnect() { document.removeEventListener(ITEM_SELECTED, (e) => this.itemSelected(e.detail), false); document.removeEventListener(ITEM_KEY_ESC, () => this.toogleContent(), false); + this.cleanup(); } onClick() { @@ -55,4 +56,15 @@ export default class extends Controller { document.dispatchEvent(event); } } + + setFloatingElement() { + this.cleanup = autoUpdate(this.inputTarget, this.popoverTarget, () => { + computePosition(this.inputTarget, this.popoverTarget).then(({ x, y }) => { + Object.assign(this.popoverTarget.style, { + left: `${x}px`, + top: `${y}px`, + }); + }); + }); + } } From 685f7b6a9568f665e5218755c926f06c0d602943 Mon Sep 17 00:00:00 2001 From: Cirdes Henrique Date: Sun, 21 Jul 2024 11:58:33 -0300 Subject: [PATCH 3/9] fix combobox autoload --- lib/phlex_ui.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/phlex_ui.rb b/lib/phlex_ui.rb index f0eff36..49d60cf 100644 --- a/lib/phlex_ui.rb +++ b/lib/phlex_ui.rb @@ -65,7 +65,7 @@ module PhlexUI autoload :Clipboard, "phlex_ui/clipboard" autoload :Codeblock, "phlex_ui/codeblock" autoload :Collapsible, "phlex_ui/collapsible" - autoload :Command, "phlex_ui/combobox" + autoload :Combobox, "phlex_ui/combobox" autoload :Command, "phlex_ui/command" autoload :ContextMenu, "phlex_ui/context_menu" autoload :Dialog, "phlex_ui/dialog" From d770c6372eeb90f3e8ffdf89f01427ea117a451f Mon Sep 17 00:00:00 2001 From: Cirdes Henrique Date: Sun, 21 Jul 2024 12:41:21 -0300 Subject: [PATCH 4/9] fix gemspec lock --- Gemfile.lock | 8 ++++---- phlex_ui.gemspec | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 42cad97..2c4176b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,9 +3,9 @@ PATH specs: phlex_ui (0.1.10) activesupport (>= 6.0) - phlex (~> 1.11) - rouge (~> 4.2.0) - zeitwerk (~> 2.6) + phlex (>= 1.11) + rouge (>= 4.2.0) + zeitwerk (>= 2.6) GEM remote: https://rubygems.org/ @@ -82,4 +82,4 @@ DEPENDENCIES standard BUNDLED WITH - 2.5.10 + 2.3.25 diff --git a/phlex_ui.gemspec b/phlex_ui.gemspec index 5730fce..b107211 100644 --- a/phlex_ui.gemspec +++ b/phlex_ui.gemspec @@ -12,9 +12,9 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 3.2" - s.add_dependency "phlex", "~> 1.11" - s.add_dependency "rouge", "~> 4.2.0" - s.add_dependency "zeitwerk", "~> 2.6" + s.add_dependency "phlex", ">= 1.11" + s.add_dependency "rouge", ">= 4.2.0" + s.add_dependency "zeitwerk", ">= 2.6" s.add_dependency "activesupport", ">= 6.0" s.add_development_dependency "rake" From d5e2e7232458e373fcf0c403c4b3dcd005d5c892 Mon Sep 17 00:00:00 2001 From: Cirdes Date: Tue, 23 Jul 2024 14:57:02 +0000 Subject: [PATCH 5/9] revert phlex version to 1.10 --- Gemfile.lock | 6 +++--- phlex_ui.gemspec | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 2c4176b..79863af 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,9 +3,9 @@ PATH specs: phlex_ui (0.1.10) activesupport (>= 6.0) - phlex (>= 1.11) - rouge (>= 4.2.0) - zeitwerk (>= 2.6) + phlex (~> 1.10) + rouge (~> 4.2.0) + zeitwerk (~> 2.6) GEM remote: https://rubygems.org/ diff --git a/phlex_ui.gemspec b/phlex_ui.gemspec index b107211..979a51e 100644 --- a/phlex_ui.gemspec +++ b/phlex_ui.gemspec @@ -12,9 +12,9 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 3.2" - s.add_dependency "phlex", ">= 1.11" - s.add_dependency "rouge", ">= 4.2.0" - s.add_dependency "zeitwerk", ">= 2.6" + s.add_dependency "phlex", "~> 1.10" + s.add_dependency "rouge", "~> 4.2.0" + s.add_dependency "zeitwerk", "~> 2.6" s.add_dependency "activesupport", ">= 6.0" s.add_development_dependency "rake" From c0d581158aa1eb74704560cbaaf0730117fd7a89 Mon Sep 17 00:00:00 2001 From: Cirdes Date: Mon, 29 Jul 2024 20:54:31 +0000 Subject: [PATCH 6/9] move combobox.rb to combobox/combobox.rb --- lib/phlex_ui/{ => combobox}/combobox.rb | 0 lib/phlex_ui/combobox/combobox_content_controller.js | 9 +++++---- lib/phlex_ui/combobox/combobox_controller.js | 5 +++-- lib/phlex_ui/combobox/combobox_item_controller.js | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) rename lib/phlex_ui/{ => combobox}/combobox.rb (100%) diff --git a/lib/phlex_ui/combobox.rb b/lib/phlex_ui/combobox/combobox.rb similarity index 100% rename from lib/phlex_ui/combobox.rb rename to lib/phlex_ui/combobox/combobox.rb diff --git a/lib/phlex_ui/combobox/combobox_content_controller.js b/lib/phlex_ui/combobox/combobox_content_controller.js index 5b0c13d..a642d6e 100644 --- a/lib/phlex_ui/combobox/combobox_content_controller.js +++ b/lib/phlex_ui/combobox/combobox_content_controller.js @@ -1,6 +1,6 @@ import { Controller } from "@hotwired/stimulus"; -import { POPOVER_OPENED } from "./combobox_controller"; +const POPOVER_OPENED = "phlexui--combobox#popoverOpened"; export const ITEM_KEY_UP = "phlexui--combobox-content#keyUp"; export const ITEM_KEY_DOWN = "phlexui--combobox-content#keyDown"; @@ -39,7 +39,7 @@ export default class extends Controller { handleKeyDown() { const id = this.getSelectedItemId(); - const length = this.itemTargets.length; + const { length } = this.itemTargets; const event = new CustomEvent(ITEM_KEY_DOWN, { detail: { id, length } }); document.dispatchEvent(event); @@ -71,7 +71,8 @@ export default class extends Controller { updateGroupVisibility() { this.groupTargets.forEach((group) => { - const hasVisibleItems = group.querySelectorAll("[data-phlexui--combobox-content-target='item']:not(.hidden)").length > 0; + const hasVisibleItems = + group.querySelectorAll("[data-phlexui--combobox-content-target='item']:not(.hidden)").length > 0; this.toggleVisibility([group], hasVisibleItems); }); } @@ -101,4 +102,4 @@ export default class extends Controller { const selectedItem = this.itemTargets.find((item) => item.getAttribute("aria-selected") === "true"); return selectedItem.getAttribute("id"); } -} \ No newline at end of file +} diff --git a/lib/phlex_ui/combobox/combobox_controller.js b/lib/phlex_ui/combobox/combobox_controller.js index 1eda38b..37f22f2 100644 --- a/lib/phlex_ui/combobox/combobox_controller.js +++ b/lib/phlex_ui/combobox/combobox_controller.js @@ -7,11 +7,12 @@ export const POPOVER_OPENED = "phlexui--combobox#popoverOpened"; export default class extends Controller { static targets = ["input", "popover", "content", "search"]; - static values = { closed: Boolean } + + static values = { closed: Boolean }; constructor(...args) { super(...args); - this.cleanup; + this.cleanup = undefined; } connect() { diff --git a/lib/phlex_ui/combobox/combobox_item_controller.js b/lib/phlex_ui/combobox/combobox_item_controller.js index a4eeb13..88c1a8d 100644 --- a/lib/phlex_ui/combobox/combobox_item_controller.js +++ b/lib/phlex_ui/combobox/combobox_item_controller.js @@ -46,7 +46,7 @@ export default class extends Controller { if (idx === "0") return; - const prevIdx = parseInt(idx) - 1; + const prevIdx = parseInt(idx, 10) - 1; if (this.element.id === `${base}-${prevIdx}`) { this.element.setAttribute("aria-selected", true); @@ -57,7 +57,7 @@ export default class extends Controller { handleKeyDown({ id, length }) { const [base, idx] = id.split("-"); - const nextIdx = parseInt(idx) + 1; + const nextIdx = parseInt(idx, 10) + 1; if (nextIdx === length) return; From fcba794bf506441489a3894331f9167de719ebee Mon Sep 17 00:00:00 2001 From: Cirdes Date: Mon, 29 Jul 2024 20:59:14 +0000 Subject: [PATCH 7/9] fix autoload error --- lib/phlex_ui.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/phlex_ui.rb b/lib/phlex_ui.rb index 9d8a0c6..36ac79d 100644 --- a/lib/phlex_ui.rb +++ b/lib/phlex_ui.rb @@ -62,7 +62,6 @@ module PhlexUI autoload :Clipboard, "phlex_ui/clipboard" autoload :Codeblock, "phlex_ui/codeblock" autoload :Collapsible, "phlex_ui/collapsible" - autoload :Combobox, "phlex_ui/combobox" autoload :Command, "phlex_ui/command" autoload :ContextMenu, "phlex_ui/context_menu" autoload :Dialog, "phlex_ui/dialog" From cfdc1c865cba03e393a73076523c01638e52e2af Mon Sep 17 00:00:00 2001 From: Cirdes Date: Wed, 31 Jul 2024 21:11:14 +0000 Subject: [PATCH 8/9] Moving to RBUI namespace --- lib/phlex_ui.rb | 13 +++- lib/rbui.rb | 17 +++++ lib/rbui/attribute_merger.rb | 64 +++++++++++++++++++ lib/rbui/base.rb | 27 ++++++++ lib/{phlex_ui => rbui}/combobox/combobox.rb | 4 +- .../combobox/combobox_content.rb | 6 +- .../combobox/combobox_content_controller.js | 12 ++-- .../combobox/combobox_controller.js | 2 +- .../combobox/combobox_empty.rb | 4 +- .../combobox/combobox_group.rb | 4 +- .../combobox/combobox_item.rb | 10 +-- .../combobox/combobox_item_controller.js | 4 +- .../combobox/combobox_list.rb | 4 +- .../combobox/combobox_search_input.rb | 8 +-- .../combobox/combobox_separator.rb | 2 +- .../combobox/combobox_trigger.rb | 8 +-- test/phlex_ui/combobox_test.rb | 48 -------------- test/rbui/combobox_test.rb | 48 ++++++++++++++ test/test_helper.rb | 1 + 19 files changed, 201 insertions(+), 85 deletions(-) create mode 100644 lib/rbui.rb create mode 100644 lib/rbui/attribute_merger.rb create mode 100644 lib/rbui/base.rb rename lib/{phlex_ui => rbui}/combobox/combobox.rb (50%) rename lib/{phlex_ui => rbui}/combobox/combobox_content.rb (54%) rename lib/{phlex_ui => rbui}/combobox/combobox_content_controller.js (86%) rename lib/{phlex_ui => rbui}/combobox/combobox_controller.js (96%) rename lib/{phlex_ui => rbui}/combobox/combobox_empty.rb (76%) rename lib/{phlex_ui => rbui}/combobox/combobox_group.rb (91%) rename lib/{phlex_ui => rbui}/combobox/combobox_item.rb (78%) rename lib/{phlex_ui => rbui}/combobox/combobox_item_controller.js (95%) rename lib/{phlex_ui => rbui}/combobox/combobox_list.rb (55%) rename lib/{phlex_ui => rbui}/combobox/combobox_search_input.rb (87%) rename lib/{phlex_ui => rbui}/combobox/combobox_separator.rb (93%) rename lib/{phlex_ui => rbui}/combobox/combobox_trigger.rb (87%) delete mode 100644 test/phlex_ui/combobox_test.rb create mode 100644 test/rbui/combobox_test.rb diff --git a/lib/phlex_ui.rb b/lib/phlex_ui.rb index 36ac79d..3857e3a 100644 --- a/lib/phlex_ui.rb +++ b/lib/phlex_ui.rb @@ -2,9 +2,14 @@ require "phlex" require "zeitwerk" -loader = Zeitwerk::Loader.for_gem +# WARNING: Zeitwerk defines the constant RBUI after the directory +# loader = Zeitwerk::Loader.for_gem +# temporarily disable the constant to avoid errors with dual module definition +loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false) + loader.inflector.inflect( - "phlex_ui" => "PhlexUI" + "phlex_ui" => "PhlexUI", + "rbui" => "RBUI" ) loader.collapse("#{__dir__}/phlex_ui/accordion") @@ -16,7 +21,6 @@ loader.collapse("#{__dir__}/phlex_ui/button") loader.collapse("#{__dir__}/phlex_ui/calendar") loader.collapse("#{__dir__}/phlex_ui/card") -loader.collapse("#{__dir__}/phlex_ui/combobox") loader.collapse("#{__dir__}/phlex_ui/chart") loader.collapse("#{__dir__}/phlex_ui/checkbox") loader.collapse("#{__dir__}/phlex_ui/clipboard") @@ -43,6 +47,9 @@ loader.collapse("#{__dir__}/phlex_ui/tooltip") loader.collapse("#{__dir__}/phlex_ui/typography") +# RBUI +loader.collapse("#{__dir__}/rbui/combobox") + loader.setup # ready! module PhlexUI diff --git a/lib/rbui.rb b/lib/rbui.rb new file mode 100644 index 0000000..cfe8560 --- /dev/null +++ b/lib/rbui.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# loader = Zeitwerk::Loader.for_gem + +# loader.inflector.inflect( +# "rbui" => "RBUI" +# ) + +# loader.collapse("#{__dir__}/rbui/combobox") + +# loader.setup # ready! + +module RBUI + extend Phlex::Kit +end + +# loader.eager_load diff --git a/lib/rbui/attribute_merger.rb b/lib/rbui/attribute_merger.rb new file mode 100644 index 0000000..b263a32 --- /dev/null +++ b/lib/rbui/attribute_merger.rb @@ -0,0 +1,64 @@ +module RBUI + class AttributeMerger + attr_reader :default_attrs, :user_attrs + + def initialize(default_attrs, user_attrs) + @default_attrs = flatten_hash(default_attrs) + @user_attrs = flatten_hash(user_attrs) + end + + # @return [String] + # any key that ends with ! will override the default value + # ex: if default_attrs = { class: "text-right" }, user_attrs = { class!: "text-left" } + # the result will be { class: "text-left } + def call + merged_attrs = merge_hashes(default_attrs, user_attrs) + mix(merged_attrs, user_attrs) + end + + private + + # @return [Hash] + def mix(*args) + args.each_with_object({}) do |object, result| + result.merge!(object) do |_key, old, new| + case new + when Hash + old.is_a?(Hash) ? mix(old, new) : new + when Array + old.is_a?(Array) ? (old + new) : new + when String + old.is_a?(String) ? "#{old} #{new}" : new + else + new + end + end + + result.transform_keys! do |key| + key.end_with?("!") ? key.name.chop.to_sym : key + end + end + end + + def flatten_hash(hash, parent_key = "", result_hash = {}) + hash.each do |key, value| + new_key = parent_key.empty? ? key : :"#{parent_key}_#{key}" + if value.is_a? Hash + flatten_hash(value, new_key, result_hash) + else + result_hash[new_key] = value + end + end + result_hash + end + + def merge_hashes(hash1, hash2) + flat_hash1 = flatten_hash(hash1) + flat_hash2 = flatten_hash(hash2) + + flat_hash1.merge(flat_hash2) do |key, oldval, newval| + "#{oldval} #{newval}" + end + end + end +end diff --git a/lib/rbui/base.rb b/lib/rbui/base.rb new file mode 100644 index 0000000..20cfcfb --- /dev/null +++ b/lib/rbui/base.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "tailwind_merge" + +module RBUI + class Base < Phlex::HTML + attr_reader :attrs + + def initialize(**user_attrs) + @attrs = AttributeMerger.new(default_attrs, user_attrs).call + @attrs[:class] = ::TailwindMerge::Merger.new.merge(@attrs[:class]) if @attrs[:class] + end + + if defined?(Rails) && Rails.env.development? + def before_template + comment { "Before #{self.class.name}" } + super + end + end + + private + + def default_attrs + {} + end + end +end diff --git a/lib/phlex_ui/combobox/combobox.rb b/lib/rbui/combobox/combobox.rb similarity index 50% rename from lib/phlex_ui/combobox/combobox.rb rename to lib/rbui/combobox/combobox.rb index 1bd2b25..65b3b1e 100644 --- a/lib/phlex_ui/combobox/combobox.rb +++ b/lib/rbui/combobox/combobox.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module PhlexUI +module RBUI class Combobox < Base def view_template(&) div(**attrs, &) @@ -9,7 +9,7 @@ def view_template(&) private def default_attrs - {data: {controller: "phlexui--combobox", action: "click@window->phlexui--combobox#clickOutside", phlexui__combobox_closed_value: "true"}} + {data: {controller: "rbui--combobox", action: "click@window->rbui--combobox#clickOutside", rbui__combobox_closed_value: "true"}} end end end diff --git a/lib/phlex_ui/combobox/combobox_content.rb b/lib/rbui/combobox/combobox_content.rb similarity index 54% rename from lib/phlex_ui/combobox/combobox_content.rb rename to lib/rbui/combobox/combobox_content.rb index 030fd7c..a4fd27c 100644 --- a/lib/phlex_ui/combobox/combobox_content.rb +++ b/lib/rbui/combobox/combobox_content.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -module PhlexUI +module RBUI class ComboboxContent < Base def view_template(&) div(**attrs) do div( - data: {controller: "phlexui--combobox-content", action: "keydown.up->phlexui--combobox-content#handleKeyUp keydown.down->phlexui--combobox-content#handleKeyDown keydown.enter->phlexui--combobox-content#handleKeyEnter keydown.esc->phlexui--combobox-content#handleKeyEsc"}, + data: {controller: "rbui--combobox-content", action: "keydown.up->rbui--combobox-content#handleKeyUp keydown.down->rbui--combobox-content#handleKeyDown keydown.enter->rbui--combobox-content#handleKeyEnter keydown.esc->rbui--combobox-content#handleKeyEsc"}, class: "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground rounded-lg border shadow-md", & ) end @@ -15,7 +15,7 @@ def view_template(&) def default_attrs { - data: {phlexui__combobox_target: "popover"}, + data: {rbui__combobox_target: "popover"}, class: "invisible absolute top-0 left-0 p-1.5 rounded" } end diff --git a/lib/phlex_ui/combobox/combobox_content_controller.js b/lib/rbui/combobox/combobox_content_controller.js similarity index 86% rename from lib/phlex_ui/combobox/combobox_content_controller.js rename to lib/rbui/combobox/combobox_content_controller.js index a642d6e..0394bff 100644 --- a/lib/phlex_ui/combobox/combobox_content_controller.js +++ b/lib/rbui/combobox/combobox_content_controller.js @@ -1,11 +1,11 @@ import { Controller } from "@hotwired/stimulus"; -const POPOVER_OPENED = "phlexui--combobox#popoverOpened"; +const POPOVER_OPENED = "rbui--combobox#popoverOpened"; -export const ITEM_KEY_UP = "phlexui--combobox-content#keyUp"; -export const ITEM_KEY_DOWN = "phlexui--combobox-content#keyDown"; -export const ITEM_KEY_ENTER = "phlexui--combobox-content#keyEnter"; -export const ITEM_KEY_ESC = "phlexui--combobox-content#keyEsc"; +export const ITEM_KEY_UP = "rbui--combobox-content#keyUp"; +export const ITEM_KEY_DOWN = "rbui--combobox-content#keyDown"; +export const ITEM_KEY_ENTER = "rbui--combobox-content#keyEnter"; +export const ITEM_KEY_ESC = "rbui--combobox-content#keyEsc"; export default class extends Controller { static targets = ["list", "item", "empty", "group", "search"]; @@ -72,7 +72,7 @@ export default class extends Controller { updateGroupVisibility() { this.groupTargets.forEach((group) => { const hasVisibleItems = - group.querySelectorAll("[data-phlexui--combobox-content-target='item']:not(.hidden)").length > 0; + group.querySelectorAll("[data-rbui--combobox-content-target='item']:not(.hidden)").length > 0; this.toggleVisibility([group], hasVisibleItems); }); } diff --git a/lib/phlex_ui/combobox/combobox_controller.js b/lib/rbui/combobox/combobox_controller.js similarity index 96% rename from lib/phlex_ui/combobox/combobox_controller.js rename to lib/rbui/combobox/combobox_controller.js index 37f22f2..49d9b4e 100644 --- a/lib/phlex_ui/combobox/combobox_controller.js +++ b/lib/rbui/combobox/combobox_controller.js @@ -3,7 +3,7 @@ import { computePosition, autoUpdate } from "@floating-ui/dom"; import { ITEM_SELECTED } from "./combobox_item_controller"; import { ITEM_KEY_ESC } from "./combobox_content_controller"; -export const POPOVER_OPENED = "phlexui--combobox#popoverOpened"; +export const POPOVER_OPENED = "rbui--combobox#popoverOpened"; export default class extends Controller { static targets = ["input", "popover", "content", "search"]; diff --git a/lib/phlex_ui/combobox/combobox_empty.rb b/lib/rbui/combobox/combobox_empty.rb similarity index 76% rename from lib/phlex_ui/combobox/combobox_empty.rb rename to lib/rbui/combobox/combobox_empty.rb index f846f3a..3d55e55 100644 --- a/lib/phlex_ui/combobox/combobox_empty.rb +++ b/lib/rbui/combobox/combobox_empty.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module PhlexUI +module RBUI class ComboboxEmpty < Base def view_template(&) div(**attrs, &) @@ -9,7 +9,7 @@ def view_template(&) private def default_attrs - {class: "hidden py-6 text-center text-sm", role: "presentation", data: {phlexui__combobox_content_target: "empty"}} + {class: "hidden py-6 text-center text-sm", role: "presentation", data: {rbui__combobox_content_target: "empty"}} end end end diff --git a/lib/phlex_ui/combobox/combobox_group.rb b/lib/rbui/combobox/combobox_group.rb similarity index 91% rename from lib/phlex_ui/combobox/combobox_group.rb rename to lib/rbui/combobox/combobox_group.rb index 373a569..70c17f3 100644 --- a/lib/phlex_ui/combobox/combobox_group.rb +++ b/lib/rbui/combobox/combobox_group.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module PhlexUI +module RBUI class ComboboxGroup < Base def initialize(heading: nil, **attrs) @heading = heading @@ -31,7 +31,7 @@ def default_attrs role: "presentation", data: { value: @heading, - phlexui__combobox_content_target: "group" + rbui__combobox_content_target: "group" } } end diff --git a/lib/phlex_ui/combobox/combobox_item.rb b/lib/rbui/combobox/combobox_item.rb similarity index 78% rename from lib/phlex_ui/combobox/combobox_item.rb rename to lib/rbui/combobox/combobox_item.rb index 623b8cb..21f8a1d 100644 --- a/lib/phlex_ui/combobox/combobox_item.rb +++ b/lib/rbui/combobox/combobox_item.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module PhlexUI +module RBUI class ComboboxItem < Base def initialize(value: nil, **attrs) @value = value @@ -9,7 +9,7 @@ def initialize(value: nil, **attrs) def view_template(&block) div(**attrs) do - div(class: "invisible", data: {phlexui__combobox_item_target: "check"}) { icon } + div(class: "invisible", data: {rbui__combobox_item_target: "check"}) { icon } block.call end end @@ -40,9 +40,9 @@ def default_attrs data: { value: @value, selected: false, - phlexui__combobox_content_target: "item", - controller: "phlexui--combobox-item", - action: "click->phlexui--combobox-item#selectItem mouseenter->phlexui--combobox-item#mouseenter" + rbui__combobox_content_target: "item", + controller: "rbui--combobox-item", + action: "click->rbui--combobox-item#selectItem mouseenter->rbui--combobox-item#mouseenter" }, tabindex: "0", role: "option" diff --git a/lib/phlex_ui/combobox/combobox_item_controller.js b/lib/rbui/combobox/combobox_item_controller.js similarity index 95% rename from lib/phlex_ui/combobox/combobox_item_controller.js rename to lib/rbui/combobox/combobox_item_controller.js index 88c1a8d..496b8c6 100644 --- a/lib/phlex_ui/combobox/combobox_item_controller.js +++ b/lib/rbui/combobox/combobox_item_controller.js @@ -1,8 +1,8 @@ import { Controller } from "@hotwired/stimulus"; import { ITEM_KEY_UP, ITEM_KEY_DOWN, ITEM_KEY_ENTER } from "./combobox_content_controller"; -export const ITEM_SELECTED = "phlexui--combobox-item#selected"; -const ITEM_MOUSEENTER = "phlexui--combobox-item#mouseenter"; +export const ITEM_SELECTED = "rbui--combobox-item#selected"; +const ITEM_MOUSEENTER = "rbui--combobox-item#mouseenter"; export default class extends Controller { static targets = ["check"]; diff --git a/lib/phlex_ui/combobox/combobox_list.rb b/lib/rbui/combobox/combobox_list.rb similarity index 55% rename from lib/phlex_ui/combobox/combobox_list.rb rename to lib/rbui/combobox/combobox_list.rb index 364ba9b..da99e5b 100644 --- a/lib/phlex_ui/combobox/combobox_list.rb +++ b/lib/rbui/combobox/combobox_list.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module PhlexUI +module RBUI class ComboboxList < Base def initialize(**attrs) @id = "list#{SecureRandom.hex(4)}" @@ -14,7 +14,7 @@ def view_template(&) private def default_attrs - {id: @id, data: {phlexui__combobox_content_target: "list"}, role: "listbox", tabindex: "-1", aria_activedescendant: "#{@id}-0", class: "max-h-[300px] overflow-y-auto overflow-x-hidden"} + {id: @id, data: {rbui__combobox_content_target: "list"}, role: "listbox", tabindex: "-1", aria_activedescendant: "#{@id}-0", class: "max-h-[300px] overflow-y-auto overflow-x-hidden"} end end end diff --git a/lib/phlex_ui/combobox/combobox_search_input.rb b/lib/rbui/combobox/combobox_search_input.rb similarity index 87% rename from lib/phlex_ui/combobox/combobox_search_input.rb rename to lib/rbui/combobox/combobox_search_input.rb index d94ad0e..db385e8 100644 --- a/lib/phlex_ui/combobox/combobox_search_input.rb +++ b/lib/rbui/combobox/combobox_search_input.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module PhlexUI +module RBUI class ComboboxSearchInput < Base def initialize(placeholder:, **attrs) @placeholder = placeholder @@ -44,9 +44,9 @@ def default_attrs "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50", placeholder: @placeholder, data: { - action: "input->phlexui--combobox-content#filter", - phlexui__combobox_target: "search", - phlexui__combobox_content_target: "search" + action: "input->rbui--combobox-content#filter", + rbui__combobox_target: "search", + rbui__combobox_content_target: "search" }, autocomplete: "off", autocorrect: "off", diff --git a/lib/phlex_ui/combobox/combobox_separator.rb b/lib/rbui/combobox/combobox_separator.rb similarity index 93% rename from lib/phlex_ui/combobox/combobox_separator.rb rename to lib/rbui/combobox/combobox_separator.rb index 6df3fac..3f14366 100644 --- a/lib/phlex_ui/combobox/combobox_separator.rb +++ b/lib/rbui/combobox/combobox_separator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module PhlexUI +module RBUI class ComboboxSeparator < Base def view_template(&) div(**attrs, &) diff --git a/lib/phlex_ui/combobox/combobox_trigger.rb b/lib/rbui/combobox/combobox_trigger.rb similarity index 87% rename from lib/phlex_ui/combobox/combobox_trigger.rb rename to lib/rbui/combobox/combobox_trigger.rb index bae484a..98f20f8 100644 --- a/lib/phlex_ui/combobox/combobox_trigger.rb +++ b/lib/rbui/combobox/combobox_trigger.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module PhlexUI +module RBUI class ComboboxTrigger < Base def initialize(placeholder:, **attrs) @placeholder = placeholder @@ -9,7 +9,7 @@ def initialize(placeholder:, **attrs) def view_template button(**attrs) do - span(data: {phlexui__combobox_target: "content"}) { @placeholder } + span(data: {rbui__combobox_target: "content"}) { @placeholder } icon end end @@ -39,8 +39,8 @@ def icon def default_attrs {class: "inline-flex items-center whitespace-nowrap rounded-md text-sm ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2 w-[200px] justify-between", data: { - action: "phlexui--combobox#onClick", - phlexui__combobox_target: "input" + action: "rbui--combobox#onClick", + rbui__combobox_target: "input" }, role: "combobox", variant: "outline", aria: { diff --git a/test/phlex_ui/combobox_test.rb b/test/phlex_ui/combobox_test.rb deleted file mode 100644 index 6a64dde..0000000 --- a/test/phlex_ui/combobox_test.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -class PhlexUI::ComboboxTest < Minitest::Test - include Phlex::Testing::ViewHelper - - def test_render_with_all_items - output = phlex_context do - PhlexUI.Combobox do - PhlexUI.ComboboxTrigger(placeholder: "Select event...", aria_controls: "list") - PhlexUI.ComboboxContent(id: "list") do - PhlexUI.ComboboxSearchInput(placeholder: "Search event...") - PhlexUI.ComboboxList do - PhlexUI.ComboboxEmpty { "No results found." } - PhlexUI.ComboboxGroup(heading: "Suggestions") do - PhlexUI.ComboboxItem(value: "railsworld") do |item| - item.span { "Rails World" } - end - PhlexUI.ComboboxItem(value: "tropicalrb") do |item| - item.span { "Tropical.rb" } - end - PhlexUI.ComboboxItem(value: "friendly.rb") do |item| - item.span { "Friendly.rb" } - end - end - - PhlexUI.ComboboxSeparator() - - PhlexUI.ComboboxGroup(heading: "Others") do - PhlexUI.ComboboxItem(value: "railsconf") do |item| - item.span { "RailsConf" } - end - PhlexUI.ComboboxItem(value: "euruko") do |item| - item.span { "Euruko" } - end - PhlexUI.ComboboxItem(value: "rubykaigi") do |item| - item.span { "RubyKaigi" } - end - end - end - end - end - end - - assert_match(/Tropical.rb/, output) - end -end diff --git a/test/rbui/combobox_test.rb b/test/rbui/combobox_test.rb new file mode 100644 index 0000000..9c5e607 --- /dev/null +++ b/test/rbui/combobox_test.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require "test_helper" + +class RBUI::ComboboxTest < Minitest::Test + include Phlex::Testing::ViewHelper + + def test_render_with_all_items + output = phlex_context do + RBUI.Combobox do + RBUI.ComboboxTrigger(placeholder: "Select event...", aria_controls: "list") + RBUI.ComboboxContent(id: "list") do + RBUI.ComboboxSearchInput(placeholder: "Search event...") + RBUI.ComboboxList do + RBUI.ComboboxEmpty { "No results found." } + RBUI.ComboboxGroup(heading: "Suggestions") do + RBUI.ComboboxItem(value: "railsworld") do |item| + item.span { "Rails World" } + end + RBUI.ComboboxItem(value: "tropicalrb") do |item| + item.span { "Tropical.rb" } + end + RBUI.ComboboxItem(value: "friendly.rb") do |item| + item.span { "Friendly.rb" } + end + end + + RBUI.ComboboxSeparator() + + RBUI.ComboboxGroup(heading: "Others") do + RBUI.ComboboxItem(value: "railsconf") do |item| + item.span { "RailsConf" } + end + RBUI.ComboboxItem(value: "euruko") do |item| + item.span { "Euruko" } + end + RBUI.ComboboxItem(value: "rubykaigi") do |item| + item.span { "RubyKaigi" } + end + end + end + end + end + end + + assert_match(/Tropical.rb/, output) + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index e4a04c3..8d8a7c7 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -2,6 +2,7 @@ $LOAD_PATH.unshift File.expand_path("../lib", __dir__) require "phlex_ui" +require "rbui" require "phlex/testing/view_helper" require "minitest/autorun" From d7e7cce779494c14ce5cd99376c80d9027fcd0de Mon Sep 17 00:00:00 2001 From: Cirdes Date: Wed, 31 Jul 2024 21:13:39 +0000 Subject: [PATCH 9/9] remove comments from rbui.rb --- lib/rbui.rb | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/lib/rbui.rb b/lib/rbui.rb index cfe8560..658862c 100644 --- a/lib/rbui.rb +++ b/lib/rbui.rb @@ -1,17 +1,5 @@ # frozen_string_literal: true -# loader = Zeitwerk::Loader.for_gem - -# loader.inflector.inflect( -# "rbui" => "RBUI" -# ) - -# loader.collapse("#{__dir__}/rbui/combobox") - -# loader.setup # ready! - module RBUI extend Phlex::Kit end - -# loader.eager_load