Skip to content

Commit

Permalink
feat(UI): Implement a DraggableList component (#460)
Browse files Browse the repository at this point in the history
This PR is the first step to fix #408.

## UI/UX preview


https://github.com/user-attachments/assets/d9db0c12-bee1-4795-ab3b-fc7c4e11ae9e



## usage

The component needs a reactive list of `Item` (a ref). Item is a regular
object that needs an id filed that is a string.
It will mutate the given array of items to reflect the user selected
order.

```vue
<script setup lang="ts">
import { ref } from "vue";

const draggableListData = ref(
  Array.from({ length: 25 }, (v, i) => ({
    id: `${i}`,
    color: `hsl(${(360 / 25) * i}deg, 90%, 50%)`,
    content: Array.from(
      { length: Math.floor(Math.random() * 10) + 1 }, // Random number of items between 1 and 10
      () => String.fromCharCode(97 + Math.floor(Math.random() * 26)) // Random lowercase letter
    ),
  }))
);
</script>

<template>
  <DraggableList v-model:items="draggableListData">
    <template #item="{ id, color, content }">
      <div :style="{ backgroundColor: color, color: 'white' }">
        <span>ID: {{ id }}</span>
        <ul>
          <li v-for="(c, i) in content" :key="i">{{ c }}</li>
        </ul>
      </div>
    </template>
  </DraggableList>
</template>
```

## implementation choices

The implementation introduces two new dependencies. 
- [html-to-image](https://github.com/bubkoo/html-to-image) to create
rasterized version of the moved item
- [interact.js](https://interactjs.io/) to streamline pointer
interaction

The main container is a flexbox, items flow normally in it (no absolute
positioning).
When a drag start the selected items becomes slightly transparent and a
raster copy of it is generated. That raster copy follows user's pointer
using computed translation (CSS transform). To make room for the item to
be dropped each items also has a drop indicator. The size of this drop
indicator reflects the size of the dragged item. I use margin top &
bottom to give different feedback when dragging down or up. This is nice
because no coordinates calculation is needed and items continue to flow
normally in the container.

To limit clashes with inner item code when dragging all the items are
transformed to a rasterized version of themself.

## known limitation

~~For now it is not possible to automatically scroll the container when
dragging outside of its visible range.~~

---------

Co-authored-by: Thomas S. <[email protected]>
  • Loading branch information
rouk1 and thomass-dev authored Oct 16, 2024
1 parent 3c892b1 commit 2e80e75
Show file tree
Hide file tree
Showing 13 changed files with 603 additions and 50 deletions.
23 changes: 23 additions & 0 deletions skore-ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions skore-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
"@floating-ui/vue": "^1.1.5",
"@vscode/markdown-it-katex": "^1.1.0",
"date-fns": "^3.6.0",
"html-to-image": "^1.11.11",
"interactjs": "^1.10.27",
"markdown-it": "^14.1.0",
"markdown-it-emoji": "^3.0.0",
"markdown-it-highlightjs": "^4.2.0",
Expand Down
34 changes: 34 additions & 0 deletions skore-ui/src/assets/fixtures/html-snippet.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<div id="container">
<div id="square"></div>
</div>
<script>
const container = document.getElementById("container");
const square = document.getElementById("square");
setInterval(() => {
square.style.backgroundColor = "#" + ((Math.random() * 0xffffff) << 0).toString(16);
}, 500);
setInterval(() => {
const newHeight = Math.round(Math.random() * 200);
container.style.height = `${newHeight}px`;
square.style.height = `${newHeight / 2}px`;
}, 1000);
</script>
<style>
#container {
display: flex;
width: 100px;
height: 100px;
align-items: center;
justify-content: center;
padding: 5px;
border: 1px dashed #ccc;
background-color: #fff;
transition: all 0.5s linear;
}

#square {
width: 50px;
height: 50px;
transition: all 0.5s linear;
}
</style>
Binary file modified skore-ui/src/assets/fonts/icomoon.eot
Binary file not shown.
49 changes: 25 additions & 24 deletions skore-ui/src/assets/fonts/icomoon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified skore-ui/src/assets/fonts/icomoon.ttf
Binary file not shown.
Binary file modified skore-ui/src/assets/fonts/icomoon.woff
Binary file not shown.
52 changes: 28 additions & 24 deletions skore-ui/src/assets/styles/_icons.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,98 +27,102 @@
text-transform: none;
}

.icon-branch::before {
.icon-edit::before {
content: "\e900";
}

.icon-more::before {
.icon-handle::before {
content: "\e901";
}

.icon-trash::before {
.icon-more::before {
content: "\e902";
}

.icon-edit::before {
.icon-branch::before {
content: "\e903";
}

.icon-copy::before {
.icon-trash::before {
content: "\e904";
}

.icon-pill::before {
.icon-copy::before {
content: "\e905";
}

.icon-new-document::before {
.icon-pill::before {
content: "\e906";
}

.icon-recent-document::before {
.icon-new-document::before {
content: "\e907";
}

.icon-plus-circle::before {
.icon-recent-document::before {
content: "\e908";
}

.icon-warning::before {
.icon-plus-circle::before {
content: "\e909";
}

.icon-info::before {
.icon-warning::before {
content: "\e90a";
}

.icon-error::before {
.icon-info::before {
content: "\e90b";
}

.icon-success::before {
.icon-error::before {
content: "\e90c";
}

.icon-search::before {
.icon-success::before {
content: "\e90d";
}

.icon-maximize::before {
.icon-search::before {
content: "\e90e";
}

.icon-folder::before {
.icon-maximize::before {
content: "\e90f";
}

.icon-plot::before {
.icon-folder::before {
content: "\e910";
}

.icon-text::before {
.icon-plot::before {
content: "\e911";
}

.icon-gift::before {
.icon-text::before {
content: "\e912";
}

.icon-pie-chart::before {
.icon-gift::before {
content: "\e913";
}

.icon-chevron-left::before {
.icon-pie-chart::before {
content: "\e914";
}

.icon-chevron-down::before {
.icon-chevron-left::before {
content: "\e915";
}

.icon-chevron-right::before {
.icon-chevron-down::before {
content: "\e916";
}

.icon-chevron-up::before {
.icon-chevron-right::before {
content: "\e917";
}

.icon-chevron-up::before {
content: "\e918";
}
Loading

0 comments on commit 2e80e75

Please sign in to comment.