diff --git a/sample.py b/sample.py index f53acc4..9edd067 100644 --- a/sample.py +++ b/sample.py @@ -64,3 +64,67 @@ st.write(f'original_items: {original_items}') st.write(f'sorted_items: {sorted_items}') + +st.write('----') +st.write('Sort items in a single container with remove.') +items = ['item1', 'item2', 'item3'] +sorted_items = sort_items(items, remove=True) +st.write(sorted_items) + +st.write('----') +st.write('Sort items vertically with remove.') +items = [ + {'header': 'container1', 'items': ['item1', 'item2', 'item3']}, + {'header': 'container2', 'items': ['item4', 'item5', 'item6']}, +] +sorted_items = sort_items(items, multi_containers=True, direction="vertical", remove=True) +st.write(sorted_items) + +st.write('----') +st.write('Advanced custom style.') +original_items = [ + {'header': 'first container', 'items': ['A', 'B', 'C']}, + {'header': 'second container', 'items': ['D', 'E', 'F']} +] + +custom_style = """ +.sortable-component { + border: 3px solid #6495ED; + border-radius: 10px; + padding: 5px; +} +.sortable-container { + background-color: #F0F0F0; + counter-reset: item; +} +.sortable-container-header { + background-color: #FFBFDF; + padding-left: 1rem; +} +.sortable-container-body { + background-color: #F0F0F0; +} +.sortable-item, .sortable-item:hover { + background-color: #6495ED; + font-color: #FFFFFF; + font-weight: bold; +} +.sortable-item::before { + content: counter(item) ". "; + counter-increment: item; +} +.sortable-item.dragging::before { + content: none; + counter-increment: none; +} +.cross-button{ + background-color: #FF6347; + color: #FFFFFF; + &:hover { + background-color: #FFBFDF; + } +} +""" +sorted_items = sort_items(original_items, multi_containers=True, custom_style=custom_style, remove=True) +st.write(f'original_items: {original_items}') +st.write(f'sorted_items: {sorted_items}') \ No newline at end of file diff --git a/streamlit_sortables/__init__.py b/streamlit_sortables/__init__.py index 6441c9f..75dbd78 100644 --- a/streamlit_sortables/__init__.py +++ b/streamlit_sortables/__init__.py @@ -47,7 +47,7 @@ # `declare_component` and call it done. The wrapper allows us to customize # our component's API: we can pre-process its input args, post-process its # output value, and add a docstring for users. -def sort_items(items: list[T], header: Optional[str]=None, multi_containers: bool=False, direction: str="horizontal", custom_style: Optional[str]=None, key: Any=None) -> list[T]: +def sort_items(items: list[T], header: Optional[str]=None, multi_containers: bool=False, direction: str="horizontal", custom_style: Optional[str]=None, key: Any=None, remove: bool=False) -> list[T]: """Create a new instance of "sortable_items". Parameters @@ -69,6 +69,8 @@ def sort_items(items: list[T], header: Optional[str]=None, multi_containers: bo An optional key that uniquely identifies this component. If this is None, and the component's arguments are changed, the component will be re-mounted in the Streamlit frontend and lose its current state. + remove: bool + If True, each item will contain a remove button. Defaults to False. Returns ------- @@ -86,7 +88,7 @@ def sort_items(items: list[T], header: Optional[str]=None, multi_containers: bo if not all(map(lambda item: isinstance(item, dict), items)): raise ValueError('items must be list[dict[str, Any]] if multi_containers is True.') - component_value = _component_func(items=items, direction=direction, customStyle=custom_style, default=items, key=key) + component_value = _component_func(items=items, direction=direction, customStyle=custom_style, default=items, key=key, remove=remove) # We could modify the value returned from the component if we wanted. # There's no need to do this in our simple example - but it's an option. @@ -224,5 +226,72 @@ def sort_items(items: list[T], header: Optional[str]=None, multi_containers: bo """ sorted_items = sort_items(original_items, multi_containers=True, custom_style=custom_style) + st.write(f'original_items: {original_items}') + st.write(f'sorted_items: {sorted_items}') + + st.write('----') + st.write('Sort items with remove.') + items = [ + {'header': 'container1', 'items': ['item1', 'item2', 'item3']}, + {'header': 'container2', 'items': ['item4', 'item5', 'item6']}, + ] + sorted_items = sort_items(items, multi_containers=True, remove=True) + st.write(sorted_items) + + st.write('----') + st.write('Sort items vertically with remove.') + items = [ + {'header': 'container1', 'items': ['item1', 'item2', 'item3']}, + {'header': 'container2', 'items': ['item4', 'item5', 'item6']}, + ] + sorted_items = sort_items(items, multi_containers=True, direction="vertical", remove=True) + st.write(sorted_items) + + st.write('----') + st.write('Advanced custom style.') + original_items = [ + {'header': 'first container', 'items': ['A', 'B', 'C']}, + {'header': 'second container', 'items': ['D', 'E', 'F']} + ] + + custom_style = """ + .sortable-component { + border: 3px solid #6495ED; + border-radius: 10px; + padding: 5px; + } + .sortable-container { + background-color: #F0F0F0; + counter-reset: item; + } + .sortable-container-header { + background-color: #FFBFDF; + padding-left: 1rem; + } + .sortable-container-body { + background-color: #F0F0F0; + } + .sortable-item, .sortable-item:hover { + background-color: #6495ED; + font-color: #FFFFFF; + font-weight: bold; + } + .sortable-item::before { + content: counter(item) ". "; + counter-increment: item; + } + .sortable-item.dragging::before { + content: none; + counter-increment: none; + } + .cross-button{ + background-color: #FF6347; + color: #FFFFFF; + &:hover { + background-color: #FFBFDF; + } + } + """ + sorted_items = sort_items(original_items, multi_containers=True, custom_style=custom_style, remove=True) st.write(f'original_items: {original_items}') st.write(f'sorted_items: {sorted_items}') \ No newline at end of file diff --git a/streamlit_sortables/frontend/src/SortableComponent.css b/streamlit_sortables/frontend/src/SortableComponent.css index bbbbcfa..aabed8b 100644 --- a/streamlit_sortables/frontend/src/SortableComponent.css +++ b/streamlit_sortables/frontend/src/SortableComponent.css @@ -58,3 +58,26 @@ .sortable-component.vertical .sortable-item { display: block; } + +li { + display: flex; + align-items: center; + justify-content: space-around; +} + +.li-content { + flex: 1; +} + +.cross-button { + border-radius: 5px; + border: none; + color: var(--primary-color); + background-color: var(--background-color); + cursor: pointer; + margin-left: 8px; + + &:hover { + background-color: var(--secondary-background-color); + } +} \ No newline at end of file diff --git a/streamlit_sortables/frontend/src/SortableComponent.tsx b/streamlit_sortables/frontend/src/SortableComponent.tsx index 374042f..b75aafc 100644 --- a/streamlit_sortables/frontend/src/SortableComponent.tsx +++ b/streamlit_sortables/frontend/src/SortableComponent.tsx @@ -30,7 +30,8 @@ type Direction = 'horizontal' | 'vertical'; interface StreamlitArguments { direction?: Direction, items: ContainerDescription[], - customStyle?: string + customStyle?: string, + remove?: Boolean } interface ContainerDescription { @@ -67,7 +68,8 @@ function Container(props: ContainerProps) { interface SortableComponentProps { direction?: Direction, - items: ContainerDescription[] + items: ContainerDescription[], + remove?: Boolean } function SortableComponent(props: SortableComponentProps) { @@ -106,6 +108,8 @@ function SortableComponent(props: SortableComponentProps) { { items.map(item => { return ( + props.remove ? + {item}: {item} ) }) @@ -231,6 +235,30 @@ function SortableComponent(props: SortableComponentProps) { }); }) } + + function removeItem(id: any) { + const activeContainerIndex = findContainer(id); + const activeItemIndex = items[activeContainerIndex].items.indexOf(id); + const activeItem = items[activeContainerIndex].items[activeItemIndex]; + const newItems = items.map(({ header, items }, index) => { + if (index === activeContainerIndex) { + return { + header: header, + items: items.filter(item => item !== activeItem) + } + } else { + return { + header: header, + items: items + } + } + }) + setItems(newItems); + + Streamlit.setComponentValue(newItems); + Streamlit.setFrameHeight(); + + } } function SortableComponentWrapper(props: ComponentProps) { @@ -243,7 +271,7 @@ function SortableComponentWrapper(props: ComponentProps) { return (
- +
) } diff --git a/streamlit_sortables/frontend/src/SortableItem.tsx b/streamlit_sortables/frontend/src/SortableItem.tsx index 5221e1c..a1ca217 100644 --- a/streamlit_sortables/frontend/src/SortableItem.tsx +++ b/streamlit_sortables/frontend/src/SortableItem.tsx @@ -8,7 +8,8 @@ export interface SortableItemProps { id: string, isActive?: boolean, children?: ReactNode, - isOverlay?: boolean + isOverlay?: boolean, + onRemove?: (id:string) => void } export const SortableItem: FunctionComponent = ((props) => { @@ -47,7 +48,8 @@ export const SortableItem: FunctionComponent = ((props) => { return (
  • - {props.children ? props.children : null} + {props.children ? props.children : null} + {props.onRemove&&()}
  • ) })