Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 79 additions & 4 deletions preview/src/components/drag_and_drop_list/component.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,51 @@
use dioxus::prelude::*;
use dioxus_primitives::drag_and_drop_list::{self, DragAndDropListItemProps, DragAndDropListProps};
use dioxus_primitives::drag_and_drop_list::{
self, DragAndDropContext, DragAndDropItemContext, DragAndDropListItemProps,
};
use dioxus_primitives::icon::Icon;

#[derive(Props, Clone, PartialEq)]
pub struct DragAndDropListProps {
/// Items (labels) to be rendered.
pub items: Vec<Element>,

/// Set if the list items should be removable
#[props(default)]
pub is_removable: bool,

/// Accessible label for the list
#[props(default)]
pub aria_label: Option<String>,

/// Additional attributes to apply to the list element.
#[props(extends = GlobalAttributes)]
pub attributes: Vec<Attribute>,

/// The children of the list component.
pub children: Element,
}

#[component]
pub fn DragAndDropList(props: DragAndDropListProps) -> Element {
let is_removable = props.is_removable;
let items = props
.items
.iter()
.map(|item| {
rsx! {
DragIcon {}
div { class: "item-body-div", {item} }
if is_removable {
RemoveButton {}
}
}
})
.collect();

rsx! {
document::Link { rel: "stylesheet", href: asset!("./style.css") }
drag_and_drop_list::DragAndDropList {
items: props.items,
is_removable: props.is_removable,
items,
aria_label: props.aria_label,
attributes: props.attributes,
{props.children}
Expand All @@ -20,9 +58,46 @@ pub fn DragAndDropListItem(props: DragAndDropListItemProps) -> Element {
rsx! {
drag_and_drop_list::DragAndDropListItem {
index: props.index,
is_removable: props.is_removable,
attributes: props.attributes,
{props.children}
}
}
}

#[component]
fn DragIcon() -> Element {
rsx! {
div { class: "item-icon-div", aria_hidden: "true",
Icon {
// equal icon from lucide https://lucide.dev/icons/equal
line { x1: "5", x2: "19", y1: "9", y2: "9" }
line { x1: "5", x2: "19", y1: "15", y2: "15" }
}
}
}
}

#[component]
pub fn RemoveButton(
#[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
children: Element,
) -> Element {
let mut ctx: DragAndDropContext = use_context();
let item_ctx: DragAndDropItemContext = use_context();
let index = item_ctx.index();
let label = format!("Remove item {}", index + 1);
rsx! {
button {
class: "remove-button",
aria_label: "{label}",
onclick: move |_| ctx.remove(index),
..attributes,
{children}
Icon {
// X icon from lucide https://lucide.dev/icons/x
path { d: "M18 6 6 18" }
path { d: "m6 6 12 12" }
}
}
}
}
71 changes: 24 additions & 47 deletions primitives/src/drag_and_drop_list.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//! Defines the [`DragAndDropList`] component and its sub-components.
use crate::icon::Icon;
use dioxus::prelude::*;
use std::rc::Rc;

Expand Down Expand Up @@ -39,8 +38,24 @@ fn resolve_drop_position(from: usize, to: usize) -> DropPosition {
to.cmp(&from).into()
}

/// Context provided by [`DragAndDropListItem`] to its children.
/// Use `use_context::<DragAndDropItemContext>()` to access the current item's index.
#[derive(Clone, Copy)]
struct DragAndDropContext {
pub struct DragAndDropItemContext {
index: usize,
}

impl DragAndDropItemContext {
/// Returns the index of the current item in the list.
pub fn index(&self) -> usize {
self.index
}
}

/// Context provided by [`DragAndDropList`] to its descendants.
/// Use `use_context::<DragAndDropContext>()` to access list-level operations.
#[derive(Clone, Copy)]
pub struct DragAndDropContext {
drag_from: Signal<Option<usize>>,
drop_to: Signal<Option<usize>>,
drop_position: Signal<DropPosition>,
Expand Down Expand Up @@ -96,7 +111,8 @@ impl DragAndDropContext {
self.list_items.set(list);
}

fn remove(&mut self, index: usize) {
/// Remove the item at the given index from the list.
pub fn remove(&mut self, index: usize) {
let mut list = (self.list_items)();
if list.remove(index).is_ok() {
let new_len = list.len();
Expand Down Expand Up @@ -196,16 +212,12 @@ impl DragAndDropContext {
}
}

/// The props for the [`DragAndDropListItem`] component.
/// The props for the [`DragAndDropList`] component.
#[derive(Props, Clone, PartialEq)]
pub struct DragAndDropListProps {
/// Items (labels) to be rendered.
pub items: Vec<Element>,

/// Set if the list items should be removable
#[props(default)]
pub is_removable: bool,

/// Accessible label for the list
#[props(default)]
pub aria_label: Option<String>,
Expand Down Expand Up @@ -274,7 +286,6 @@ pub fn DragAndDropList(props: DragAndDropListProps) -> Element {
rsx! {
DragAndDropListItem {
index,
is_removable: props.is_removable,
{children}
}
}
Expand Down Expand Up @@ -313,12 +324,9 @@ pub fn DragAndDropList(props: DragAndDropListProps) -> Element {
/// The props for the [`DragAndDropListItemProps`] component.
#[derive(Props, Clone, PartialEq)]
pub struct DragAndDropListItemProps {
/// The index of the index trigger
/// The index of the item in the list
pub index: usize,

/// Set if the list item should be removable
pub is_removable: bool,

/// Additional attributes to apply to the list item element.
#[props(extends = GlobalAttributes)]
pub attributes: Vec<Attribute>,
Expand Down Expand Up @@ -354,6 +362,7 @@ pub fn DragAndDropListItem(props: DragAndDropListItemProps) -> Element {
let mut ctx: DragAndDropContext = use_context();

let index = props.index;
use_context_provider(|| DragAndDropItemContext { index });

let mut item_ref: Signal<Option<Rc<MountedData>>> = use_signal(|| None);
use_effect(move || {
Expand Down Expand Up @@ -413,7 +422,7 @@ pub fn DragAndDropListItem(props: DragAndDropListItemProps) -> Element {
}
Key::Delete | Key::Backspace => {
event.prevent_default();
if !(ctx.is_dragging)() && props.is_removable {
if !(ctx.is_dragging)() {
ctx.remove(index);
}
}
Expand Down Expand Up @@ -480,11 +489,7 @@ pub fn DragAndDropListItem(props: DragAndDropListItemProps) -> Element {
//ondragleave: move |_| ctx.drop_to.set(None),
onkeydown,
..props.attributes,
div { class: "item-icon-div", aria_hidden: "true", DragIcon {} }
div { class: "item-body-div", {props.children} }
if props.is_removable {
RemoveButton { index, on_click: move || ctx.remove(index) }
}
{props.children}
}
if (ctx.drop_position)() == DropPosition::After && render_drop_indicator((ctx.drop_to)()) {
DropIndicator { }
Expand All @@ -500,31 +505,3 @@ fn DropIndicator() -> Element {
}
}
}

#[component]
fn RemoveButton(index: usize, on_click: Callback<()>) -> Element {
let label = format!("Remove item {}", index + 1);
rsx! {
button {
class: "remove-button",
aria_label: "{label}",
onclick: move |_| on_click.call(()),
Icon {
// X icon from lucide https://lucide.dev/icons/x
path { d: "M18 6 6 18" }
path { d: "m6 6 12 12" }
}
}
}
}

#[component]
fn DragIcon() -> Element {
rsx! {
Icon {
// equal icon from lucide https://lucide.dev/icons/equal
line { x1: "5", x2: "19", y1: "9", y2: "9", }
line { x1: "5", x2: "19", y1: "15", y2: "15", }
}
}
}
Loading