Skip to content

Commit

Permalink
feat: add NeToastNotification component (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
andre8244 authored Apr 19, 2024
1 parent a25db1c commit 8b7587e
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 0 deletions.
160 changes: 160 additions & 0 deletions src/components/NeToastNotification.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
<!--
Copyright (C) 2024 Nethesis S.r.l.
SPDX-License-Identifier: GPL-3.0-or-later
-->

<script setup lang="ts">
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { faXmark as fasXmark } from '@fortawesome/free-solid-svg-icons'
import { library } from '@fortawesome/fontawesome-svg-core'
import { humanDistanceToNowLoc, formatDateLoc } from '../main'
import NeButton from './NeButton.vue'
import NeRoundedIcon from './NeRoundedIcon.vue'
import NeTooltip from './NeTooltip.vue'
import { type PropType } from 'vue'

export interface NeNotification {
id: string
kind: 'info' | 'warning' | 'error' | 'success'
title: string
description?: string
timestamp?: Date
payload?: any
isShown?: boolean
primaryLabel?: string
primaryAction?: Function
secondaryLabel?: string
secondaryAction?: Function
}

defineProps({
notification: {
type: Object as PropType<NeNotification>,
required: true
},
srCloseLabel: {
type: String,
required: true
},
primaryButtonRightAligned: {
type: Boolean,
default: false
},
showCloseButton: {
type: Boolean,
default: false
},
showTimestamp: {
type: Boolean,
default: false
},
fullWidth: {
type: Boolean,
default: false
}
})

defineEmits(['close'])

// add fontawesome icons
library.add(fasXmark)
</script>

<template>
<div
:class="[
`pointer-events-auto w-full overflow-hidden rounded-lg bg-white text-sm shadow-lg ring-1 ring-black ring-opacity-5 dark:bg-gray-800 dark:shadow-gray-950`,
{ 'max-w-sm': !fullWidth }
]"
>
<div class="relative p-4">
<div
v-if="showCloseButton || (showTimestamp && notification.timestamp)"
class="absolute right-0 top-0 block pr-4 pt-4"
>
<!-- close button -->
<button
v-if="showCloseButton"
type="button"
class="rounded-md leading-none text-gray-600 transition-colors hover:text-gray-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 focus:ring-offset-white dark:text-gray-300 dark:hover:text-gray-200 dark:focus:ring-primary-300 dark:focus:ring-offset-gray-900"
@click="$emit('close')"
>
<span class="sr-only">{{ srCloseLabel }}</span>
<font-awesome-icon :icon="['fas', 'xmark']" class="h-5 w-5" aria-hidden="true" />
</button>
<!-- timestamp -->
<NeTooltip
v-else-if="showTimestamp && notification.timestamp"
placement="left-start"
class="text-gray-500 dark:text-gray-400"
>
<template #trigger>
{{ humanDistanceToNowLoc(notification.timestamp) }}
</template>
<template #content>
{{ formatDateLoc(notification.timestamp, 'Pp') }}
</template>
</NeTooltip>
</div>
<div class="flex items-start">
<div class="flex-shrink-0">
<!-- custom icon -->
<template v-if="$slots.icon">
<slot name="icon" />
</template>
<!-- standard icon -->
<NeRoundedIcon v-else :kind="notification.kind" />
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p
:class="[
'font-semibold text-gray-900 dark:text-gray-50',
{
'mr-6': showCloseButton,
'!mr-24': showTimestamp && notification.timestamp && !showCloseButton
}
]"
>
{{ notification.title }}
</p>
<p
v-if="notification.description"
class="mt-2 break-words text-gray-700 dark:text-gray-200"
>
{{ notification.description }}
</p>
</div>
</div>
<div
v-if="notification.primaryLabel || notification.secondaryLabel"
class="ml-10 flex flex-shrink-0"
>
<div
:class="[
`mt-4 flex grow justify-end`,
primaryButtonRightAligned ? 'flex-row' : 'flex-row-reverse'
]"
>
<NeButton
v-if="notification.secondaryLabel && notification.secondaryAction"
kind="tertiary"
size="md"
class="ml-3 mt-0 w-auto"
@click="notification.secondaryAction"
>
{{ notification.secondaryLabel }}
</NeButton>
<NeButton
v-if="notification.primaryLabel && notification.primaryAction"
kind="primary"
size="md"
class="ml-3 w-auto"
@click="notification.primaryAction"
>
{{ notification.primaryLabel }}
</NeButton>
</div>
</div>
</div>
</div>
</template>
2 changes: 2 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@ export { default as NeTabs } from '@/components/NeTabs.vue'
export { default as NeTextArea } from '@/components/NeTextArea.vue'
export { default as NeTextInput } from '@/components/NeTextInput.vue'
export { default as NeToggle } from '@/components/NeToggle.vue'
export { default as NeToastNotification } from '@/components/NeToastNotification.vue'

// types export
export type { NeComboboxOption } from '@/components/NeCombobox.vue'
export type { NePaginatorProps } from '@/components/NePaginator.vue'
export type { Tab } from '@/components/NeTabs.vue'
export type { NeNotification } from '@/components/NeToastNotification.vue'

// library functions export
export {
Expand Down
77 changes: 77 additions & 0 deletions stories/NeToastNotification.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (C) 2024 Nethesis S.r.l.
// SPDX-License-Identifier: GPL-3.0-or-later

import type { Meta, StoryObj } from '@storybook/vue3'
import { NeToastNotification, NeNotification, NeRoundedIcon } from '../src/main'
import { faHeart } from '@fortawesome/free-solid-svg-icons'
import { library } from '@fortawesome/fontawesome-svg-core'

const meta = {
title: 'Visual/NeToastNotification',
component: NeToastNotification,
args: {
srCloseLabel: 'Close',
primaryButtonRightAligned: false,
showCloseButton: false,
showTimestamp: false,
fullWidth: false,
notification: undefined
}
} satisfies Meta<typeof NeToastNotification>

export default meta
type Story = StoryObj<typeof meta>

library.add(faHeart)

const notification: NeNotification = {
id: '1',
kind: 'info',
title: 'Toast title',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
timestamp: new Date(),
payload: undefined,
isShown: true,
primaryLabel: 'Primary',
primaryAction: () => {
console.log('Primary action')
},
secondaryLabel: 'Secondary',
secondaryAction: () => {
console.log('Secondary action')
}
}

const template = '<NeToastNotification v-bind="args"/>'

export const Default: Story = {
render: (args) => ({
components: { NeToastNotification },
setup() {
return { args }
},
template: template
}),
args: { notification }
}

const templateWithIconSlot = `<NeToastNotification v-bind="args">
<template #icon>
<NeRoundedIcon
:customIcon="['fas', 'heart']"
customForegroundClasses="text-fuchsia-700 dark:text-fuchsia-50"
customBackgroundClasses="bg-fuchsia-100 dark:bg-fuchsia-700"
/>
</template>
</NeToastNotification>`

export const IconSlot: Story = {
render: (args) => ({
components: { NeToastNotification, NeRoundedIcon },
setup() {
return { args }
},
template: templateWithIconSlot
}),
args: { notification }
}

0 comments on commit 8b7587e

Please sign in to comment.