Skip to content
Open
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
64 changes: 64 additions & 0 deletions sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}')
73 changes: 71 additions & 2 deletions streamlit_sortables/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
-------
Expand All @@ -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.
Expand Down Expand Up @@ -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}')
23 changes: 23 additions & 0 deletions streamlit_sortables/frontend/src/SortableComponent.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
34 changes: 31 additions & 3 deletions streamlit_sortables/frontend/src/SortableComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ type Direction = 'horizontal' | 'vertical';
interface StreamlitArguments {
direction?: Direction,
items: ContainerDescription[],
customStyle?: string
customStyle?: string,
remove?: Boolean
}

interface ContainerDescription {
Expand Down Expand Up @@ -67,7 +68,8 @@ function Container(props: ContainerProps) {

interface SortableComponentProps {
direction?: Direction,
items: ContainerDescription[]
items: ContainerDescription[],
remove?: Boolean
}

function SortableComponent(props: SortableComponentProps) {
Expand Down Expand Up @@ -106,6 +108,8 @@ function SortableComponent(props: SortableComponentProps) {
{
items.map(item => {
return (
props.remove ?
<SortableItem key={item} id={item} isActive={item === activeItem} onRemove={removeItem}>{item}</SortableItem>:
<SortableItem key={item} id={item} isActive={item === activeItem}>{item}</SortableItem>
)
})
Expand Down Expand Up @@ -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) {
Expand All @@ -243,7 +271,7 @@ function SortableComponentWrapper(props: ComponentProps) {
return (
<div className={className}>
<style>{args.customStyle}</style>
<SortableComponent items={items} direction={args.direction} />
<SortableComponent items={items} direction={args.direction} remove={args.remove}/>
</div>
)
}
Expand Down
6 changes: 4 additions & 2 deletions streamlit_sortables/frontend/src/SortableItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<SortableItemProps> = ((props) => {
Expand Down Expand Up @@ -47,7 +48,8 @@ export const SortableItem: FunctionComponent<SortableItemProps> = ((props) => {

return (
<li className={className} ref={sortableProps.setNodeRef} style={style} {...sortableProps.attributes} {...sortableProps.listeners}>
{props.children ? props.children : null}
<span className="li-content">{props.children ? props.children : null}</span>
{props.onRemove&&(<button className="cross-button" onClick={()=>props.onRemove!(props.id)}>×</button>)}
</li>
)
})