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/),
|
||||
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
|
||||
### Added
|
||||
- Checkbox component
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# 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",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@cellule-financiere-pmo/visua-vue",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.8",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@cellule-financiere-pmo/visua": "1.1.3",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@cellule-financiere-pmo/visua-vue",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.8",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
|
10
src/App.vue
10
src/App.vue
|
@ -4,7 +4,10 @@
|
|||
// import VLinkView from '../template/VLinkView.vue';
|
||||
// import VAccordionView from '../template/VAccordionView.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>
|
||||
|
||||
|
||||
|
@ -14,5 +17,8 @@ import VCheckboxView from '../template/VCheckboxView.vue';
|
|||
<!-- <VLinkView/> -->
|
||||
<!-- <VAccordionView/> -->
|
||||
<!-- <VInputView/> -->
|
||||
<VCheckboxView/>
|
||||
<!-- <VCheckboxView/> -->
|
||||
<!-- <VBadgeView/> -->
|
||||
<!-- <VSelectView/> -->
|
||||
<VProgressBarView/>
|
||||
</template>
|
||||
|
|
|
@ -8,3 +8,8 @@
|
|||
@import './primevue-style/various.css';
|
||||
@import './primevue-style/form.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