Skip to content
Merged
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
4 changes: 4 additions & 0 deletions packages/design-system/docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ export default defineConfig({
text: 'OcErrorLog',
link: '/OcErrorLog'
},
{
text: 'OcFloatingActionButton',
link: '/OcFloatingActionButton'
},
{
text: 'OcFileInput',
link: '/OcFileInput'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
title: OcFloatingActionButton component
next: false
prev: false
---

# OcFloatingActionButton component

## Description

The `OcFloatingActionButton` component displays a button that floats above an interface and represents the primary or
most common action of a screen.

## Examples

### Default

The default use case displays a menu of stacked buttons.

::: livecode

```html
<oc-floating-action-button
aria-label="Floating action button"
class="!static"
:items="[
{ label: 'File', icon: 'file', to: { path: '/' } },
{ label: 'Folder', icon: 'folder', to: { path: '/' } },
{ label: 'Public link', icon: 'link', to : { path: '/' } }
]"
/>
```

:::

### Action mode

While setting `mode` to `action`, only the primary Floating Action Button will be displayed.

::: livecode

```html
<oc-floating-action-button
aria-label="Floating action button"
mode="action"
class="!static"
:to="{ path: '/' }"
/>
```

:::

::: component-api
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<template>
<div class="fixed flex flex-col items-end bottom-[20px] right-[20px]">
<template v-if="expanded">
<oc-button
v-for="item in items"
:key="item.label"
class="mb-2 rounded-full"
appearance="filled"
color-role="primary"
:type="item.to ? 'router-link' : 'button'"
:to="item.to"
@click="onChildButtonClicked(item)"
>
<oc-icon :name="item.icon" />
<span v-text="item.label" />
</oc-button>
</template>
<oc-button
class="rounded-full size-14"
appearance="filled"
color-role="primary"
:aria-label="computedAriaLabel"
:type="mode === 'action' && to ? 'router-link' : 'button'"
:to="to"
@click="onPrimaryButtonClicked"
>
<oc-icon :name="expanded ? 'close' : 'add'" fill-type="line" />
</oc-button>
</div>
</template>
<script setup lang="ts">
import { computed, ref, unref } from 'vue'
import { RouteLocationRaw } from 'vue-router'
import { useGettext } from 'vue3-gettext'

export interface Props {
/**
* @docs The aria label of the primary action button.
* @default 'Open actions menu'
*/
ariaLabel?: string
/**
* @docs The mode of the floating action button.
* @default menu
*/
mode?: 'action' | 'menu'
/**
* @docs The route location of the primary action button when the `mode` is set to `action`.
*/
to?: RouteLocationRaw
/**
* @docs The handler of the primary action button when the `mode` is set to `action`.
*/
handler?: () => void
/**
* @docs The menu items of the floating action button element.
*/
items?: {
icon: string
label: string
handler?: () => void
to?: RouteLocationRaw
}[]
}

const {
mode = 'menu',
ariaLabel = '',
items = [],
handler = null,
to = null
} = defineProps<Props>()

const { $gettext } = useGettext()

const expanded = ref(false)

const computedAriaLabel = computed(() => {
return ariaLabel ? ariaLabel : $gettext('Open actions menu')
})

const onPrimaryButtonClicked = () => {
if (mode === 'action') {
handler?.()
return
}

expanded.value = !unref(expanded)
}

const onChildButtonClicked = (item: Props['items'][number]) => {
item.handler?.()
expanded.value = false
}
</script>
1 change: 1 addition & 0 deletions packages/design-system/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ export { default as OcTextarea } from './OcTextarea/OcTextarea.vue'
export { default as OcTextInput } from './OcTextInput/OcTextInput.vue'
export { default as OcErrorLog } from './OcErrorLog/OcErrorLog.vue'
export { default as OcEmojiPicker } from './OcEmojiPicker/OcEmojiPicker.vue'
export { default as OcFloatingActionButton } from './OcFloatingActionButton/OcFloatingActionButton.vue'
2 changes: 2 additions & 0 deletions packages/design-system/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import OcTextarea from './src/components/OcTextarea/OcTextarea.vue'
import OcTextInput from './src/components/OcTextInput/OcTextInput.vue'
import OcErrorLog from './src/components/OcErrorLog/OcErrorLog.vue'
import OcEmojiPicker from './src/components/OcEmojiPicker/OcEmojiPicker.vue'
import OcFloatingActionButton from './src/components/OcFloatingActionButton/OcFloatingActionButton.vue'

declare module 'vue' {
interface GlobalComponents {
Expand Down Expand Up @@ -111,5 +112,6 @@ declare module 'vue' {
OcTextInput: typeof OcTextInput
OcErrorLog: typeof OcErrorLog
OcEmojiPicker: typeof OcEmojiPicker
OcFloatingActionButton: typeof OcFloatingActionButton
}
}
18 changes: 6 additions & 12 deletions packages/web-app-mail/src/components/MailList.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
<template>
<app-loading-spinner v-if="isLoading" />
<template v-else>
<oc-floating-action-button
class="md:hidden"
mode="action"
:aria-label="$gettext('Write new Email')"
:to="{ name: 'mail-create', query: { ...route.query, draftId: 'new' } }"
/>
<no-content-message v-if="!currentMailbox" icon="folder" icon-fill-type="line">
<template #message>
<span v-text="$gettext('No mailbox selected')" />
Expand All @@ -20,18 +26,6 @@
<h2 class="text-lg ml-4" v-text="currentMailbox.name"></h2>
<div class="paceholder" />
</div>
<div class="py-2 px-4">
<oc-button
id="new-email-menu-btn"
type="router-link"
:to="{ name: 'mail-create', query: { ...route.query, draftId: 'new' } }"
class="w-full"
appearance="filled"
>
<oc-icon name="edit-box" fill-type="line" />
<span v-text="$gettext('Write new Email')" />
</oc-button>
</div>
<no-content-message
v-if="!mails || !mails.length"
class="mail-list-empty"
Expand Down
16 changes: 14 additions & 2 deletions packages/web-app-mail/src/components/MailboxTree.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
<template>
<div class="mailbox-tree h-full px-1 flex flex-col">
<div>
<h1 v-if="currentAccount" class="text-lg ml-4 truncate" v-text="currentAccount.name" />
<div class="px-2 py-4">
<oc-button
id="new-email-menu-btn"
type="router-link"
:to="{ name: 'mail-create', query: { ...route.query, draftId: 'new' } }"
class="w-full hidden md:flex"
appearance="filled"
>
<oc-icon name="edit-box" fill-type="line" />
<span v-text="$gettext('Write new Email')" />
</oc-button>
</div>
<app-loading-spinner v-if="isLoading" />
<template v-else>
<no-content-message v-if="!mailboxes?.length" icon="folder-reduce" icon-fill-type="line">
Expand Down Expand Up @@ -49,7 +60,7 @@

<script setup lang="ts">
import type { Mailbox } from '../types'
import { AppLoadingSpinner, NoContentMessage } from '@opencloud-eu/web-pkg'
import { AppLoadingSpinner, NoContentMessage, useRoute } from '@opencloud-eu/web-pkg'
import { useLoadMailboxes } from '../composables/useLoadMailboxes'
import { useMailboxesStore } from '../composables/piniaStores/mailboxes'
import { storeToRefs } from 'pinia'
Expand All @@ -67,6 +78,7 @@ const { currentAccount } = storeToRefs(accountsStore)
const { setCurrentMail } = useMailsStore()
const { loadMails } = useLoadMails()
const { isLoading } = useLoadMailboxes()
const route = useRoute()

const onSelectMailbox = async (mailbox: Mailbox) => {
setCurrentMailbox(mailbox)
Expand Down