refactor(ui): re-implement table with rows as a simple responsive grid component
This commit is contained in:
parent
bf563626ba
commit
e17d88b5e1
|
@ -9,7 +9,6 @@ import usePlayOptions from '~/composables/audio/usePlayOptions'
|
||||||
import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue'
|
import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue'
|
||||||
import PlayIndicator from '~/components/audio/track/PlayIndicator.vue'
|
import PlayIndicator from '~/components/audio/track/PlayIndicator.vue'
|
||||||
import PlayButton from '~/components/audio/PlayButton.vue'
|
import PlayButton from '~/components/audio/PlayButton.vue'
|
||||||
import Button from '~/components/ui/Button.vue'
|
|
||||||
import { usePlayer } from '~/composables/audio/player'
|
import { usePlayer } from '~/composables/audio/player'
|
||||||
import { useQueue } from '~/composables/audio/queue'
|
import { useQueue } from '~/composables/audio/queue'
|
||||||
import { useStore } from '~/store'
|
import { useStore } from '~/store'
|
||||||
|
@ -65,13 +64,16 @@ const hover = ref(false)
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
:class="[{ active }, 'track-row row']"
|
:class="[{ active }, 'track-row row', $style.row]"
|
||||||
@dblclick="activateTrack(track, index)"
|
@dblclick="activateTrack(track, index)"
|
||||||
@mousemove="hover = true"
|
@mousemove="hover = true"
|
||||||
@mouseout="hover = false"
|
@mouseout="hover = false"
|
||||||
|
style="display: contents;"
|
||||||
>
|
>
|
||||||
|
<!-- 1. column: Play button or track position -->
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="actions one wide left floated column"
|
class="one wide column"
|
||||||
role="button"
|
role="button"
|
||||||
@click.prevent.exact="activateTrack(track, index)"
|
@click.prevent.exact="activateTrack(track, index)"
|
||||||
>
|
>
|
||||||
|
@ -116,40 +118,44 @@ const hover = ref(false)
|
||||||
{{ `${track.position}`.padStart(2, '0') }}
|
{{ `${track.position}`.padStart(2, '0') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 2. column: Cover art or nothing -->
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="showArt"
|
class="image column"
|
||||||
class="image left floated column"
|
|
||||||
role="button"
|
role="button"
|
||||||
@click.prevent.exact="activateTrack(track, index)"
|
@click.prevent.exact="activateTrack(track, index)"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
v-if="track.album?.cover?.urls.original"
|
v-if="showArt && track.cover?.urls.original"
|
||||||
v-lazy="store.getters['instance/absoluteUrl'](track.album.cover.urls.medium_square_crop)"
|
|
||||||
alt=""
|
|
||||||
class="ui artist-track mini image"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
v-else-if="track.cover?.urls.original"
|
|
||||||
v-lazy="store.getters['instance/absoluteUrl'](track.cover.urls.medium_square_crop)"
|
v-lazy="store.getters['instance/absoluteUrl'](track.cover.urls.medium_square_crop)"
|
||||||
alt=""
|
alt=""
|
||||||
class="ui artist-track mini image"
|
class="ui artist-track mini image"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
v-else-if="track.artist_credit?.length && track.artist_credit[0].artist.cover?.urls.original"
|
v-else-if="showArt && track.album?.cover?.urls.original"
|
||||||
|
v-lazy="store.getters['instance/absoluteUrl'](track.album.cover.urls.medium_square_crop)"
|
||||||
|
alt=""
|
||||||
|
class="ui artist-track mini image"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-else-if="showArt && track.artist_credit?.length && track.artist_credit[0].artist.cover?.urls.original"
|
||||||
v-lazy="store.getters['instance/absoluteUrl'](track.artist_credit[0].artist.cover.urls.medium_square_crop) "
|
v-lazy="store.getters['instance/absoluteUrl'](track.artist_credit[0].artist.cover.urls.medium_square_crop) "
|
||||||
alt=""
|
alt=""
|
||||||
class="ui artist-track mini image"
|
class="ui artist-track mini image"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
v-else
|
v-else-if="showArt"
|
||||||
alt=""
|
alt=""
|
||||||
class="ui artist-track mini image"
|
class="ui artist-track mini image"
|
||||||
src="../../../assets/audio/default-cover.png"
|
src="../../../assets/audio/default-cover.png"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- third column: title ! -->
|
||||||
<div
|
<div
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="content ellipsis left floated column"
|
class="content ellipsis column"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
@click="activateTrack(track, index)"
|
@click="activateTrack(track, index)"
|
||||||
|
@ -157,21 +163,26 @@ const hover = ref(false)
|
||||||
{{ track.title }}
|
{{ track.title }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 4. column: album link -->
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="showAlbum"
|
class="content ellipsis column"
|
||||||
class="content ellipsis left floated column"
|
|
||||||
>
|
>
|
||||||
<router-link
|
<router-link
|
||||||
|
v-if="showAlbum"
|
||||||
:to="{ name: 'library.albums.detail', params: { id: track.album?.id } }"
|
:to="{ name: 'library.albums.detail', params: { id: track.album?.id } }"
|
||||||
>
|
>
|
||||||
{{ track.album?.title }}
|
{{ track.album?.title }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 5. column: artist link -->
|
||||||
<div
|
<div
|
||||||
v-if="showArtist"
|
class="content ellipsis column"
|
||||||
class="content ellipsis left floated column"
|
|
||||||
>
|
>
|
||||||
<template
|
<template
|
||||||
|
v-if="showArtist"
|
||||||
v-for="ac in track.artist_credit"
|
v-for="ac in track.artist_credit"
|
||||||
:key="ac.artist.id"
|
:key="ac.artist.id"
|
||||||
>
|
>
|
||||||
|
@ -187,27 +198,33 @@ const hover = ref(false)
|
||||||
<span>{{ ac.joinphrase }}</span>
|
<span>{{ ac.joinphrase }}</span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 6. column: favorite icon -->
|
||||||
<div
|
<div
|
||||||
v-if="store.state.auth.authenticated"
|
class="meta column"
|
||||||
class="meta right floated column"
|
|
||||||
>
|
>
|
||||||
<track-favorite-icon
|
<track-favorite-icon
|
||||||
|
v-if="store.state.auth.authenticated"
|
||||||
ghost
|
ghost
|
||||||
:track="track"
|
:track="track"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 7. column: duration -->
|
||||||
<div
|
<div
|
||||||
v-if="showDuration"
|
class="meta column"
|
||||||
class="meta right floated column"
|
|
||||||
>
|
>
|
||||||
<human-duration
|
<human-duration
|
||||||
v-if="track.uploads[0] && track.uploads[0].duration"
|
v-if="showDuration && track.uploads[0] && track.uploads[0].duration"
|
||||||
:duration="track.uploads[0].duration"
|
:duration="track.uploads[0].duration"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 8. column: play button dropdown menu -->
|
||||||
<div
|
<div
|
||||||
v-if="displayActions"
|
v-if="displayActions"
|
||||||
class="meta right floated column"
|
class="meta column"
|
||||||
>
|
>
|
||||||
<PlayButton
|
<PlayButton
|
||||||
:dropdown-only="true"
|
:dropdown-only="true"
|
||||||
|
@ -217,3 +234,14 @@ const hover = ref(false)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style module>
|
||||||
|
.row > :has(> :is(a, span)) {
|
||||||
|
line-height: 46px;
|
||||||
|
}
|
||||||
|
.row > div {
|
||||||
|
/* total height 64px, according to designs on penpot */
|
||||||
|
margin-bottom: 8px;
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -16,6 +16,7 @@ import TrackRow from '~/components/audio/track/Row.vue'
|
||||||
import Input from '~/components/ui/Input.vue'
|
import Input from '~/components/ui/Input.vue'
|
||||||
import Spacer from '~/components/ui/Spacer.vue'
|
import Spacer from '~/components/ui/Spacer.vue'
|
||||||
import Loader from '~/components/ui/Loader.vue'
|
import Loader from '~/components/ui/Loader.vue'
|
||||||
|
import Table from '~/components/ui/Table.vue'
|
||||||
|
|
||||||
import useErrorHandler from '~/composables/useErrorHandler'
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
|
||||||
|
@ -150,7 +151,9 @@ const updatePage = (page: number) => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
<!-- Show the search bar if search is true -->
|
<!-- Show the search bar if search is true -->
|
||||||
|
|
||||||
<Input search
|
<Input search
|
||||||
v-if="search"
|
v-if="search"
|
||||||
v-model="query"
|
v-model="query"
|
||||||
|
@ -175,61 +178,38 @@ const updatePage = (page: number) => {
|
||||||
@refresh="fetchData()"
|
@refresh="fetchData()"
|
||||||
/>
|
/>
|
||||||
</slot>
|
</slot>
|
||||||
<div v-else>
|
|
||||||
|
<!-- Table on screens > 768px wide -->
|
||||||
|
<!-- TODO: Make responsive to parent container instead of screen -->
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
v-else
|
||||||
:class="['track-table', 'ui', 'unstackable', 'grid', 'tablet-and-up']"
|
:class="['track-table', 'ui', 'unstackable', 'grid', 'tablet-and-up']"
|
||||||
>
|
>
|
||||||
<Loader v-if="isLoading" />
|
<Loader v-if="isLoading" />
|
||||||
<div class="track-table row">
|
|
||||||
<div
|
|
||||||
v-if="showPosition"
|
|
||||||
class="actions left floated column"
|
|
||||||
>
|
|
||||||
<i class="bi bi-hash" />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="actions left floated column"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
v-if="showArt"
|
|
||||||
class="image left floated column"
|
|
||||||
/>
|
|
||||||
<div class="content ellipsis left floated column">
|
|
||||||
<b>{{ labels.title }}</b>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="showAlbum"
|
|
||||||
class="content ellipsisleft floated column"
|
|
||||||
>
|
|
||||||
<b>{{ labels.album }}</b>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="showArtist"
|
|
||||||
class="content ellipsis left floated column"
|
|
||||||
>
|
|
||||||
<b>{{ labels.artist }}</b>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="store.state.auth.authenticated"
|
|
||||||
class="meta right floated column"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
v-if="showDuration"
|
|
||||||
class="meta right floated column"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
class="bi bi-clock"
|
|
||||||
style="padding: 0.5rem"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="displayActions"
|
|
||||||
class="meta right floated column"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- For each item, build a row -->
|
<Table
|
||||||
|
:grid-template-columns="['48px', '48px', 'auto', 'auto', 'auto', '48px', '64px', '48px']"
|
||||||
|
:header-props="{ 'table-header': true }"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<label />
|
||||||
|
<label />
|
||||||
|
<label>
|
||||||
|
<span>{{ labels.title }}</span>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span v-if="showAlbum">{{ labels.album }}</span>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span v-if="showArtist">{{ labels.artist }}</span>
|
||||||
|
</label>
|
||||||
|
<label />
|
||||||
|
<label>
|
||||||
|
<i v-if="showDuration" class="bi bi-clock" />
|
||||||
|
</label>
|
||||||
|
<label />
|
||||||
|
</template>
|
||||||
|
|
||||||
<track-row
|
<track-row
|
||||||
v-for="(track, index) in allTracks"
|
v-for="(track, index) in allTracks"
|
||||||
|
@ -245,7 +225,11 @@ const updatePage = (page: number) => {
|
||||||
:show-duration="showDuration"
|
:show-duration="showDuration"
|
||||||
:is-podcast="isPodcast"
|
:is-podcast="isPodcast"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
|
||||||
<Pagination
|
<Pagination
|
||||||
v-if="paginateResults"
|
v-if="paginateResults"
|
||||||
:pages="paginateBy"
|
:pages="paginateBy"
|
||||||
|
@ -254,6 +238,9 @@ const updatePage = (page: number) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Under 768px screen width -->
|
||||||
|
<!-- TODO: Make responsive to parent container instead of screen -->
|
||||||
|
|
||||||
<div
|
<div
|
||||||
:class="['track-table', 'ui', 'unstackable', 'grid', 'tablet-and-below']"
|
:class="['track-table', 'ui', 'unstackable', 'grid', 'tablet-and-below']"
|
||||||
>
|
>
|
||||||
|
@ -283,3 +270,9 @@ const updatePage = (page: number) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
[table-header] {
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -90,6 +90,11 @@ const attributes = computed(() => ({
|
||||||
place-content: center;
|
place-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[layout=grid], &[layout=grid-custom] > * {
|
||||||
|
/* Set this global variable through `width` */
|
||||||
|
grid-column: var(--grid-column);
|
||||||
|
}
|
||||||
|
|
||||||
&[layout=stack] {
|
&[layout=stack] {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
<script setup lang="ts" generic="T extends string">
|
||||||
|
defineProps<{
|
||||||
|
gridTemplateColumns: (`${number}${'px' | 'fr'}` | 'auto')[]
|
||||||
|
headerProps?: { [key: string]: unknown }
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section :class="$style.table" :style="`grid-template-columns: ${gridTemplateColumns.join(' ')};`">
|
||||||
|
<span :class="$style['table-header']" v-bind="headerProps">
|
||||||
|
<slot name="header" />
|
||||||
|
</span>
|
||||||
|
<slot />
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style module>
|
||||||
|
.table {
|
||||||
|
width: 100%; align-self: stretch; display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table cells */
|
||||||
|
.table > * {
|
||||||
|
height: 64px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table header */
|
||||||
|
.table-header {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
/* Table header cells */
|
||||||
|
.table-header > *{
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
line-height: 36px;
|
||||||
|
border: 0px solid var(--border-color);
|
||||||
|
border-width: 1px 0;
|
||||||
|
color: color-mix(in oklab, currentcolor 50%, var(--border-color));
|
||||||
|
font-weight: 900;
|
||||||
|
grid-column: span 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Auto-expand cells if following cells are empty */
|
||||||
|
.table > :has(+ :empty) {
|
||||||
|
grid-column-end: span 2;
|
||||||
|
}
|
||||||
|
.table > :has(+ :empty + :empty) {
|
||||||
|
grid-column-end: span 3;
|
||||||
|
}
|
||||||
|
.table > :has(+ :empty + :empty + :empty) {
|
||||||
|
grid-column-end: span 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide empty content (after the header row) */
|
||||||
|
.table > .table-header ~ :empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -20,18 +20,18 @@ export type Key = KeysOfUnion<WidthProps>
|
||||||
const widths = {
|
const widths = {
|
||||||
minContent: 'width: min-content; flex-grow: 0;',
|
minContent: 'width: min-content; flex-grow: 0;',
|
||||||
iconWidth: 'width: 40px;',
|
iconWidth: 'width: 40px;',
|
||||||
tiny: "width: 124px; grid-column: span 2;",
|
tiny: "width: 124px; --grid-column: span 2;",
|
||||||
buttonWidth: "width: 136px; grid-column: span 2; flex-grow: 0; min-width: min-content;",
|
buttonWidth: "width: 136px; --grid-column: span 2; flex-grow: 0; min-width: min-content;",
|
||||||
small: "width: 202px; grid-column: span 3;",
|
small: "width: 202px; --grid-column: span 3;",
|
||||||
medium: "width: 280px; grid-column: span 4;",
|
medium: "width: 280px; --grid-column: span 4;",
|
||||||
auto: "width: auto;",
|
auto: "width: auto;",
|
||||||
full: "width: auto; grid-column: 1 / -1; place-self: stretch; flex-grow: 1;",
|
full: "width: auto; --grid-column: 1 / -1; place-self: stretch; flex-grow: 1;",
|
||||||
width: (w: string) => `width: ${w}; flex-grow:0;`,
|
width: (w: string) => `width: ${w}; flex-grow:0;`,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
const sizes = {
|
const sizes = {
|
||||||
squareSmall: 'height: 40px; width: 40px; padding: 4px; grid-column: span 1; justify-content: center;',
|
squareSmall: 'height: 40px; width: 40px; padding: 4px; justify-content: center;',
|
||||||
square: 'height: 48px; width: 48px; grid-column: span 1; justify-content: center;',
|
square: 'height: 48px; width: 48px; justify-content: center;',
|
||||||
lowHeight: 'height: 40px;',
|
lowHeight: 'height: 40px;',
|
||||||
normalHeight: 'height: 48px;',
|
normalHeight: 'height: 48px;',
|
||||||
} as const
|
} as const
|
||||||
|
|
|
@ -55,6 +55,7 @@ export default defineConfig({
|
||||||
{ text: "Spacer", link: "/components/ui/layout/spacer" },
|
{ text: "Spacer", link: "/components/ui/layout/spacer" },
|
||||||
{ text: "Header", link: "/components/ui/layout/header" },
|
{ text: "Header", link: "/components/ui/layout/header" },
|
||||||
{ text: "Section", link: "/components/ui/layout/section" },
|
{ text: "Section", link: "/components/ui/layout/section" },
|
||||||
|
{ text: "Table", link: "/components/ui/layout/table" },
|
||||||
{ text: "Using `flex`", link: "/components/ui/layout/flex" },
|
{ text: "Using `flex`", link: "/components/ui/layout/flex" },
|
||||||
{ text: "Using `stack`", link: "/components/ui/layout/stack" },
|
{ text: "Using `stack`", link: "/components/ui/layout/stack" },
|
||||||
{ text: "Using `grid`", link: "/components/ui/layout/grid" },
|
{ text: "Using `grid`", link: "/components/ui/layout/grid" },
|
||||||
|
|
|
@ -192,39 +192,3 @@ Note that you can set the minimum space occupied by the `Spacer` with its `size`
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<Tabs class="solid" style="border: 32px solid var(--background-color);border-radius: 8px;outline: 1px solid var(--border-color);margin: 0 -32px;">
|
|
||||||
<Tab title="Flex (default)" icon="⠖">
|
|
||||||
|
|
||||||
Items are laid out in a row and wrapped as they overflow the container
|
|
||||||
|
|
||||||
[Layout flex](/components/ui/layout/flex)
|
|
||||||
|
|
||||||
</Tab>
|
|
||||||
<Tab title="Grid" icon="ꖛ">
|
|
||||||
|
|
||||||
Align items both vertically and horizontally
|
|
||||||
|
|
||||||
[Layout grid](/components/ui/layout/grid)
|
|
||||||
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab title="Stack" icon="𝌆">
|
|
||||||
|
|
||||||
Add space between vertically stacked items
|
|
||||||
|
|
||||||
[Layout stack](/components/ui/layout/stack)
|
|
||||||
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab title="Columns" icon="ꔖ">
|
|
||||||
|
|
||||||
Let text and items flow like on a printed newspaper
|
|
||||||
|
|
||||||
[Layout columns](/components/ui/layout/columns)
|
|
||||||
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
</Tabs>
|
|
||||||
|
|
|
@ -0,0 +1,211 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Table from "~/components/ui/Table.vue";
|
||||||
|
import Link from "~/components/ui/Link.vue";
|
||||||
|
import Button from "~/components/ui/Button.vue"; // Need to import this, else vitepress will not load the stylesheet with the colors.
|
||||||
|
</script>
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import Table from "~/components/ui/Table.vue";
|
||||||
|
```
|
||||||
|
|
||||||
|
# Table
|
||||||
|
|
||||||
|
Arrange cells in a grid.
|
||||||
|
|
||||||
|
For every row, add exactly one element per column.
|
||||||
|
|
||||||
|
<Link
|
||||||
|
to="https://design.funkwhale.audio/#/workspace/a4e0101a-252c-80ef-8003-918b4c2c3927/e3a187f0-0f5e-11ed-adb9-fff9e854a67c?page-id=6ca536f0-0f5f-11ed-adb9-fff9e854a67c">
|
||||||
|
Design [Penpot]
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
**Prop** `grid-template-columns`: An array of the column widths. You can make a column either fixed-width or use part of the available space:
|
||||||
|
|
||||||
|
- `100px`: Exactly this width
|
||||||
|
- `auto`: The the widest cell in the column
|
||||||
|
- `1fr`: A certain fraction of the remaining space
|
||||||
|
|
||||||
|
The whole table always has a width of 100% (or `stretch`, if inside a flex or grid context). If the minimum sizes don't fit, all cells will shrink proportionally.
|
||||||
|
|
||||||
|
```vue-html
|
||||||
|
<Table :grid-template-columns="['100px', 'auto', '2fr', '1fr']">
|
||||||
|
<b>100px</b>
|
||||||
|
<b>auto</b>
|
||||||
|
<b>2fr</b>
|
||||||
|
<b>1fr</b>
|
||||||
|
</Table>
|
||||||
|
```
|
||||||
|
|
||||||
|
<Table :grid-template-columns="['100px', 'auto', '2fr', '1fr']">
|
||||||
|
<b>100px</b>
|
||||||
|
<b>auto</b>
|
||||||
|
<b>2fr</b>
|
||||||
|
<b>1fr</b>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
## Add a table header
|
||||||
|
|
||||||
|
Use the `#header` slot to add items to the header. Make sure to add one item per column.
|
||||||
|
|
||||||
|
```vue-html
|
||||||
|
<Table :grid-template-columns="['3fr', '1fr']">
|
||||||
|
<template #header>
|
||||||
|
<label >Column 1</label>
|
||||||
|
<label >Column 2</label>
|
||||||
|
</template>
|
||||||
|
<label>A</label>
|
||||||
|
<label>B</label>
|
||||||
|
</Table>
|
||||||
|
```
|
||||||
|
|
||||||
|
<Table :grid-template-columns="['3fr', '1fr']">
|
||||||
|
<template #header>
|
||||||
|
<label >Column 1</label>
|
||||||
|
<label >Column 2</label>
|
||||||
|
</template>
|
||||||
|
<label>A</label>
|
||||||
|
<label>B</label>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
## Let cells span multiple rows or columns
|
||||||
|
|
||||||
|
- Cells automatically expand into the **next column** if the following item is empty. Make sure the first element in each row is not empty!
|
||||||
|
- You can let elements span **multiple rows** by adding `style=grid-row: span 3`. In this case, you have to remove the corresponding cells directly below.
|
||||||
|
|
||||||
|
```vue-html
|
||||||
|
<Table :grid-template-columns="['48px', '48px', 'auto', 'auto', 'auto', '48px', '64px', '48px']">
|
||||||
|
<template #header>
|
||||||
|
<label></label>
|
||||||
|
<label></label>
|
||||||
|
<label>Title</label>
|
||||||
|
<label>Album</label>
|
||||||
|
<label>Artist</label>
|
||||||
|
<label></label>
|
||||||
|
<label>⏱</label>
|
||||||
|
<label></label>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Row 1 -->
|
||||||
|
<div> </div>
|
||||||
|
<div>C1</div>
|
||||||
|
<div>Title 1</div>
|
||||||
|
<div></div>
|
||||||
|
<div>Artist 1</div>
|
||||||
|
<div></div>
|
||||||
|
<div>D1</div>
|
||||||
|
<div>⌄</div>
|
||||||
|
|
||||||
|
<!-- Row 2 -->
|
||||||
|
<div>B2</div>
|
||||||
|
<div>C2</div>
|
||||||
|
<div>Title 2</div>
|
||||||
|
<div style="grid-row: span 2; height: auto; background: blue;">Album 2</div>
|
||||||
|
<div>Artist 2</div>
|
||||||
|
<div>F2</div>
|
||||||
|
<div>D2</div>
|
||||||
|
<div>⌄</div>
|
||||||
|
|
||||||
|
<!-- Row 3 -->
|
||||||
|
<div>B3</div>
|
||||||
|
<div>C3</div>
|
||||||
|
<div>Title 3</div>
|
||||||
|
<div>Artist 3</div>
|
||||||
|
<div>F3</div>
|
||||||
|
<div>D3</div>
|
||||||
|
<div>⌄</div>
|
||||||
|
|
||||||
|
<!-- Row 4 -->
|
||||||
|
<div>B4</div>
|
||||||
|
<div>C4</div>
|
||||||
|
<div>Title 4</div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div>F4</div>
|
||||||
|
<div>D4</div>
|
||||||
|
<div>⌄</div>
|
||||||
|
</Table>
|
||||||
|
```
|
||||||
|
|
||||||
|
<Table :grid-template-columns="['48px', '48px', 'auto', 'auto', 'auto', '48px', '64px', '48px']">
|
||||||
|
<template #header>
|
||||||
|
<label></label>
|
||||||
|
<label></label>
|
||||||
|
<label>Title</label>
|
||||||
|
<label>Album</label>
|
||||||
|
<label>Artist</label>
|
||||||
|
<label></label>
|
||||||
|
<label>⏱</label>
|
||||||
|
<label></label>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Row 1 -->
|
||||||
|
<div> </div>
|
||||||
|
<div>C1</div>
|
||||||
|
<div>Title 1</div>
|
||||||
|
<div></div>
|
||||||
|
<div>Artist 1</div>
|
||||||
|
<div></div>
|
||||||
|
<div>D1</div>
|
||||||
|
<div>⌄</div>
|
||||||
|
|
||||||
|
<!-- Row 2 -->
|
||||||
|
<div>B2</div>
|
||||||
|
<div>C2</div>
|
||||||
|
<div>Title 2</div>
|
||||||
|
<div style="grid-row: span 2; height: auto; background: blue;">Album 2</div>
|
||||||
|
<div>Artist 2</div>
|
||||||
|
<div>F2</div>
|
||||||
|
<div>D2</div>
|
||||||
|
<div>⌄</div>
|
||||||
|
|
||||||
|
<!-- Row 3 -->
|
||||||
|
<div>B3</div>
|
||||||
|
<div>C3</div>
|
||||||
|
<div>Title 3</div>
|
||||||
|
<div>Artist 3</div>
|
||||||
|
<div>F3</div>
|
||||||
|
<div>D3</div>
|
||||||
|
<div>⌄</div>
|
||||||
|
|
||||||
|
<!-- Row 4 -->
|
||||||
|
<div>B4</div>
|
||||||
|
<div>C4</div>
|
||||||
|
<div>Title 4</div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div>F4</div>
|
||||||
|
<div>D4</div>
|
||||||
|
<div>⌄</div>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
## Add props to the table header
|
||||||
|
|
||||||
|
To add class names and other properties to the table header, set the `header-props` prop. In the following example, the whole header section is made inert.
|
||||||
|
|
||||||
|
Note that `style` properties may not have an effect because the header section is `display: contents`. Instead, add a custom attribute and add scoped style rule targeting your attribute.
|
||||||
|
|
||||||
|
```vue-html
|
||||||
|
<Table
|
||||||
|
:grid-template-columns="['1fr', '1fr']"
|
||||||
|
:header-props="{ inert: '' }"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<label>
|
||||||
|
<Button low-height primary @click="console.log('clicked 1')">1</Button>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<Button low-height primary @click="console.log('clicked 2')">2</Button>
|
||||||
|
</label>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
```
|
||||||
|
|
||||||
|
<Table
|
||||||
|
:grid-template-columns="['1fr', '1fr']"
|
||||||
|
:header-props="{ inert: '' }"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<Button low-height primary @click="console.log('clicked 1')">1</Button>
|
||||||
|
<Button low-height primary @click="console.log('clicked 2')">2</Button>
|
||||||
|
</template>
|
||||||
|
</Table>
|
Loading…
Reference in New Issue