@tsiky/components-r19 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/AnnouncementPanel/FlexRowContainer.css +17 -17
- package/src/components/AnnouncementPanel/FlexRowContainer.stories.tsx +329 -329
- package/src/components/AnnouncementPanel/FlexRowContainer.tsx +24 -24
- package/src/components/AnnouncementPanel/ListBox/CounterListBox.css +56 -56
- package/src/components/AnnouncementPanel/ListBox/CounterListBox.stories.tsx +292 -292
- package/src/components/AnnouncementPanel/ListBox/CounterListBox.tsx +106 -106
- package/src/components/AnnouncementPanel/ListBox/SimpleListBox.css +57 -57
- package/src/components/AnnouncementPanel/ListBox/SimpleListBox.stories.tsx +189 -189
- package/src/components/AnnouncementPanel/ListBox/SimpleListBox.tsx +138 -138
- package/src/components/AnnouncementPanel/ListBox/TrendListBox.css +61 -61
- package/src/components/AnnouncementPanel/ListBox/TrendListBox.stories.tsx +257 -257
- package/src/components/AnnouncementPanel/ListBox/TrendListBox.tsx +90 -90
- package/src/components/AnnouncementPanel/ListBox/index.ts +3 -3
- package/src/components/AnnouncementPanel/ListContentContainer.css +23 -23
- package/src/components/AnnouncementPanel/ListContentContainer.stories.tsx +212 -212
- package/src/components/AnnouncementPanel/ListContentContainer.tsx +33 -33
- package/src/components/AnnouncementPanel/index.ts +3 -3
- package/src/components/Charts/area-chart-admission/AreaChartAdmission.tsx +7 -1
- package/src/components/Charts/bar-chart/BarChart.tsx +6 -2
- package/src/components/Charts/boxplot-chart/BoxPlotChart.tsx +114 -114
- package/src/components/Charts/mixed-chart/MixedChart.tsx +1 -1
- package/src/components/Charts/sankey-adaptation/sankey.tsx +70 -70
- package/src/components/DraggableSwitcher/DraggableSwitcherButton.tsx +58 -58
- package/src/components/DraggableSwitcher/context/useDraggableSwitcher.tsx +45 -45
- package/src/components/DraggableSwitcher/index.ts +2 -2
- package/src/components/DynamicInput/DynamicInput.module.css +125 -126
- package/src/components/DynamicInput/input/SelectInput.tsx +75 -75
- package/src/components/DynamicInput/input/assets/SelectInput.module.css +95 -95
- package/src/components/DynamicTable/AdvancedFilters.tsx +196 -196
- package/src/components/DynamicTable/ColumnSorter.tsx +185 -185
- package/src/components/DynamicTable/Pagination.tsx +115 -115
- package/src/components/DynamicTable/TableCell.tsx +38 -30
- package/src/components/DynamicTable/TableHeader.tsx +39 -34
- package/src/components/DynamicTable/TableauDynamique.module.css +77 -70
- package/src/components/DynamicTable/TableauDynamique.tsx +154 -154
- package/src/components/DynamicTable/filters/SelectFilter.tsx +69 -69
- package/src/components/DynamicTable/tools/tableTypes.ts +63 -63
- package/src/components/EntryControl/EntryControl.tsx +117 -117
- package/src/components/Grid/grid.css +285 -285
- package/src/components/MetricsPanel/MetricsPanel.tsx +37 -37
- package/src/components/MetricsPanel/renderers/CompactRenderer.tsx +1 -1
- package/src/components/NavItem/NavItem.tsx +58 -58
- package/src/components/PeriodRange/PeriodRange.module.css +158 -158
- package/src/components/PeriodRange/PeriodRange.tsx +130 -130
- package/src/components/PeriodSelect/PeriodSelect.module.css +64 -65
- package/src/components/PeriodSelect/PeriodSelect.tsx +48 -42
- package/src/components/SearchBar/SearchBar.css +40 -40
- package/src/components/TranslationKey/TranslationKey.css +272 -272
- package/src/components/TranslationKey/TranslationKey.tsx +8 -7
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { createContext, useContext, useState, useEffect } from 'react';
|
|
3
|
-
import type { ReactNode } from 'react';
|
|
4
|
-
|
|
5
|
-
interface DraggableContextType {
|
|
6
|
-
isDraggable: boolean;
|
|
7
|
-
switchDraggable: (value: boolean) => void;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const DraggableContext = createContext<DraggableContextType | undefined>(undefined);
|
|
11
|
-
|
|
12
|
-
export const DraggableProvider = ({ children }: { children: ReactNode }) => {
|
|
13
|
-
const [isDraggable, setIsDraggable] = useState(false);
|
|
14
|
-
|
|
15
|
-
// Charger depuis localStorage au premier rendu
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
const stored = localStorage.getItem('isDraggable');
|
|
18
|
-
if (stored !== null) {
|
|
19
|
-
setIsDraggable(stored === 'true'); // localStorage stocke toujours des strings
|
|
20
|
-
}
|
|
21
|
-
}, []);
|
|
22
|
-
|
|
23
|
-
// Sauvegarder dans localStorage quand ça change
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
localStorage.setItem('isDraggable', String(isDraggable));
|
|
26
|
-
}, [isDraggable]);
|
|
27
|
-
|
|
28
|
-
const switchDraggable = (value: boolean) => {
|
|
29
|
-
setIsDraggable(value);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<DraggableContext.Provider value={{ isDraggable, switchDraggable }}>
|
|
34
|
-
{children}
|
|
35
|
-
</DraggableContext.Provider>
|
|
36
|
-
);
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export const useDraggableSwitcher = () => {
|
|
40
|
-
const context = useContext(DraggableContext);
|
|
41
|
-
if (!context) {
|
|
42
|
-
throw new Error('useDraggableSwitcher must be used inside a DraggableProvider');
|
|
43
|
-
}
|
|
44
|
-
return context;
|
|
45
|
-
};
|
|
1
|
+
'use client';
|
|
2
|
+
import { createContext, useContext, useState, useEffect } from 'react';
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
interface DraggableContextType {
|
|
6
|
+
isDraggable: boolean;
|
|
7
|
+
switchDraggable: (value: boolean) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const DraggableContext = createContext<DraggableContextType | undefined>(undefined);
|
|
11
|
+
|
|
12
|
+
export const DraggableProvider = ({ children }: { children: ReactNode }) => {
|
|
13
|
+
const [isDraggable, setIsDraggable] = useState(false);
|
|
14
|
+
|
|
15
|
+
// Charger depuis localStorage au premier rendu
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const stored = localStorage.getItem('isDraggable');
|
|
18
|
+
if (stored !== null) {
|
|
19
|
+
setIsDraggable(stored === 'true'); // localStorage stocke toujours des strings
|
|
20
|
+
}
|
|
21
|
+
}, []);
|
|
22
|
+
|
|
23
|
+
// Sauvegarder dans localStorage quand ça change
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
localStorage.setItem('isDraggable', String(isDraggable));
|
|
26
|
+
}, [isDraggable]);
|
|
27
|
+
|
|
28
|
+
const switchDraggable = (value: boolean) => {
|
|
29
|
+
setIsDraggable(value);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<DraggableContext.Provider value={{ isDraggable, switchDraggable }}>
|
|
34
|
+
{children}
|
|
35
|
+
</DraggableContext.Provider>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const useDraggableSwitcher = () => {
|
|
40
|
+
const context = useContext(DraggableContext);
|
|
41
|
+
if (!context) {
|
|
42
|
+
throw new Error('useDraggableSwitcher must be used inside a DraggableProvider');
|
|
43
|
+
}
|
|
44
|
+
return context;
|
|
45
|
+
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from './DraggableSwitcherButton';
|
|
2
|
-
export * from './context/useDraggableSwitcher';
|
|
1
|
+
export * from './DraggableSwitcherButton';
|
|
2
|
+
export * from './context/useDraggableSwitcher';
|
|
@@ -1,126 +1,125 @@
|
|
|
1
|
-
/* DynamicInput.module.css */
|
|
2
|
-
.container {
|
|
3
|
-
padding: 20px;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
border:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
background-
|
|
98
|
-
background-
|
|
99
|
-
background-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
outline: 2px
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
1
|
+
/* DynamicInput.module.css */
|
|
2
|
+
.container {
|
|
3
|
+
padding: 20px;
|
|
4
|
+
border-radius: 8px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.fieldGroup {
|
|
8
|
+
margin-bottom: 1.5rem;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.fieldLabel {
|
|
12
|
+
display: block;
|
|
13
|
+
margin-bottom: 0.5rem;
|
|
14
|
+
font-weight: 500;
|
|
15
|
+
color: #212529;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.required::after {
|
|
19
|
+
content: ' *';
|
|
20
|
+
color: #dc3545;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.fieldInput {
|
|
24
|
+
width: 100%;
|
|
25
|
+
padding: 0.5rem 1rem;
|
|
26
|
+
border: 1px solid #6c757d;
|
|
27
|
+
border-radius: 0.375rem;
|
|
28
|
+
transition:
|
|
29
|
+
border-color 0.15s ease-in-out,
|
|
30
|
+
box-shadow 0.15s ease-in-out;
|
|
31
|
+
line-height: 1.5;
|
|
32
|
+
font-size: 1rem;
|
|
33
|
+
background-color: #f8f9fa;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.fieldInput:focus {
|
|
37
|
+
border-color: #0d6efd;
|
|
38
|
+
outline: 0;
|
|
39
|
+
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.fieldInvalid {
|
|
43
|
+
border-color: #dc3545;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.fieldValid {
|
|
47
|
+
border-color: #198754;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.fieldError {
|
|
51
|
+
display: block;
|
|
52
|
+
margin-top: 0.25rem;
|
|
53
|
+
font-size: 0.875rem;
|
|
54
|
+
color: #dc3545;
|
|
55
|
+
font-weight: 500;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Styles spécifiques pour les radios */
|
|
59
|
+
.radioGroup {
|
|
60
|
+
display: flex;
|
|
61
|
+
flex-direction: column;
|
|
62
|
+
gap: 0.5rem;
|
|
63
|
+
background-color: transparent;
|
|
64
|
+
padding: 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.radioGroup .form-check {
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
gap: 0.5rem;
|
|
71
|
+
background-color: transparent;
|
|
72
|
+
margin-bottom: 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.radioGroup .form-check-label {
|
|
76
|
+
color: #212529;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Styles pour les checkboxes */
|
|
80
|
+
.checkboxGroup {
|
|
81
|
+
display: flex;
|
|
82
|
+
flex-direction: column;
|
|
83
|
+
gap: 0.5rem;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.checkboxGroup .form-check {
|
|
87
|
+
display: flex;
|
|
88
|
+
align-items: center;
|
|
89
|
+
gap: 0.5rem;
|
|
90
|
+
margin-bottom: 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* Styles spécifiques pour les selects */
|
|
94
|
+
.selectInput select {
|
|
95
|
+
appearance: none;
|
|
96
|
+
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
|
|
97
|
+
background-repeat: no-repeat;
|
|
98
|
+
background-position: right 0.75rem center;
|
|
99
|
+
background-size: 1rem;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Styles pour les textareas */
|
|
103
|
+
.textareaInput textarea {
|
|
104
|
+
resize: vertical;
|
|
105
|
+
min-height: 100px;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Styles pour les champs désactivés */
|
|
109
|
+
.fieldInput:disabled {
|
|
110
|
+
background-color: rgba(0, 0, 0, 0.05);
|
|
111
|
+
cursor: not-allowed;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Amélioration de l'accessibilité pour le focus */
|
|
115
|
+
.fieldInput:focus-visible {
|
|
116
|
+
outline: 2px solid #0d6efd;
|
|
117
|
+
outline-offset: 2px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* Styles pour la grille dans AllInputs */
|
|
121
|
+
.gridContainer {
|
|
122
|
+
display: grid;
|
|
123
|
+
gap: 20px;
|
|
124
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
125
|
+
}
|
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import styles from './assets/SelectInput.module.css';
|
|
4
|
-
|
|
5
|
-
interface SelectInputProps {
|
|
6
|
-
field: any;
|
|
7
|
-
value: any;
|
|
8
|
-
onChange: (value: any) => void;
|
|
9
|
-
onBlur: () => void;
|
|
10
|
-
error?: string;
|
|
11
|
-
touched?: boolean;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const SelectInput: React.FC<SelectInputProps> = ({
|
|
15
|
-
field,
|
|
16
|
-
value,
|
|
17
|
-
onChange,
|
|
18
|
-
onBlur,
|
|
19
|
-
error,
|
|
20
|
-
touched,
|
|
21
|
-
}) => {
|
|
22
|
-
const hasError = Boolean(touched && error);
|
|
23
|
-
const displayLabel = field.label || '';
|
|
24
|
-
const placeholder = field.placeholder || '';
|
|
25
|
-
|
|
26
|
-
// Trouve le label de l'option sélectionnée si présente
|
|
27
|
-
const selectedOptionLabel =
|
|
28
|
-
field.options?.find((o: any) => String(o.value) === String(value))?.label ?? '';
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<div
|
|
32
|
-
className={`${styles.wrapper} ${hasError ? styles.wrapperInvalid : ''} ${
|
|
33
|
-
value ? styles.wrapperHasValue : ''
|
|
34
|
-
}`}
|
|
35
|
-
>
|
|
36
|
-
{/* Select natif (masqué visuellement mais accessible) */}
|
|
37
|
-
<select
|
|
38
|
-
id={field.name}
|
|
39
|
-
name={field.name}
|
|
40
|
-
className={styles.nativeSelect}
|
|
41
|
-
value={value ?? ''}
|
|
42
|
-
onChange={(e) => onChange(e.target.value)}
|
|
43
|
-
onBlur={onBlur}
|
|
44
|
-
aria-invalid={hasError}
|
|
45
|
-
aria-label={displayLabel}
|
|
46
|
-
>
|
|
47
|
-
{/* Placeholder : visible quand le select est fermé, caché dans la liste */}
|
|
48
|
-
<option value='' disabled hidden>
|
|
49
|
-
{placeholder || `Sélectionnez ${displayLabel?.toLowerCase() || ''}`}
|
|
50
|
-
</option>
|
|
51
|
-
|
|
52
|
-
{field.options?.map((option: any, idx: number) => (
|
|
53
|
-
<option key={idx} value={option.value} className={styles.optionStyle}>
|
|
54
|
-
{option.label}
|
|
55
|
-
</option>
|
|
56
|
-
))}
|
|
57
|
-
</select>
|
|
58
|
-
|
|
59
|
-
{/* Couche visuelle */}
|
|
60
|
-
<div className={styles.display}>
|
|
61
|
-
{displayLabel && <span className={styles.topLabel}>{displayLabel}</span>}
|
|
62
|
-
<span
|
|
63
|
-
className={`${styles.valueLine} ${value ? styles.valueActive : styles.valuePlaceholder}`}
|
|
64
|
-
>
|
|
65
|
-
{value ? selectedOptionLabel : placeholder}
|
|
66
|
-
</span>
|
|
67
|
-
|
|
68
|
-
{/* caret (SVG) */}
|
|
69
|
-
<span className={styles.caret} aria-hidden='true'></span>
|
|
70
|
-
</div>
|
|
71
|
-
|
|
72
|
-
{hasError && <span className={styles.error}>{error}</span>}
|
|
73
|
-
</div>
|
|
74
|
-
);
|
|
75
|
-
};
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import styles from './assets/SelectInput.module.css';
|
|
4
|
+
|
|
5
|
+
interface SelectInputProps {
|
|
6
|
+
field: any;
|
|
7
|
+
value: any;
|
|
8
|
+
onChange: (value: any) => void;
|
|
9
|
+
onBlur: () => void;
|
|
10
|
+
error?: string;
|
|
11
|
+
touched?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const SelectInput: React.FC<SelectInputProps> = ({
|
|
15
|
+
field,
|
|
16
|
+
value,
|
|
17
|
+
onChange,
|
|
18
|
+
onBlur,
|
|
19
|
+
error,
|
|
20
|
+
touched,
|
|
21
|
+
}) => {
|
|
22
|
+
const hasError = Boolean(touched && error);
|
|
23
|
+
const displayLabel = field.label || '';
|
|
24
|
+
const placeholder = field.placeholder || '';
|
|
25
|
+
|
|
26
|
+
// Trouve le label de l'option sélectionnée si présente
|
|
27
|
+
const selectedOptionLabel =
|
|
28
|
+
field.options?.find((o: any) => String(o.value) === String(value))?.label ?? '';
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div
|
|
32
|
+
className={`${styles.wrapper} ${hasError ? styles.wrapperInvalid : ''} ${
|
|
33
|
+
value ? styles.wrapperHasValue : ''
|
|
34
|
+
}`}
|
|
35
|
+
>
|
|
36
|
+
{/* Select natif (masqué visuellement mais accessible) */}
|
|
37
|
+
<select
|
|
38
|
+
id={field.name}
|
|
39
|
+
name={field.name}
|
|
40
|
+
className={styles.nativeSelect}
|
|
41
|
+
value={value ?? ''}
|
|
42
|
+
onChange={(e) => onChange(e.target.value)}
|
|
43
|
+
onBlur={onBlur}
|
|
44
|
+
aria-invalid={hasError}
|
|
45
|
+
aria-label={displayLabel}
|
|
46
|
+
>
|
|
47
|
+
{/* Placeholder : visible quand le select est fermé, caché dans la liste */}
|
|
48
|
+
<option value='' disabled hidden>
|
|
49
|
+
{placeholder || `Sélectionnez ${displayLabel?.toLowerCase() || ''}`}
|
|
50
|
+
</option>
|
|
51
|
+
|
|
52
|
+
{field.options?.map((option: any, idx: number) => (
|
|
53
|
+
<option key={idx} value={option.value} className={styles.optionStyle}>
|
|
54
|
+
{option.label}
|
|
55
|
+
</option>
|
|
56
|
+
))}
|
|
57
|
+
</select>
|
|
58
|
+
|
|
59
|
+
{/* Couche visuelle */}
|
|
60
|
+
<div className={styles.display}>
|
|
61
|
+
{displayLabel && <span className={styles.topLabel}>{displayLabel}</span>}
|
|
62
|
+
<span
|
|
63
|
+
className={`${styles.valueLine} ${value ? styles.valueActive : styles.valuePlaceholder}`}
|
|
64
|
+
>
|
|
65
|
+
{value ? selectedOptionLabel : placeholder}
|
|
66
|
+
</span>
|
|
67
|
+
|
|
68
|
+
{/* caret (SVG) */}
|
|
69
|
+
<span className={styles.caret} aria-hidden='true'></span>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
{hasError && <span className={styles.error}>{error}</span>}
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
};
|