Skip to content

Commit

Permalink
Extract Spaces-Projects tiles to design-system/oc-tiles component
Browse files Browse the repository at this point in the history
  • Loading branch information
pascalwengerter committed Dec 28, 2022
1 parent c2ec30b commit fff34d0
Show file tree
Hide file tree
Showing 14 changed files with 503 additions and 310 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Add tiles component

We have added a new component to display resources as tiles.

https://github.com/owncloud/web/pull/8137
56 changes: 56 additions & 0 deletions packages/design-system/src/components/OcTiles/OcTiles.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { shallowMount } from 'web-test-helpers'
import OcList from '../OcList/OcList.vue'
import OcTiles from './OcTiles.vue'

const stubs = {
'oc-list': OcList
}

const spacesResources = [
{
name: 'Space 1',
path: '',
type: 'space',
isFolder: true,
getDriveAliasAndItem: () => '1'
},
{
name: 'Space 2',
path: '',
type: 'space',
isFolder: true,
getDriveAliasAndItem: () => '2'
}
]

describe('OcTiles component', () => {
it('renders an array of spaces correctly', () => {
const wrapper = getWrapper({ data: spacesResources })
expect(wrapper.html()).toMatchSnapshot()
})

it('renders a footer slot', () => {
const wrapper = getWrapper({}, { footer: 'Hello, OcTiles footer!' })
expect(wrapper.html()).toMatchSnapshot()
})

it('emits context menu event upon right click on tile', async () => {
const wrapper = getWrapper({ data: spacesResources })
await wrapper.find('oc-tile-stub').trigger('contextmenu')
expect(wrapper.emitted().contextmenuClicked.length).toBe(1)
})

function getWrapper(props = {}, slots = {}) {
return shallowMount(OcTiles, {
props: {
...props
},
slots: {
...slots
},
global: {
stubs
}
})
}
})
80 changes: 80 additions & 0 deletions packages/design-system/src/components/OcTiles/OcTiles.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<template>
<div>
<oc-list class="oc-tiles oc-flex">
<li v-for="(resource, index) in data" :key="resource.id" class="oc-tiles-item">
<oc-tile
:ref="`row-${index}`"
:resource="resource"
@contextmenu.native="
$emit(constants.EVENT_TROW_CONTEXTMENU, $refs[`row-${index}`][0], $event, resource)
"
>
<template #imageField="{ item }">
<slot name="image" :item="item" />
</template>
<template #actions="{ item }">
<slot name="actions" :item="item" />
</template>
</oc-tile>
</li>
</oc-list>
<!-- @slot Footer of the tiles list -->
<slot name="footer" />
</div>
</template>

<script lang="ts">
import { defineComponent, PropType } from 'vue'
import { Resource } from 'web-client'
import OcList from '../OcList/OcList.vue'
import OcTile from '../_OcTile/OcTile.vue'
// Constants should match what is being used in OcTable/ResourceTable
// Alignment regarding naming would be an API-breaking change and can
// Be done at a later point in time?
import { EVENT_TROW_CLICKED, EVENT_TROW_CONTEXTMENU } from '../../helpers/constants'
export default defineComponent({
name: 'OcTiles',
status: 'prototype',
release: 'unreleased',
components: {
OcList,
OcTile
},
props: {
/**
* Array of resources (spaces, folders, files) to be displayed as tiles
*/
data: {
type: Array as PropType<Resource[]>,
default: () => []
}
},
data() {
return {
constants: {
EVENT_TROW_CLICKED,
EVENT_TROW_CONTEXTMENU
}
}
}
})
</script>

<style lang="scss" scoped>
.oc-tiles {
flex-wrap: wrap;
gap: 15px;
justify-content: flex-start;
.oc-tiles-item {
width: 252px;
@media (max-width: $oc-breakpoint-xsmall-max) {
width: 100%;
}
}
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`OcTiles component renders a footer slot 1`] = `
<div>
<ul class="oc-list oc-my-rm oc-mx-rm oc-tiles oc-flex"></ul> Hello, OcTiles footer!
</div>
`;

exports[`OcTiles component renders an array of spaces correctly 1`] = `
<div>
<ul class="oc-list oc-my-rm oc-mx-rm oc-tiles oc-flex">
<li class="oc-tiles-item">
<oc-tile-stub resource="[object Object]"></oc-tile-stub>
</li>
<li class="oc-tiles-item">
<oc-tile-stub resource="[object Object]"></oc-tile-stub>
</li>
</ul>
</div>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,10 @@ describe('OcTableCell', () => {
width: 'shrink'
},
slots: {
deafult: 'Hello world!'
default: 'Hello world!'
}
})

expect(wrapper.element.tagName).toBe('TH')
expect(wrapper.classes()).toContain('oc-table-cell-align-right')
expect(wrapper.classes()).toContain('oc-table-cell-align-bottom')
expect(wrapper.classes()).toContain('oc-table-cell-width-shrink')
expect(wrapper).toMatchSnapshot()
})
})
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`OcTableCell Uses correct element 1`] = `<th class="oc-table-cell oc-table-cell-align-right oc-table-cell-align-bottom oc-table-cell-width-shrink"></th>`;
exports[`OcTableCell Uses correct element 1`] = `<th class="oc-table-cell oc-table-cell-align-right oc-table-cell-align-bottom oc-table-cell-width-shrink">Hello world!</th>`;
41 changes: 41 additions & 0 deletions packages/design-system/src/components/_OcTile/OcTile.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { shallowMount } from 'web-test-helpers'
import OcTile from './OcTile.vue'

const defaultSpace = {
name: 'Space 1',
path: '',
type: 'space',
isFolder: true,
getDriveAliasAndItem: () => '1'
}
const disabledSpace = {
name: 'Space 1',
path: '',
type: 'space',
isFolder: true,
disabled: true,
getDriveAliasAndItem: () => '1'
}

describe('OcTile component', () => {
it('renders default space correctly', () => {
const wrapper = getWrapper({ resource: defaultSpace })
expect(wrapper).toMatchSnapshot()
})
it('renders disabled space correctly', () => {
const wrapper = getWrapper({ resource: disabledSpace })
expect(wrapper).toMatchSnapshot()
})

function getWrapper(props = {}, slots = {}) {
return shallowMount(OcTile, {
props: {
...props
},
slots: {
actions: '',
imageField: ''
}
})
}
})
159 changes: 159 additions & 0 deletions packages/design-system/src/components/_OcTile/OcTile.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<template>
<div
class="oc-tile-card oc-card oc-card-default oc-rounded"
:data-item-id="resource.id"
:class="resource.disabled ? 'state-trashed' : ''"
>
<oc-resource-link
class="oc-card-media-top oc-border-b oc-flex oc-flex-center oc-flex-middle"
:resource="resource"
:folder-link="getResourceRoute"
@click.stop.native="tileResourceClicked"
>
<oc-tag
v-if="resource.disabled"
class="resource-disabled-indicator oc-position-absolute"
type="span"
>
<span v-translate>Disabled</span>
</oc-tag>
<!-- Slot for resource image, renders resource type icon by default -->
<slot name="imageField" :item="resource">
<oc-img v-if="resource.thumbnail" class="tile-preview" :src="resource.thumbnail" />
<oc-resource-icon
v-else
:resource="resource"
size="xxlarge"
class="tile-default-image oc-p-m"
/>
</slot>
</oc-resource-link>
<div class="oc-card-body oc-p-s">
<div class="oc-flex oc-flex-between oc-flex-middle">
<div class="oc-flex oc-flex-middle oc-text-truncate">
<oc-resource
:resource="resource"
:folder-link="getResourceRoute"
:is-resource-clickable="!resource.disabled"
@click.stop.native="tileResourceClicked"
/>
</div>
<div class="oc-flex oc-flex-middle">
<!-- Slot for individual actions -->
<slot name="actions" :item="resource" />
</div>
</div>
<p
v-if="resource.description"
class="oc-text-left oc-ml-xs oc-mt-xs oc-mb-rm oc-text-truncate"
>
<small v-text="resource.description" />
</p>
</div>
</div>
</template>

<script lang="ts">
import { defineComponent, PropType } from 'vue'
import { Resource } from 'web-client'
import OcImg from '../OcImage/OcImage.vue'
import OcResource from '../OcResource/OcResource.vue'
import OcResourceIcon from '../OcResourceIcon/OcResourceIcon.vue'
import OcResourceLink from '../OcResourceLink/OcResourceLink.vue'
import OcTag from '../OcTag/OcTag.vue'
import { createLocationSpaces } from '../../../../web-app-files/src/router'
import { createFileRouteOptions } from '../../../../web-pkg/src/helpers/router'
export default defineComponent({
name: 'OcTile',
status: 'prototype',
release: 'unreleased',
components: {
OcImg,
OcResource,
OcResourceIcon,
OcResourceLink,
OcTag
},
props: {
/**
* Resource to be displayed within the tile
*/
resource: {
type: Object as PropType<Resource>,
default: () => {}
}
},
computed: {
getResourceRoute() {
if (this.resource.type === 'space') {
return this.resource.disabled
? { path: '#' }
: createLocationSpaces(
'files-spaces-generic',
createFileRouteOptions(this.resource, { path: '', fileId: this.resource.fileId })
)
}
if (this.resource.type === 'folder') {
// return { path: this.resource.path }
}
return { path: '#' }
}
},
methods: {
tileResourceClicked() {
// Needs to handle file actions and potentially disabled folders also
if (this.resource.disabled && this.resource.type === 'space') {
this.showMessage({
title: this.$gettext('Disabled spaces cannot be entered'),
status: 'warning'
})
}
}
}
})
</script>

<style lang="scss" scoped>
.oc-tile-card {
box-shadow: none !important;
background-color: var(--oc-color-background-highlight) !important;
height: 100%;
&.state-trashed {
cursor: pointer;
.tile-image,
.tile-default-image > svg {
filter: grayscale(100%);
opacity: 80%;
}
}
.oc-card-media-top {
aspect-ratio: 16/9;
.oc-tag {
color: var(--oc-color-text-default);
&.resource-disabled-indicator {
z-index: 1;
}
}
.tile-preview {
min-width: 252px;
height: auto;
aspect-ratio: 16/9;
object-fit: cover;
}
}
.resource-name {
overflow: hidden;
color: var(--oc-color-text-default);
}
}
</style>
Loading

0 comments on commit fff34d0

Please sign in to comment.