Merge branch 'alert' into 'main'

Alert

See merge request cellule-financiere-pmo/design-system/visua-vue!11
This commit is contained in:
Paul Valerie GOMA 2025-07-27 21:28:17 +00:00
commit 06e61bd809
12 changed files with 305 additions and 6 deletions

View File

@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.11] - 2025-07-27
### Added
- Alert compoenent
- useAlert composable
## [1.0.10] - 2025-07-27
### Added
- File compoenent

View File

@ -1,3 +1,3 @@
# visua-vue
**Current version: v1.0.10**
**Current version: v1.0.11**

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "@cellule-financiere-pmo/visua-vue",
"version": "1.0.10",
"version": "1.0.11",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@cellule-financiere-pmo/visua-vue",
"version": "1.0.10",
"version": "1.0.11",
"license": "ISC",
"dependencies": {
"@cellule-financiere-pmo/visua": "1.1.3",

View File

@ -1,6 +1,6 @@
{
"name": "@cellule-financiere-pmo/visua-vue",
"version": "1.0.10",
"version": "1.0.11",
"type": "module",
"scripts": {
"dev": "vite",

View File

@ -9,7 +9,8 @@
// import VSelectView from '../template/VSelectView.vue';
// import VProgressBarView from '../template/VProgressBarView.vue';
// import VMessageView from '../template/VMessageView.vue';
import VFileUploadView from '../template/VFileUploadView.vue'
// import VFileUploadView from '../template/VFileUploadView.vue';
import VAlertView from '../template/VAlertView.vue';
</script>
@ -24,5 +25,6 @@ import VFileUploadView from '../template/VFileUploadView.vue'
<VSelectView/> -->
<!-- <VProgressBarView/> -->
<!-- <VMessageView/> -->
<VFileUploadView/>
<!-- <VFileUploadView/> -->
<VAlertView/>
</template>

View File

@ -15,3 +15,4 @@
@import './primevue-style/progressbar.css';
@import './primevue-style/fileupload.css';
@import './primevue-style/scrollpanel.css';
@import './primevue-style/toast.css';

View File

@ -0,0 +1,37 @@
:root{
--p-toast-width: 25rem;
--p-toast-border-radius: 0px;
--p-toast-border-width: var(--large-border-width);
--p-toast-icon-size: 1.5rem;
--p-toast-content-padding: 0.25rem;
--p-toast-content-gap: 0.5rem;
--p-toast-text-gap: 0.5rem;
--p-toast-summary-font-weight: var( --text-body-MD-standard-text-weight);
--p-toast-summary-font-size: var(--text-body-MD-standard-text-size);
--p-toast-detail-font-weight: var( --text-body-MD-standard-text-Regular-weight);
--p-toast-detail-font-size: var(--text-body-MD-standard-text-Regular-size);
/* info */
--p-toast-info-background: var(--background-contrast-info);
--p-toast-info-border-color: var(--border-plain-info);
--p-toast-info-color: var(--text-default-info);
--p-toast-info-detail-color: var(--input-color);
--p-toast-info-shadow: var(--shadow);
/* success */
--p-toast-success-background: var(--background-contrast-success);
--p-toast-success-border-color: var(--border-plain-success);
--p-toast-success-color: var(--text-default-success);
--p-toast-success-detail-color: var(--input-color);
--p-toast-success-shadow: var(--shadow);
/* warn */
--p-toast-warn-background: var(--background-contrast-warning);
--p-toast-warn-border-color: var(--border-plain-warning);
--p-toast-warn-color: var(--text-default-warning);
--p-toast-warn-detail-color: var(--input-color);
--p-toast-warn-shadow: var(--shadow);
/* error */
--p-toast-error-background: var(--background-contrast-error);
--p-toast-error-border-color: var(--border-plain-error);
--p-toast-error-color: var(--text-default-error);
--p-toast-error-detail-color: var(--input-color);
--p-toast-error-shadow: var(--shadow);
}

View File

@ -0,0 +1,62 @@
/**
* Interface representing the properties of an Alert component.
*/
export default interface IVAlert {
/**
* Determines if the alert should be visible.
* @default false
*/
alert?: boolean;
/**
* Indicates whether the alert has been closed.
* @default false
*/
closed?: boolean;
/**
* Specifies if the alert can be closed by the user.
* @default false
*/
closeable?: boolean;
/**
* Unique identifier for the alert instance.
*/
id?: string;
/**
* Title displayed at the top of the alert.
*/
title?: string;
/**
* Detailed description or message content of the alert.
*/
description?: string;
/**
* Determines if a smaller variant of the alert should be displayed.
* @default false
*/
small?: boolean;
/**
* Type of alert, affecting its visual style and icon.
* - `"success"`: Indicates a successful or positive action.
* - `"error"`: Indicates an error or critical issue.
* - `"info"`: Provides general information.
* - `"warn"`: Indicates a warning or potential issue.
*/
type?: "success" | "error" | "info" | "warn" | undefined;
/**
* Label for the close button, useful for accessibility.
*/
closeButtonLabel?: string;
/**
* Time in milliseconds after which the alert automatically disappears.
*/
lifeTime?: number;
}

View File

@ -0,0 +1,106 @@
<script setup lang="ts">
import Toast from 'primevue/toast';
import VButton from '../button/VButton.vue';
import type { ToastProps } from 'primevue/toast';
import styles from '@visua/typography.module.css';
const props = withDefaults(defineProps<ToastProps>(), {
group: undefined,
position: 'bottom-center',
breakpoints: undefined
});
const emit = defineEmits([
'close',
'life-end'
]);
const getIconColor = (type?: string) => {
switch (type) {
case 'error': return 'var(--text-default-error)';
case 'warn' : return 'var(--text-default-warning)';
case 'success' : return 'var(--text-default-success)';
case 'info': return 'var(--text-default-info)';
default:
return 'var(--text-default-success)';
}
};
const getIconClass = (type?: string) => {
switch (type) {
case 'error': return 'ri-spam-fill';
case 'warn' : return 'ri-alert-fill';
case 'success' : return 'ri-checkbox-circle-fill';
case 'info': return 'ri-information-fill';
default:
return 'ri-information-fill';
}
};
</script>
<template>
<Toast
:position=props.position
:group="props.group"
:breakpoints="props.breakpoints"
class="p-toast"
role="alert"
aria-live="assertive"
aria-atomic="true"
@close="emit('close', $event)"
@life-end="emit('life-end', $event)"
>
<template #container="{message, closeCallback}">
<div class="header">
<i
style="font-size: var(--p-message-icon-lg-size);"
:class="getIconClass(message.severity)"
:style="{color: getIconColor(message.severity)}"
/>
<span
:class="[styles['text-body-MD-standard-text-Medium']]"
:style="{color: getIconColor(message.severity)}"
style="width: 100%;"
>
{{ message.summary }}
</span>
<VButton
title="Fermer le message"
tertiary
no-outline
size="sm"
icon-only
aria-label="Fermer"
icon="ri-close-line"
type="button"
@click="closeCallback"
style="height: 2rem;"
/>
</div>
<div
v-if="!!message.detail"
:class="['content', styles['text-body-MD-standard-text-Regular']]"
>
<span>{{ message.detail }}</span>
<slot name="footer"/>
</div>
</template>
</Toast>
</template>
<style lang="css" scoped>
.header{
width: 100%;
display: flex;
flex-direction: row;
gap: 0.25rem;
padding: var(--p-toast-text-gap);
color: var(--text-title-grey);
align-items: center;
}
.content{
padding: 0rem 0.825rem 0.5rem 0.825rem;
color: var(--text-default-grey);
}
</style>

View File

@ -0,0 +1,24 @@
import { useToast } from "primevue/usetoast";
import type IVAlert from "@/components/alert/IVAlert.type";
export function useAlert() {
const toast = useToast();
const showAlert = ({
title = '',
description = '',
type = 'info',
closeable = true,
lifeTime,
}: IVAlert) => {
toast.add({
severity: type,
summary: title,
detail: description,
life: lifeTime,
closable: closeable,
})
}
return { showAlert}
}

View File

@ -2,9 +2,11 @@ import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
import primeVue from 'primevue/config'
import ToastService from 'primevue/toastservice'
const app = createApp(App)
app.use(primeVue)
app.use(ToastService)
app.mount('#app')

60
test/VAlert.spec.ts Normal file
View File

@ -0,0 +1,60 @@
import { mount, flushPromises } from '@vue/test-utils';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import VAlert from '../src/components/alert/VAlert.vue';
import Toast from 'primevue/toast';
import ToastService from 'primevue/toastservice';
vi.mock('primevue/toastservice', () => ({
default: {
install: () => {},
add: vi.fn()
}
}));
describe('VAlert.vue', () => {
let wrapper: ReturnType<typeof mount>;
beforeEach(() => {
const container = document.createElement('div');
container.id = 'app';
document.body.appendChild(container);
wrapper = mount(VAlert, {
attachTo: '#app',
global: {
components: { Toast },
plugins: [ToastService]
},
props: {
position: 'top-right'
}
});
});
// 💡 Helper pour injecter un toast et attendre le rendu
const renderToast = async (payload: { severity: string; summary: string; detail: string; life: number }) => {
wrapper.vm.$toast?.add(payload);
await new Promise(resolve => setTimeout(resolve, 50));
await flushPromises();
};
it('renders the Toast container via teleport', async () => {
await renderToast({
severity: 'success',
summary: 'Test Title',
detail: 'Test description',
life: 3000
});
const toast = document.querySelector('.p-toast');
expect(toast).toBeTruthy();
});
it('responds to "close" and "life-end" events', async () => {
await wrapper.vm.$emit('close', { id: 'test-alert' });
await wrapper.vm.$emit('life-end', { id: 'test-alert' });
expect(wrapper.emitted()).toHaveProperty('close');
expect(wrapper.emitted()).toHaveProperty('life-end');
});
});