diff --git a/playbook-website/config/menu.yml b/playbook-website/config/menu.yml index 0ca7893414..02be599f6f 100644 --- a/playbook-website/config/menu.yml +++ b/playbook-website/config/menu.yml @@ -425,7 +425,7 @@ kits: platforms: *1 status: stable - name: draggable - platforms: *2 + platforms: *1 description: status: stable - category: message_text_patterns diff --git a/playbook/app/pb_kits/playbook/pb_draggable/docs/_draggable_default.html.erb b/playbook/app/pb_kits/playbook/pb_draggable/docs/_draggable_default.html.erb new file mode 100644 index 0000000000..1cbd5f0fdc --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_draggable/docs/_draggable_default.html.erb @@ -0,0 +1,28 @@ +<% + items = [ + { + id: "21", + url: "https://unsplash.it/500/400/?image=633", + }, + { + id: "22", + url: "https://unsplash.it/500/400/?image=634", + }, + { + id: "23", + url: "https://unsplash.it/500/400/?image=637", + }, + ] +%> + +<%= pb_rails("draggable", props: { initial_items: items }) do %> + <%= pb_rails("draggable/draggable_container") do %> + <%= pb_rails("flex") do %> + <% items.each do |item| %> + <%= pb_rails("draggable/draggable_item", props: { drag_id: item[:id], container: "carlos" }) do %> + <%= pb_rails("image", props: { alt: item[:id], margin: "xs", size: "md", url: item[:url] }) %> + <% end %> + <% end %> + <% end %> + <% end %> +<% end %> \ No newline at end of file diff --git a/playbook/app/pb_kits/playbook/pb_draggable/docs/example.yml b/playbook/app/pb_kits/playbook/pb_draggable/docs/example.yml index 141356a735..0d398590a6 100644 --- a/playbook/app/pb_kits/playbook/pb_draggable/docs/example.yml +++ b/playbook/app/pb_kits/playbook/pb_draggable/docs/example.yml @@ -1,5 +1,6 @@ examples: - + rails: + - draggable_default: Default react: - draggable_default: Default @@ -7,5 +8,3 @@ examples: - draggable_with_selectable_list: Draggable with SelectableList Kit - draggable_with_cards: Draggable with Cards - draggable_multiple_containers: Dragging Across Multiple Containers - - diff --git a/playbook/app/pb_kits/playbook/pb_draggable/draggable.html.erb b/playbook/app/pb_kits/playbook/pb_draggable/draggable.html.erb new file mode 100644 index 0000000000..0d83c489c8 --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_draggable/draggable.html.erb @@ -0,0 +1,8 @@ +<%= content_tag(:div, + aria: object.aria, + class: object.classname, + data: object.data, + id: object.id, + **combined_html_options) do %> + <%= content %> +<% end %> diff --git a/playbook/app/pb_kits/playbook/pb_draggable/draggable.rb b/playbook/app/pb_kits/playbook/pb_draggable/draggable.rb new file mode 100644 index 0000000000..cdcef50a63 --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_draggable/draggable.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Playbook + module PbDraggable + class Draggable < Playbook::KitBase + prop :initial_items, type: Playbook::Props::Array, + default: [] + + def data + Hash(prop(:data)).merge(pb_draggable: initial_items) + end + + def classname + generate_classname("pb_draggable") + end + end + end +end diff --git a/playbook/app/pb_kits/playbook/pb_draggable/draggable_container.html.erb b/playbook/app/pb_kits/playbook/pb_draggable/draggable_container.html.erb new file mode 100644 index 0000000000..0d83c489c8 --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_draggable/draggable_container.html.erb @@ -0,0 +1,8 @@ +<%= content_tag(:div, + aria: object.aria, + class: object.classname, + data: object.data, + id: object.id, + **combined_html_options) do %> + <%= content %> +<% end %> diff --git a/playbook/app/pb_kits/playbook/pb_draggable/draggable_container.rb b/playbook/app/pb_kits/playbook/pb_draggable/draggable_container.rb new file mode 100644 index 0000000000..fa42bc1c18 --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_draggable/draggable_container.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Playbook + module PbDraggable + class DraggableContainer < Playbook::KitBase + prop :container + + def data + Hash(prop(:data)).merge( + pb_draggable_container: container.presence || "" + ) + end + + def classname + generate_classname("pb_draggable_container") + end + end + end +end diff --git a/playbook/app/pb_kits/playbook/pb_draggable/draggable_item.html.erb b/playbook/app/pb_kits/playbook/pb_draggable/draggable_item.html.erb new file mode 100644 index 0000000000..0d83c489c8 --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_draggable/draggable_item.html.erb @@ -0,0 +1,8 @@ +<%= content_tag(:div, + aria: object.aria, + class: object.classname, + data: object.data, + id: object.id, + **combined_html_options) do %> + <%= content %> +<% end %> diff --git a/playbook/app/pb_kits/playbook/pb_draggable/draggable_item.rb b/playbook/app/pb_kits/playbook/pb_draggable/draggable_item.rb new file mode 100644 index 0000000000..8063cab3ae --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_draggable/draggable_item.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Playbook + module PbDraggable + class DraggableItem < Playbook::KitBase + prop :container + prop :drag_id, type: Playbook::Props::String, + default: "" + + def data + Hash(prop(:data)).merge( + pb_draggable_item: true, + pb_draggable_item_container: container.presence || "", + pb_draggable_item_drag_id: drag_id + ) + end + + def classname + generate_classname("pb_draggable_item") + end + end + end +end diff --git a/playbook/app/pb_kits/playbook/pb_draggable/index.js b/playbook/app/pb_kits/playbook/pb_draggable/index.js new file mode 100644 index 0000000000..2c485a5818 --- /dev/null +++ b/playbook/app/pb_kits/playbook/pb_draggable/index.js @@ -0,0 +1,116 @@ +import PbEnhancedElement from "../pb_enhanced_element"; + +const DRAGGABLE_SELECTOR = "[data-pb-draggable]"; +const DRAGGABLE_CONTAINER_SELECTOR = "[data-pb-draggable-container]"; +const DRAGGABLE_ITEM_SELECTOR = "[data-pb-draggable-item]"; + +const initialState = { + items: [], + dragData: { id: "", initialGroup: "" }, + isDragging: "", + activeContainer: "" +}; + +export default class PbDraggable extends PbEnhancedElement { + static get selector() { + return DRAGGABLE_SELECTOR; + } + + connect() { + document.pbDraggableState = document.pbDraggableState || initialState; + this.setItems(this.getInitialItems()); + this.bindEventListeners(); + } + + getInitialItems() { + return JSON.parse(this.element.dataset.pbDraggable); + } + + bindEventListeners() { + this.bindContainerEventListener(); + this.bindItemEventListener(); + } + + bindContainerEventListener() { + const containerElement = this.element.querySelector(DRAGGABLE_CONTAINER_SELECTOR); + const container = containerElement.dataset.pbDraggableContainer; + containerElement.addEventListener("dragover", (e) => this.handleDragOver(e, container)); + containerElement.addEventListener("drop", () => this.handleDrop(container)); + } + + bindItemEventListener() { + const itemElements = this.element.querySelectorAll(DRAGGABLE_ITEM_SELECTOR); + itemElements.forEach((itemElement) => { + const container = itemElement.dataset.pbDraggableItemContainer; + const dragId = itemElement.dataset.pbDraggableItemDragId; + itemElement.addEventListener("dragstart", () => this.handleDragStart(dragId, container)); + itemElement.addEventListener("dragenter", () => this.handleDragEnter(dragId, container)); + itemElement.addEventListener("dragend", () => this.handleDragEnd()); + }); + } + + setItems(items) { + document.pbDraggableState = { ...document.pbDraggableState, items: items }; + } + + setDragData(dragData) { + document.pbDraggableState = { ...document.pbDraggableState, dragData: dragData }; + } + + setIsDragging(isDragging) { + document.pbDraggableState = { ...document.pbDraggableState, isDragging: isDragging }; + } + + setActiveContainer(activeContainer) { + document.pbDraggableState = { ...document.pbDraggableState, activeContainer: activeContainer }; + } + + changeCategory(itemId, container) { + document.pbDraggableState = { + ...document.pbDraggableState, + items: document.pbDraggableState.items.map(item => + item.id === itemId ? { ...item, container: container } : item + ) + }; + } + + reorderItems(dragId, targetId) { + const newItems = [...document.pbDraggableState.items]; + const draggedItem = newItems.find(item => item.id === dragId); + const draggedIndex = newItems.indexOf(draggedItem); + const targetIndex = newItems.findIndex(item => item.id === targetId); + + newItems.splice(draggedIndex, 1); + newItems.splice(targetIndex, 0, draggedItem); + + document.pbDraggableState = { ...document.pbDraggableState, items: newItems }; + } + + handleDragStart(id, container) { + this.setDragData({ id: id, initialGroup: container }); + this.setIsDragging(id); + } + + handleDragEnter(id, container) { + if (document.pbDraggableState.dragData.id !== id) { + this.reorderItems(document.pbDraggableState.dragData.id, id); + this.setDragData({ id: document.pbDraggableState.dragData.id, initialGroup: container }); + } + } + + handleDragEnd() { + this.setIsDragging(""); + this.setActiveContainer(""); + } + + handleDrop(container) { + this.setIsDragging(""); + this.setActiveContainer(""); + this.changeCategory(document.pbDraggableState.dragData.id, container); + } + + handleDragOver(e, container) { + e.preventDefault(); + this.setActiveContainer(container); + } +} diff --git a/playbook/app/pb_kits/playbook/playbook-rails.js b/playbook/app/pb_kits/playbook/playbook-rails.js index 426ba44274..87cfee84da 100644 --- a/playbook/app/pb_kits/playbook/playbook-rails.js +++ b/playbook/app/pb_kits/playbook/playbook-rails.js @@ -45,6 +45,9 @@ PbNav.start() import PbStarRating from './pb_star_rating' PbStarRating.start() +import PbDraggable from './pb_draggable' +PbDraggable.start() + import 'flatpickr' // React-Rendered Rails Kits =====