Merge branch 'progress' into 'main'
Progress See merge request cellule-financiere-pmo/design-system/visua-vue!8
This commit is contained in:
commit
b8458d6c2f
|
@ -5,6 +5,15 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.8] - 2025-07-23
|
||||||
|
### Added
|
||||||
|
- ProgressBar component
|
||||||
|
|
||||||
|
## [1.0.7] - 2025-07-23
|
||||||
|
### Added
|
||||||
|
- Select component
|
||||||
|
- Badge component
|
||||||
|
|
||||||
## [1.0.6] - 2025-07-23
|
## [1.0.6] - 2025-07-23
|
||||||
### Added
|
### Added
|
||||||
- Checkbox component
|
- Checkbox component
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
# visua-vue
|
# visua-vue
|
||||||
|
|
||||||
**Current version: v1.0.6**
|
**Current version: v1.0.8**
|
4
package-lock.json
generated
4
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@cellule-financiere-pmo/visua-vue",
|
"name": "@cellule-financiere-pmo/visua-vue",
|
||||||
"version": "1.0.6",
|
"version": "1.0.8",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@cellule-financiere-pmo/visua-vue",
|
"name": "@cellule-financiere-pmo/visua-vue",
|
||||||
"version": "1.0.6",
|
"version": "1.0.8",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cellule-financiere-pmo/visua": "1.1.3",
|
"@cellule-financiere-pmo/visua": "1.1.3",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@cellule-financiere-pmo/visua-vue",
|
"name": "@cellule-financiere-pmo/visua-vue",
|
||||||
"version": "1.0.6",
|
"version": "1.0.8",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|
10
src/App.vue
10
src/App.vue
|
@ -4,7 +4,10 @@
|
||||||
// import VLinkView from '../template/VLinkView.vue';
|
// import VLinkView from '../template/VLinkView.vue';
|
||||||
// import VAccordionView from '../template/VAccordionView.vue';
|
// import VAccordionView from '../template/VAccordionView.vue';
|
||||||
// import VInputView from '../template/VInputView.vue';
|
// import VInputView from '../template/VInputView.vue';
|
||||||
import VCheckboxView from '../template/VCheckboxView.vue';
|
// import VCheckboxView from '../template/VCheckboxView.vue';
|
||||||
|
// import VBadgeView from '../template/VBadgeView.vue';
|
||||||
|
// import VSelectView from '../template/VSelectView.vue';
|
||||||
|
import VProgressBarView from '../template/VProgressBarView.vue';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,5 +17,8 @@ import VCheckboxView from '../template/VCheckboxView.vue';
|
||||||
<!-- <VLinkView/> -->
|
<!-- <VLinkView/> -->
|
||||||
<!-- <VAccordionView/> -->
|
<!-- <VAccordionView/> -->
|
||||||
<!-- <VInputView/> -->
|
<!-- <VInputView/> -->
|
||||||
<VCheckboxView/>
|
<!-- <VCheckboxView/> -->
|
||||||
|
<!-- <VBadgeView/> -->
|
||||||
|
<!-- <VSelectView/> -->
|
||||||
|
<VProgressBarView/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -8,3 +8,8 @@
|
||||||
@import './primevue-style/various.css';
|
@import './primevue-style/various.css';
|
||||||
@import './primevue-style/form.css';
|
@import './primevue-style/form.css';
|
||||||
@import './primevue-style/checkbox.css';
|
@import './primevue-style/checkbox.css';
|
||||||
|
@import './primevue-style/tag.css';
|
||||||
|
@import './primevue-style/select.css';
|
||||||
|
@import './primevue-style/overlay.css';
|
||||||
|
@import './primevue-style/iconfield.css';
|
||||||
|
@import './primevue-style/progressbar.css';
|
||||||
|
|
3
src/assets/style/primevue-style/iconfield.css
Normal file
3
src/assets/style/primevue-style/iconfield.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
:root{
|
||||||
|
--p-iconfield-icon-color: var(--input-color);
|
||||||
|
}
|
17
src/assets/style/primevue-style/overlay.css
Normal file
17
src/assets/style/primevue-style/overlay.css
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
:root{
|
||||||
|
--p-overlay-modal-background: var(--background-lifted-grey);
|
||||||
|
--p-overlay-modal-border-color: var(--border-default-grey);
|
||||||
|
--p-overlay-modal-color: var(--text-default-grey);
|
||||||
|
--p-overlay-popover-background: var(--background-lifted-grey);
|
||||||
|
--p-overlay-popover-border-color: var(--border-default-grey);
|
||||||
|
--p-overlay-popover-color: var(--text-default-grey);
|
||||||
|
--p-overlay-navigation-shadow: var(--shadow);
|
||||||
|
--p-overlay-modal-border-radius: 0px;
|
||||||
|
--p-overlay-modal-padding: 1.25rem;
|
||||||
|
--p-overlay-modal-shadow: var(--shadow);
|
||||||
|
--p-overlay-popover-border-radius: 0px;
|
||||||
|
--p-overlay-popover-padding: 0.75rem;
|
||||||
|
--p-overlay-popover-shadow: var(--shadow);
|
||||||
|
--p-overlay-select-border-radius: 0px;
|
||||||
|
--p-overlay-select-shadow: var(--shadow);
|
||||||
|
}
|
9
src/assets/style/primevue-style/progressbar.css
Normal file
9
src/assets/style/primevue-style/progressbar.css
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
:root{
|
||||||
|
--p-progressbar-background: var(--background-contrast-grey);
|
||||||
|
--p-progressbar-height: 1rem;
|
||||||
|
--p-progressbar-border-radius: 0.75rem;
|
||||||
|
--p-progressbar-value-background: var(--background-active-blue-france);
|
||||||
|
--p-progressbar-label-color: var(--text-inverted-grey);
|
||||||
|
--p-progressbar-label-font-size: var(--text-body-XS-mention-text-Medium-size);
|
||||||
|
--p-progressbar-label-font-weight: var(--text-body-XS-mention-text-Medium-weight);
|
||||||
|
}
|
47
src/assets/style/primevue-style/select.css
Normal file
47
src/assets/style/primevue-style/select.css
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
:root{
|
||||||
|
--p-select-background: var(--input-background);
|
||||||
|
--p-select-disabled-background: var(--input-disabled-background);
|
||||||
|
--p-select-border-color: var(--input-border-color);
|
||||||
|
--p-select-hover-border-color: var(--input-border-color);
|
||||||
|
--p-select-color: var(--input-color);
|
||||||
|
--p-select-disabled-color: var(--input-disabled-color);
|
||||||
|
--p-select-placeholder-color: var(--input-color);
|
||||||
|
--p-select-padding-x: var(--input-padding-x);
|
||||||
|
--p-select-padding-y: var(--input-padding-y);
|
||||||
|
--p-select-border-radius: var(--input-border-raduis);
|
||||||
|
--p-select-dropdown-width: 2rem;
|
||||||
|
--p-select-dropdown-color: var(--input-color);
|
||||||
|
--p-select-overlay-background: var(--input-background);
|
||||||
|
--p-select-overlay-border-color: var(--input-border-color);
|
||||||
|
--p-select-overlay-border-radius: 0px 0px 0.25rem 0.25rem;
|
||||||
|
--p-select-overlay-color: var(--input-color);
|
||||||
|
--p-select-overlay-shadow: var(--shadow);
|
||||||
|
--p-select-list-padding: 0.25rem;
|
||||||
|
--p-select-list-gap: 0.125rem;
|
||||||
|
--p-select-list-header-padding: 0.5rem 1rem;
|
||||||
|
/* options */
|
||||||
|
--p-select-option-selected-background: var(--background-active-blue-france);
|
||||||
|
--p-select-option-selected-focus-background: var(--background-active-blue-france);
|
||||||
|
--p-select-option-color: var(--input-color);
|
||||||
|
--p-select-option-focus-color: var(--input-color);
|
||||||
|
--p-select-option-selected-color: var(--text-inverted-blue-france);
|
||||||
|
--p-select-option-padding: 0.5rem 0.75rem;
|
||||||
|
--p-select-option-border-radius: 0px;
|
||||||
|
--p-select-option-group-background: var(--input-background);
|
||||||
|
--p-select-option-group-color: var(--input-color);
|
||||||
|
--p-select-option-group-font-weight: var(--text-body-MD-standard-text-Regular-weight);
|
||||||
|
--p-select-option-group-padding: 0.25rem;
|
||||||
|
--p-select-clear-icon-color: var(--input-color);
|
||||||
|
--p-select-checkmark-color: var(--input-color);
|
||||||
|
--p-select-checkmark-gutter-start: 0.125rem;
|
||||||
|
--p-select-checkmark-gutter-end: 0.125rem;
|
||||||
|
--p-select-empty-message-padding: 0.25rem;
|
||||||
|
/* focus */
|
||||||
|
--p-select-focus-border-color: var(--input-border-color);
|
||||||
|
--p-select-focus-ring-width: var(--focus-width);
|
||||||
|
--p-select-focus-ring-style: var(--focus-style);
|
||||||
|
--p-select-focus-ring-color: var(--focus-color);
|
||||||
|
--p-select-focus-ring-offset: var(--focus-offset);
|
||||||
|
--p-select-option-focus-background: var(--background-transparent-active);
|
||||||
|
--p-select-option-selected-focus-color: var(--text-inverted-blue-france);
|
||||||
|
}
|
23
src/assets/style/primevue-style/tag.css
Normal file
23
src/assets/style/primevue-style/tag.css
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
:root {
|
||||||
|
--p-tag-icon-size: var(--text-body-MD-standard-text-Regular-size);
|
||||||
|
--p-tag-font-size: var( --text-body-SM-detail-text-Maj-size);
|
||||||
|
--p-tag-font-weight: var( --text-body-SM-detail-text-Maj-weight);
|
||||||
|
--p-tag-padding: 0.25rem 0.5rem;
|
||||||
|
--p-tag-gap: 0.25rem;
|
||||||
|
--p-tag-border-radius: 0.25rem;
|
||||||
|
/* --p-tag-rounded-border-radius:
|
||||||
|
--p-tag-contrast-background: */
|
||||||
|
--p-tag-contrast-color: var(--p-surface-0);
|
||||||
|
--p-tag-danger-background: var(--background-contrast-error);
|
||||||
|
--p-tag-danger-color: var(--text-default-error);
|
||||||
|
--p-tag-warn-background: var(--background-contrast-warning);
|
||||||
|
--p-tag-warn-color: var(--text-default-warning);
|
||||||
|
--p-tag-info-background: var(--background-contrast-info);
|
||||||
|
--p-tag-info-color: var(--text-default-info);
|
||||||
|
--p-tag-success-background: var(--background-contrast-success);
|
||||||
|
--p-tag-success-color: var(--text-default-success);
|
||||||
|
--p-tag-secondary-background: var(--illustration-color-950-tournesol-default);
|
||||||
|
--p-tag-secondary-color: var(--illustration-color-sun-tournesol-default);
|
||||||
|
--p-tag-primary-background: var(--illustration-color-950-glycine-default);
|
||||||
|
--p-tag-primary-color: var(--illustration-color-sun-glycine-default);
|
||||||
|
}
|
38
src/components/badge/IVBadge.type.ts
Normal file
38
src/components/badge/IVBadge.type.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
/**
|
||||||
|
* Interface representing the properties of a Badge component.
|
||||||
|
*/
|
||||||
|
export default interface IVBadge {
|
||||||
|
/**
|
||||||
|
* The text label displayed inside the badge.
|
||||||
|
*/
|
||||||
|
label: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of badge, which determines its color and icon.
|
||||||
|
* - `success`: Indicates a positive or successful status.
|
||||||
|
* - `error`: Indicates an error or negative status.
|
||||||
|
* - `new`: Highlights something new or recently added.
|
||||||
|
* - `info`: Provides informational context.
|
||||||
|
* - `warning`: Warns the user about a potential issue.
|
||||||
|
* - **undefined**: Defaults to a neutral badge style.
|
||||||
|
*/
|
||||||
|
type?: 'success' | 'error' | 'new' | 'info' | 'warning' | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, the badge will be displayed without an icon.
|
||||||
|
* Defaults to false.
|
||||||
|
*/
|
||||||
|
noIcon?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, the badge will be rendered in a smaller size.
|
||||||
|
* Useful for compact UI elements.
|
||||||
|
*/
|
||||||
|
small?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional maximum width for the badge.
|
||||||
|
* Accepts any valid CSS width value (e.g., '100px', '10rem', '50%').
|
||||||
|
*/
|
||||||
|
maxWidth?: string;
|
||||||
|
}
|
71
src/components/badge/VBadge.vue
Normal file
71
src/components/badge/VBadge.vue
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type IVBadge from './IVBadge.type';
|
||||||
|
import Tag from 'primevue/tag';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<IVBadge>(), {
|
||||||
|
type: undefined,
|
||||||
|
noIcon: false,
|
||||||
|
small: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const severity = computed(() => {
|
||||||
|
switch (props.type) {
|
||||||
|
case 'error': return 'danger';
|
||||||
|
case 'warning' : return 'warn';
|
||||||
|
case 'success' : return 'success';
|
||||||
|
case 'info': return 'info';
|
||||||
|
case 'new': return 'secondary';
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const icon = computed(() => {
|
||||||
|
switch (props.type) {
|
||||||
|
case 'error': return 'ri-close-circle-line';
|
||||||
|
case 'warning' : return 'ri-alert-line';
|
||||||
|
case 'success' : return 'ri-checkbox-circle-line';
|
||||||
|
case 'info': return 'ri-information-line';
|
||||||
|
case 'new': return 'ri-flashlight-line';
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const limit = computed(() => props.maxWidth);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Tag
|
||||||
|
role="alert"
|
||||||
|
:value="props.label"
|
||||||
|
:severity="severity"
|
||||||
|
:title="props.label"
|
||||||
|
class="p-tag"
|
||||||
|
:class="{'small': props.small}"
|
||||||
|
>
|
||||||
|
<i v-if="!props.noIcon || props.type === undefined" :class="icon"></i>
|
||||||
|
<span :class="{'limit': props.maxWidth}">{{ props.label }}</span>
|
||||||
|
</Tag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.p-tag{height: fit-content;}
|
||||||
|
.p-tag.small{
|
||||||
|
--p-tag-icon-size: var( --text-body-SM-detail-text-Maj-size);
|
||||||
|
--p-tag-font-size: var( --text-body-XS-mention-text-Maj-size);
|
||||||
|
--p-tag-font-weight: var( --text-body-XS-mention-text-Maj-weight);
|
||||||
|
--p-tag-padding: 0.125rem 0.325rem;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.limit{
|
||||||
|
max-width: v-bind(limit);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
30
src/components/progressbar/IVProgressBar.type.ts
Normal file
30
src/components/progressbar/IVProgressBar.type.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
* Interface representing the properties of a ProgressBar component.
|
||||||
|
*/
|
||||||
|
export default interface IVProgressBar {
|
||||||
|
/**
|
||||||
|
* Whether to display the numeric value of the progress.
|
||||||
|
* If true, the value will be shown alongside the progress bar.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
showValue?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current progress value, typically between 0 and 100.
|
||||||
|
*/
|
||||||
|
value: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, the progress bar will be in indeterminate mode,
|
||||||
|
* indicating ongoing activity without a specific completion percentage.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
indeterminate?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, renders a smaller version of the progress bar.
|
||||||
|
* Useful for compact UI layouts.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
small?: boolean;
|
||||||
|
}
|
31
src/components/progressbar/VProgressBar.vue
Normal file
31
src/components/progressbar/VProgressBar.vue
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ProgressBar from 'primevue/progressbar';
|
||||||
|
import type IVProgressBar from './IVProgressBar.type';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<IVProgressBar>(), {
|
||||||
|
value: 0,
|
||||||
|
showValue: false,
|
||||||
|
indeterminate: false,
|
||||||
|
small: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mode = computed(() => (props.indeterminate ? 'indeterminate' : 'determinate'));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ProgressBar
|
||||||
|
role="progressbar"
|
||||||
|
:mode="mode"
|
||||||
|
:value="props.value"
|
||||||
|
:show-value="props.showValue && !props.small"
|
||||||
|
:class="['p-progressbar', {small: props.small}]"
|
||||||
|
style="width: 100%;"
|
||||||
|
>
|
||||||
|
<slot/>
|
||||||
|
</ProgressBar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.p-progressbar.small {--p-progressbar-height: 0.5rem;}
|
||||||
|
</style>
|
351
src/components/select/IVSelect.type.ts
Normal file
351
src/components/select/IVSelect.type.ts
Normal file
|
@ -0,0 +1,351 @@
|
||||||
|
import type { HintedString } from '@primevue/core';
|
||||||
|
import type { VirtualScrollerProps } from 'primevue/virtualscroller';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the props for a custom VSelect component.
|
||||||
|
*/
|
||||||
|
export type option = string | number | null | undefined;
|
||||||
|
|
||||||
|
export interface ISelect {
|
||||||
|
/**
|
||||||
|
* Whether the select field is required.
|
||||||
|
*/
|
||||||
|
required?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the select field is disabled.
|
||||||
|
*/
|
||||||
|
disabled?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unique ID for the select element.
|
||||||
|
*/
|
||||||
|
selectId?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name attribute for the select element.
|
||||||
|
*/
|
||||||
|
name?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional hint text displayed below the select field.
|
||||||
|
*/
|
||||||
|
hint?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently selected value.
|
||||||
|
*/
|
||||||
|
modelValue?: option;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The label displayed above the select field.
|
||||||
|
*/
|
||||||
|
label?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of options to display in the dropdown.
|
||||||
|
* Can be an array of strings, numbers, or objects.
|
||||||
|
*/
|
||||||
|
options?: Array<
|
||||||
|
string | number | Record<string, unknown> | {
|
||||||
|
value: unknown;
|
||||||
|
text: string;
|
||||||
|
disabled: boolean;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines how to extract the label from an option.
|
||||||
|
* Can be a string key or a function.
|
||||||
|
*/
|
||||||
|
optionLabel?: string | ((option: unknown) => string);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines how to extract the value from an option.
|
||||||
|
* Can be a string key or a function.
|
||||||
|
*/
|
||||||
|
optionValue?: string | ((option: unknown) => unknown);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message displayed when the selection is valid.
|
||||||
|
*/
|
||||||
|
successMessage?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message displayed when the selection is invalid.
|
||||||
|
*/
|
||||||
|
errorMessage?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text displayed when no option is selected.
|
||||||
|
*/
|
||||||
|
defaultUnselectedText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SelectProps {
|
||||||
|
/**
|
||||||
|
* Value of the component.
|
||||||
|
*/
|
||||||
|
modelValue?: unknown;
|
||||||
|
/**
|
||||||
|
* The default value for the input when not controlled by `modelValue`.
|
||||||
|
*/
|
||||||
|
defaultValue?: unknown;
|
||||||
|
/**
|
||||||
|
* The name attribute for the element, typically used in form submissions.
|
||||||
|
*/
|
||||||
|
name?: string | undefined;
|
||||||
|
/**
|
||||||
|
* An array of select items to display as the available options.
|
||||||
|
*/
|
||||||
|
options?: unknown[];
|
||||||
|
/**
|
||||||
|
* Property name or getter function to use as the label of an option.
|
||||||
|
*/
|
||||||
|
optionLabel?: string | ((data: unknown) => string) | undefined;
|
||||||
|
/**
|
||||||
|
* Property name or getter function to use as the value of an option, defaults to the option itself when not defined.
|
||||||
|
*/
|
||||||
|
optionValue?: string | ((data: unknown) => unknown) | undefined;
|
||||||
|
/**
|
||||||
|
* Property name or getter function to use as the disabled flag of an option, defaults to false when not defined.
|
||||||
|
*/
|
||||||
|
optionDisabled?: string | ((data: unknown) => boolean) | undefined;
|
||||||
|
/**
|
||||||
|
* Property name or getter function to use as the label of an option group.
|
||||||
|
*/
|
||||||
|
optionGroupLabel?: string | ((data: unknown) => string) | undefined;
|
||||||
|
/**
|
||||||
|
* Property name or getter function that refers to the children options of option group.
|
||||||
|
*/
|
||||||
|
optionGroupChildren?: string | ((data: unknown) => unknown[]) | undefined;
|
||||||
|
/**
|
||||||
|
* Height of the viewport, a scrollbar is defined if height of list exceeds this value.
|
||||||
|
* @defaultValue 14rem
|
||||||
|
*/
|
||||||
|
scrollHeight?: string | undefined;
|
||||||
|
/**
|
||||||
|
* When specified, displays a filter input at header.
|
||||||
|
* @defaultValue false
|
||||||
|
*/
|
||||||
|
filter?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Placeholder text to show when filter input is empty.
|
||||||
|
*/
|
||||||
|
filterPlaceholder?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Locale to use in filtering. The default locale is the host environment's current locale.
|
||||||
|
*/
|
||||||
|
filterLocale?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Defines the filtering algorithm to use when searching the options.
|
||||||
|
* @defaultValue contains
|
||||||
|
*/
|
||||||
|
filterMatchMode?: HintedString<'contains' | 'startsWith' | 'endsWith'> | undefined;
|
||||||
|
/**
|
||||||
|
* Fields used when filtering the options, defaults to optionLabel.
|
||||||
|
*/
|
||||||
|
filterFields?: string[] | undefined;
|
||||||
|
/**
|
||||||
|
* When present, custom value instead of predefined options can be entered using the editable input field.
|
||||||
|
* @defaultValue false
|
||||||
|
*/
|
||||||
|
editable?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Default text to display when no option is selected.
|
||||||
|
*/
|
||||||
|
placeholder?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Defines the size of the component.
|
||||||
|
*/
|
||||||
|
size?: HintedString<'small' | 'large'> | undefined;
|
||||||
|
/**
|
||||||
|
* When present, it specifies that the component should have invalid state style.
|
||||||
|
* @defaultValue false
|
||||||
|
*/
|
||||||
|
invalid?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* When present, it specifies that the component should be disabled.
|
||||||
|
* @defaultValue false
|
||||||
|
*/
|
||||||
|
disabled?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Specifies the input variant of the component.
|
||||||
|
* @defaultValue null
|
||||||
|
*/
|
||||||
|
variant?: HintedString<'outlined' | 'filled'> | undefined | null;
|
||||||
|
/**
|
||||||
|
* A property to uniquely identify an option.
|
||||||
|
*/
|
||||||
|
dataKey?: string | undefined;
|
||||||
|
/**
|
||||||
|
* When enabled, a clear icon is displayed to clear the value.
|
||||||
|
* @defaultValue false
|
||||||
|
*/
|
||||||
|
showClear?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Spans 100% width of the container when enabled.
|
||||||
|
* @defaultValue null
|
||||||
|
*/
|
||||||
|
fluid?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* @deprecated since v4.0. Use 'labelId' instead.
|
||||||
|
* Identifier of the underlying input element.
|
||||||
|
*/
|
||||||
|
inputId?: string | undefined;
|
||||||
|
/**
|
||||||
|
* @deprecated since v4.0. Use 'labelStyle' instead.
|
||||||
|
* Inline style of the input field.
|
||||||
|
*/
|
||||||
|
inputStyle?: object | undefined;
|
||||||
|
/**
|
||||||
|
* @deprecated since v4.0. Use 'labelClass' instead.
|
||||||
|
* Style class of the input field.
|
||||||
|
*/
|
||||||
|
inputClass?: string | object | undefined;
|
||||||
|
/**
|
||||||
|
* Identifier of the underlying label element.
|
||||||
|
*/
|
||||||
|
labelId?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Inline style of the label field.
|
||||||
|
*/
|
||||||
|
labelStyle?: object | undefined;
|
||||||
|
/**
|
||||||
|
* Style class of the label field.
|
||||||
|
*/
|
||||||
|
labelClass?: string | object | undefined;
|
||||||
|
/**
|
||||||
|
* @deprecated since v4.0. Use 'overlayStyle' instead.
|
||||||
|
* Inline style of the overlay panel.
|
||||||
|
*/
|
||||||
|
panelStyle?: object | undefined;
|
||||||
|
/**
|
||||||
|
* @deprecated since v4.0. Use 'overlayClass' instead.
|
||||||
|
* Style class of the overlay panel.
|
||||||
|
*/
|
||||||
|
panelClass?: string | object | undefined;
|
||||||
|
/**
|
||||||
|
* Inline style of the overlay.
|
||||||
|
*/
|
||||||
|
overlayStyle?: object | undefined;
|
||||||
|
/**
|
||||||
|
* Style class of the overlay.
|
||||||
|
*/
|
||||||
|
overlayClass?: string | object | undefined;
|
||||||
|
/**
|
||||||
|
* A valid query selector or an HTMLElement to specify where the overlay gets attached.
|
||||||
|
* @defaultValue body
|
||||||
|
*/
|
||||||
|
appendTo?: HintedString<'body' | 'self'> | undefined | HTMLElement;
|
||||||
|
/**
|
||||||
|
* Whether the select is in loading state.
|
||||||
|
* @defaultValue false
|
||||||
|
*/
|
||||||
|
loading?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Icon to display in clear button.
|
||||||
|
*/
|
||||||
|
clearIcon?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Icon to display in the select.
|
||||||
|
*/
|
||||||
|
dropdownIcon?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Icon to display in filter input.
|
||||||
|
*/
|
||||||
|
filterIcon?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Icon to display in loading state.
|
||||||
|
*/
|
||||||
|
loadingIcon?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Clears the filter value when hiding the select.
|
||||||
|
* @defaultValue false
|
||||||
|
*/
|
||||||
|
resetFilterOnHide?: boolean;
|
||||||
|
/**
|
||||||
|
* Clears the filter value when clicking on the clear icon.
|
||||||
|
* @defaultValue false
|
||||||
|
*/
|
||||||
|
resetFilterOnClear?: boolean;
|
||||||
|
/**
|
||||||
|
* Whether to use the virtualScroller feature. The properties of VirtualScroller component can be used like an object in it.
|
||||||
|
*/
|
||||||
|
virtualScrollerOptions?: VirtualScrollerProps;
|
||||||
|
/**
|
||||||
|
* Whether to focus on the first visible or selected element when the overlay panel is shown.
|
||||||
|
* @defaultValue false
|
||||||
|
*/
|
||||||
|
autoOptionFocus?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Whether to focus on the filter element when the overlay panel is shown.
|
||||||
|
* @defaultValue false
|
||||||
|
*/
|
||||||
|
autoFilterFocus?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* When enabled, the focused option is selected.
|
||||||
|
* @defaultValue false
|
||||||
|
*/
|
||||||
|
selectOnFocus?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* When enabled, the focus is placed on the hovered option.
|
||||||
|
* @defaultValue true
|
||||||
|
*/
|
||||||
|
focusOnHover?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Whether the selected option will be add highlight class.
|
||||||
|
* @defaultValue true
|
||||||
|
*/
|
||||||
|
highlightOnSelect?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Whether the selected option will be shown with a check mark.
|
||||||
|
* @defaultValue false
|
||||||
|
*/
|
||||||
|
checkmark?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Text to be displayed in hidden accessible field when filtering returns any results. Defaults to value from PrimeVue locale configuration.
|
||||||
|
* @defaultValue '{0} results are available'
|
||||||
|
*/
|
||||||
|
filterMessage?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Text to be displayed in hidden accessible field when options are selected. Defaults to value from PrimeVue locale configuration.
|
||||||
|
* @defaultValue '{0} items selected'
|
||||||
|
*/
|
||||||
|
selectionMessage?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Text to be displayed in hidden accessible field when any option is not selected. Defaults to value from PrimeVue locale configuration.
|
||||||
|
* @defaultValue No selected item
|
||||||
|
*/
|
||||||
|
emptySelectionMessage?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Text to display when filtering does not return any results. Defaults to value from PrimeVue locale configuration.
|
||||||
|
* @defaultValue No results found
|
||||||
|
*/
|
||||||
|
emptyFilterMessage?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Text to display when there are no options available. Defaults to value from PrimeVue locale configuration.
|
||||||
|
* @defaultValue No available options
|
||||||
|
*/
|
||||||
|
emptyMessage?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Index of the element in tabbing order.
|
||||||
|
*/
|
||||||
|
tabindex?: number | string | undefined;
|
||||||
|
/**
|
||||||
|
* Defines a string value that labels an interactive element.
|
||||||
|
*/
|
||||||
|
ariaLabel?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Identifier of the underlying input element.
|
||||||
|
*/
|
||||||
|
ariaLabelledby?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Form control object, typically used for handling validation and form state.
|
||||||
|
*/
|
||||||
|
formControl?: Record<string, unknown> | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default interface IVSelect extends Partial<Omit<SelectProps, 'modelValue' | 'options'>>, Partial<ISelect> {
|
||||||
|
optionTemplate?: boolean
|
||||||
|
}
|
186
src/components/select/VSelect.vue
Normal file
186
src/components/select/VSelect.vue
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Select from 'primevue/select';
|
||||||
|
import type IVSelect from './IVSelect.type';
|
||||||
|
import { useId, computed, watch, ref } from 'vue';
|
||||||
|
import VLabel from '../label/VLabel.vue';
|
||||||
|
import VHint from '../hint/VHint.vue';
|
||||||
|
import styles from '@visua/typography.module.css';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
inheritAttrs: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<IVSelect>(), {
|
||||||
|
hint: '',
|
||||||
|
successMessage: '',
|
||||||
|
errorMessage: '',
|
||||||
|
selectId: () => `select-${useId()}`,
|
||||||
|
label: '',
|
||||||
|
required: false,
|
||||||
|
disabled: false,
|
||||||
|
options: undefined,
|
||||||
|
name: '',
|
||||||
|
defaultUnselectedText: 'Sélectionner une option',
|
||||||
|
filter: false,
|
||||||
|
editable: false,
|
||||||
|
optionLabel: undefined,
|
||||||
|
optionValue: undefined,
|
||||||
|
filterMessage: 'Les résultats sont disponibles',
|
||||||
|
selectionMessage: 'Elements sélectionnés',
|
||||||
|
emptySelectionMessage: 'Aucun élément sélectionné',
|
||||||
|
emptyFilterMessage: 'Aucun résultat trouvé',
|
||||||
|
emptyMessage: 'Aucune option disponible',
|
||||||
|
optionTemplate: false,
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits([
|
||||||
|
'update:modelValue',
|
||||||
|
'value-change',
|
||||||
|
'change',
|
||||||
|
'focus',
|
||||||
|
'blur',
|
||||||
|
'before-show',
|
||||||
|
'before-hide',
|
||||||
|
'show',
|
||||||
|
'hide',
|
||||||
|
'filter',
|
||||||
|
])
|
||||||
|
|
||||||
|
const localModelValue = ref(props.modelValue);
|
||||||
|
watch(() => props.modelValue, (newVal) => {
|
||||||
|
if(localModelValue.value !== newVal){
|
||||||
|
localModelValue.value = newVal;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
watch(localModelValue, (newVal) => {
|
||||||
|
if(props.modelValue !== newVal){
|
||||||
|
emit('update:modelValue', newVal);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const message = computed(() => {
|
||||||
|
return props.errorMessage || props.successMessage
|
||||||
|
})
|
||||||
|
const messageState = computed(() => {
|
||||||
|
return props.errorMessage ? 'error' : 'valid'
|
||||||
|
})
|
||||||
|
|
||||||
|
const typeMessage = computed(() => {
|
||||||
|
if (props.errorMessage) return 'alert';
|
||||||
|
else if (props.successMessage) return 'success';
|
||||||
|
else return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
const labelState = computed(() => {
|
||||||
|
if(!props.errorMessage && !props.successMessage && !props.disabled) return 'default';
|
||||||
|
else if(!props.errorMessage && props.successMessage && !props.disabled) return 'success';
|
||||||
|
else if(props.errorMessage && !props.successMessage && !props.disabled) return 'error';
|
||||||
|
else return undefined
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="main-container">
|
||||||
|
<VLabel
|
||||||
|
:for="props.selectId"
|
||||||
|
:label="props.label"
|
||||||
|
:required="!props.disabled"
|
||||||
|
:disabled="props.disabled"
|
||||||
|
:type="labelState"
|
||||||
|
:hint="props.hint"
|
||||||
|
>
|
||||||
|
<template #required-type v-if="props.required">
|
||||||
|
<slot name="required-type"/>
|
||||||
|
</template>
|
||||||
|
</VLabel>
|
||||||
|
<Select
|
||||||
|
:name="props.name || props.selectId"
|
||||||
|
:options="props.options"
|
||||||
|
:optionLabel="props.optionLabel"
|
||||||
|
:optionValue="props.optionValue"
|
||||||
|
:disabled="props.disabled"
|
||||||
|
:aria-disabled="props.disabled"
|
||||||
|
:filter="props.filter"
|
||||||
|
:editable="props.editable"
|
||||||
|
:placeholder="props.placeholder"
|
||||||
|
:filter-message="props.filterMessage"
|
||||||
|
:selection-message="props.selectionMessage"
|
||||||
|
:empty-selection-message="props.emptySelectionMessage"
|
||||||
|
:empty-filter-message="props.emptyFilterMessage"
|
||||||
|
:empty-message="props.emptyMessage"
|
||||||
|
:show-clear="props.showClear"
|
||||||
|
:filter-fields="props.filterFields"
|
||||||
|
:filter-icon="props.filterIcon"
|
||||||
|
:filter-locale="props.filterLocale"
|
||||||
|
:filter-match-mode="props.filterMatchMode"
|
||||||
|
:filter-placeholder="props.filterPlaceholder"
|
||||||
|
:scroll-height="props.scrollHeight"
|
||||||
|
:data-key="props.dataKey"
|
||||||
|
:loading="props.loading"
|
||||||
|
v-bind="$attrs"
|
||||||
|
v-model:model-value="localModelValue"
|
||||||
|
@update:model-value="emit('update:modelValue', $event)"
|
||||||
|
@change="emit('change', $event)"
|
||||||
|
@blur="emit('blur', $event)"
|
||||||
|
@focus="emit('focus', $event)"
|
||||||
|
@value-change="emit('value-change', $event)"
|
||||||
|
@before-hide="emit('before-hide', $event)"
|
||||||
|
@before-show="emit('before-show', $event)"
|
||||||
|
@show="emit('show', $event)"
|
||||||
|
@hide="emit('hide', $event)"
|
||||||
|
@filter="emit('filter', $event)"
|
||||||
|
class="p-select"
|
||||||
|
:class="[
|
||||||
|
styles['text-body-MD-standard-text-Regular'],
|
||||||
|
{
|
||||||
|
'disabled': props.disabled,
|
||||||
|
'error': props.errorMessage && !props.successMessage && !props.disabled,
|
||||||
|
'success': !props.errorMessage && props.successMessage && !props.disabled,
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<template v-if="props.optionTemplate" #option="{option, selected, index}">
|
||||||
|
<slot name="option" :option="option" :selected="selected" :index="index"/>
|
||||||
|
</template>
|
||||||
|
</Select>
|
||||||
|
<div
|
||||||
|
:id="`select-${messageState}-desc-${messageState}`"
|
||||||
|
role="alert"
|
||||||
|
aria-live="assertive"
|
||||||
|
>
|
||||||
|
<VHint :title="message" :type="typeMessage" icon/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.main-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: start;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-select{
|
||||||
|
width: 100%;
|
||||||
|
height: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-select.disabled{
|
||||||
|
--p-select-border-color: var(--border-disabled-grey);
|
||||||
|
--p-select-dropdown-color: var(--text-disabled-grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-select.error{
|
||||||
|
--p-select-border-color: var(--border-plain-error);
|
||||||
|
--p-select-hover-border-color: var(--border-plain-error);
|
||||||
|
--p-select-focus-border-color: var(--border-plain-error);
|
||||||
|
}
|
||||||
|
.p-select.success{
|
||||||
|
--p-select-border-color: var(--border-plain-success);
|
||||||
|
--p-select-hover-border-color: var(--border-plain-success);
|
||||||
|
--p-select-focus-border-color: var(--border-plain-success);
|
||||||
|
}
|
||||||
|
</style>
|
57
test/VProgressBar.spec.ts
Normal file
57
test/VProgressBar.spec.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { mount } from '@vue/test-utils';
|
||||||
|
import VProgressBar from '../src/components/progressbar/VProgressBar.vue';
|
||||||
|
|
||||||
|
describe('VProgressBar', () => {
|
||||||
|
it('renders with default props', () => {
|
||||||
|
const wrapper = mount(VProgressBar);
|
||||||
|
const progressBar = wrapper.findComponent({ name: 'ProgressBar' });
|
||||||
|
|
||||||
|
expect(progressBar.exists()).toBe(true);
|
||||||
|
expect(progressBar.props('value')).toBe(0);
|
||||||
|
expect(progressBar.props('mode')).toBe('determinate');
|
||||||
|
expect(progressBar.props('showValue')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with custom value', () => {
|
||||||
|
const wrapper = mount(VProgressBar, {
|
||||||
|
props: { value: 75 }
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.findComponent({ name: 'ProgressBar' }).props('value')).toBe(75);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders in indeterminate mode', () => {
|
||||||
|
const wrapper = mount(VProgressBar, {
|
||||||
|
props: { indeterminate: true, value: 75 }
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.findComponent({ name: 'ProgressBar' }).props('mode')).toBe('indeterminate');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hides value when small is true even if showValue is true', () => {
|
||||||
|
const wrapper = mount(VProgressBar, {
|
||||||
|
props: { showValue: true, small: true, value: 25 }
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.findComponent({ name: 'ProgressBar' }).props('showValue')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('applies small class when small is true', () => {
|
||||||
|
const wrapper = mount(VProgressBar, {
|
||||||
|
props: { small: true, value: 15 }
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.find('.p-progressbar').classes()).toContain('small');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders slot content', () => {
|
||||||
|
const wrapper = mount(VProgressBar, {
|
||||||
|
props: { value: 43, showValue: true },
|
||||||
|
slots: {
|
||||||
|
default: 'Valeur: 43/100'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(wrapper.html()).toContain('Valeur: 43/100');
|
||||||
|
});
|
||||||
|
});
|
101
test/VSelect.spec.ts
Normal file
101
test/VSelect.spec.ts
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { mount } from '@vue/test-utils';
|
||||||
|
import VSelect from '../src/components/select/VSelect.vue';
|
||||||
|
import Select from 'primevue/select';
|
||||||
|
|
||||||
|
// Mock global matchMedia
|
||||||
|
Object.defineProperty(window, 'matchMedia', {
|
||||||
|
writable: true,
|
||||||
|
value: vi.fn().mockImplementation((query) => ({
|
||||||
|
matches: false,
|
||||||
|
media: query,
|
||||||
|
onchange: null,
|
||||||
|
addListener: vi.fn(), // deprecated
|
||||||
|
removeListener: vi.fn(), // deprecated
|
||||||
|
addEventListener: vi.fn(),
|
||||||
|
removeEventListener: vi.fn(),
|
||||||
|
dispatchEvent: vi.fn(),
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const globalConfig = {
|
||||||
|
global: {
|
||||||
|
components: {
|
||||||
|
Checkbox: Select
|
||||||
|
},
|
||||||
|
mocks: {
|
||||||
|
$primevue: {
|
||||||
|
config: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stubs: {
|
||||||
|
VLabel: false,
|
||||||
|
VHint: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('VSelect.vue', () => {
|
||||||
|
const options = [
|
||||||
|
{ value: 'apple', text: 'Apple' },
|
||||||
|
{ value: 'banana', text: 'Banana' },
|
||||||
|
{ value: 'cherry', text: 'Cherry' }
|
||||||
|
];
|
||||||
|
|
||||||
|
it('renders with basic props', () => {
|
||||||
|
const wrapper = mount(VSelect, {
|
||||||
|
props: {
|
||||||
|
label: 'Fruits',
|
||||||
|
options,
|
||||||
|
modelValue: null
|
||||||
|
},
|
||||||
|
global: globalConfig.global
|
||||||
|
});
|
||||||
|
|
||||||
|
const label = wrapper.findComponent({ name: 'VLabel' });
|
||||||
|
expect(label.exists()).toBe(true);
|
||||||
|
expect(label.props('label')).toBe('Fruits');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('emits update:modelValue and change when value is selected', async () => {
|
||||||
|
const wrapper = mount(VSelect, {
|
||||||
|
props: {
|
||||||
|
options,
|
||||||
|
modelValue: null
|
||||||
|
},
|
||||||
|
global: globalConfig.global
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simule la sélection d'une option
|
||||||
|
wrapper.vm.$emit('update:modelValue', 'banana');
|
||||||
|
wrapper.vm.$emit('change', { value: 'banana' });
|
||||||
|
|
||||||
|
const emittedUpdate = wrapper.emitted('update:modelValue');
|
||||||
|
const emittedChange = wrapper.emitted('change');
|
||||||
|
|
||||||
|
expect(emittedUpdate).toBeTruthy();
|
||||||
|
expect(emittedUpdate![0]).toEqual(['banana']);
|
||||||
|
|
||||||
|
expect(emittedChange).toBeTruthy();
|
||||||
|
expect(emittedChange![0]).toEqual([{ value: 'banana' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reacts to external modelValue changes', async () => {
|
||||||
|
const wrapper = mount(VSelect, {
|
||||||
|
props: {
|
||||||
|
options,
|
||||||
|
modelValue: 'apple'
|
||||||
|
},
|
||||||
|
global: globalConfig.global
|
||||||
|
});
|
||||||
|
|
||||||
|
await wrapper.setProps({ modelValue: 'cherry' });
|
||||||
|
expect(wrapper.emitted('update:modelValue')).toBeFalsy();
|
||||||
|
|
||||||
|
const select = wrapper.findComponent({ name: 'Select' });
|
||||||
|
expect(select.props('modelValue')).toBe('cherry');
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user