5htp-core 0.2.2 → 0.2.4
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/client/app/component.tsx +1 -2
- package/src/client/app/index.ts +2 -6
- package/src/client/assets/css/components/button.less +4 -3
- package/src/client/assets/css/components/card.less +26 -11
- package/src/client/assets/css/components/lists.less +1 -1
- package/src/client/assets/css/components/other.less +0 -11
- package/src/client/assets/css/components.less +17 -3
- package/src/client/assets/css/core.less +55 -0
- package/src/client/assets/css/theme.less +0 -4
- package/src/client/assets/css/utils/layouts.less +1 -1
- package/src/client/assets/css/utils/medias.less +0 -47
- package/src/client/components/Dialog/Manager.tsx +1 -4
- package/src/client/components/Dialog/card.tsx +1 -1
- package/src/client/components/Dialog/index.less +2 -2
- package/src/client/components/Form.ts +154 -0
- package/src/client/components/{Form → Form_old}/index.tsx +0 -0
- package/src/client/components/{Form → Form_old}/index.tsx.old +0 -0
- package/src/client/components/Select/index.tsx +159 -24
- package/src/client/components/containers/Popover/index.tsx +38 -120
- package/src/client/components/data/progressbar/circular/index.tsx +1 -3
- package/src/client/components/dropdown/index.tsx +8 -13
- package/src/client/components/inputv3/base.less +0 -1
- package/src/client/components/inputv3/base.tsx +17 -6
- package/src/client/components/inputv3/string/index.tsx +23 -10
- package/src/client/services/router/components/Page.tsx +16 -18
- package/src/client/services/router/components/router.tsx +3 -3
- package/src/client/services/router/index.tsx +17 -14
- package/src/client/services/router/request/api.ts +6 -3
- package/src/client/services/router/response/index.tsx +4 -0
- package/src/client/services/router/response/page.ts +2 -1
- package/src/common/router/index.ts +1 -1
- package/src/common/router/layouts.ts +38 -6
- package/src/common/router/register.ts +3 -9
- package/src/common/router/request/api.ts +3 -1
- package/src/common/validation/index.ts +1 -1
- package/src/common/validation/schema.ts +8 -3
- package/src/common/validation/validators.ts +21 -10
- package/src/server/app/index.ts +2 -1
- package/src/server/services/console/bugReporter.ts +2 -19
- package/src/server/services/database/index.ts +27 -19
- package/src/server/services/router/index.ts +3 -3
- package/src/server/services/router/request/api.ts +3 -0
- package/src/server/services/router/response/index.ts +7 -4
- package/src/server/services/router/response/mask/Filter.ts +16 -72
- package/src/server/services/router/response/mask/index.ts +4 -7
- package/src/server/services/router/response/page/document.tsx +2 -2
- package/src/server/services/router/response/page/index.tsx +2 -2
- package/src/server/services/schema/request.ts +4 -2
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
// Npm
|
|
6
6
|
import React from 'react';
|
|
7
|
+
import type { ComponentChild } from 'preact';
|
|
8
|
+
import type { StateUpdater } from 'preact/hooks';
|
|
7
9
|
|
|
8
10
|
// Core
|
|
9
11
|
import Button from '@client/components/button';
|
|
12
|
+
import String from '@client/components/inputv3/string';
|
|
10
13
|
import Dropdown, { TDialogControls, Props as DropdownProps } from '@client/components/dropdown';
|
|
11
14
|
|
|
12
15
|
/*----------------------------------
|
|
@@ -17,42 +20,174 @@ import Dropdown, { TDialogControls, Props as DropdownProps } from '@client/compo
|
|
|
17
20
|
- TYPES
|
|
18
21
|
----------------------------------*/
|
|
19
22
|
|
|
20
|
-
type
|
|
23
|
+
export type Choice = { label: ComponentChild, value: string }
|
|
21
24
|
|
|
22
|
-
type
|
|
25
|
+
export type Choices = Choice[]
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
type ChoicesFunc = (search: string) => Promise<Choices>
|
|
28
|
+
|
|
29
|
+
type SelectorProps = (
|
|
30
|
+
{
|
|
31
|
+
multiple: true,
|
|
32
|
+
value?: Choice[],
|
|
33
|
+
onChange: StateUpdater<Choice[]>,
|
|
34
|
+
validator?: ArrayValidator
|
|
35
|
+
}
|
|
36
|
+
|
|
|
37
|
+
{
|
|
38
|
+
multiple?: false,
|
|
39
|
+
value?: Choice,
|
|
40
|
+
onChange: StateUpdater<Choice>,
|
|
41
|
+
validator?: StringValidator
|
|
42
|
+
}
|
|
43
|
+
) & {
|
|
44
|
+
choices: Choices | ChoicesFunc,
|
|
45
|
+
enableSearch?: boolean,
|
|
46
|
+
inline?: boolean,
|
|
47
|
+
errors?: string[],
|
|
48
|
+
required?: boolean,
|
|
49
|
+
noneSelection?: false | string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type Props = DropdownProps & SelectorProps & {
|
|
25
53
|
title: string,
|
|
26
|
-
choices: Choices,
|
|
27
|
-
value?: string,
|
|
28
|
-
onChange: (value: string) => void,
|
|
29
|
-
search?: true | SearchResultsFunction
|
|
30
54
|
}
|
|
31
55
|
|
|
32
56
|
/*----------------------------------
|
|
33
57
|
- COMONENT
|
|
34
58
|
----------------------------------*/
|
|
59
|
+
export default ({
|
|
60
|
+
title, choices: initChoices, errors, validator, required, noneSelection, enableSearch, value: current,
|
|
61
|
+
onChange, inline, multiple,
|
|
62
|
+
...props
|
|
63
|
+
}: Props) => {
|
|
35
64
|
|
|
36
|
-
export default ({ title, choices, value, onChange, ...dropDownProps }: Props) => {
|
|
37
65
|
const refModal = React.useRef<TDialogControls>(null);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
66
|
+
|
|
67
|
+
/*----------------------------------
|
|
68
|
+
- INIT
|
|
69
|
+
----------------------------------*/
|
|
70
|
+
|
|
71
|
+
const choicesViaFunc = typeof initChoices === 'function';
|
|
72
|
+
if (choicesViaFunc && enableSearch === undefined)
|
|
73
|
+
enableSearch = true;
|
|
74
|
+
|
|
75
|
+
const [search, setSearch] = React.useState<{
|
|
76
|
+
keywords: string,
|
|
77
|
+
loading: boolean
|
|
78
|
+
}>({
|
|
79
|
+
keywords: '',
|
|
80
|
+
loading: choicesViaFunc
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const [choices, setChoices] = React.useState<Choices>([]);
|
|
84
|
+
|
|
85
|
+
/*----------------------------------
|
|
86
|
+
- ACTIONS
|
|
87
|
+
----------------------------------*/
|
|
88
|
+
React.useEffect(() => {
|
|
89
|
+
if (choicesViaFunc) {
|
|
90
|
+
initChoices(search.keywords).then((searchResults) => {
|
|
91
|
+
setSearch(s => ({ ...s, loading: false }))
|
|
92
|
+
setChoices(searchResults);
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
}, [initChoices, search.keywords]);
|
|
96
|
+
|
|
97
|
+
const currentList: Choice[] = current === undefined
|
|
98
|
+
? []
|
|
99
|
+
: (Array.isArray(current) ? current : [current]);
|
|
100
|
+
|
|
101
|
+
/*----------------------------------
|
|
102
|
+
- RENDER
|
|
103
|
+
----------------------------------*/
|
|
104
|
+
|
|
105
|
+
const selector = (
|
|
106
|
+
<div class={(inline ? '' : 'card ') + "col"}>
|
|
107
|
+
|
|
108
|
+
{enableSearch && (
|
|
109
|
+
<String icon="search"
|
|
110
|
+
title="Search"
|
|
111
|
+
value={search.keywords}
|
|
112
|
+
onChange={keywords => setSearch(s => ({ ...s, loading: true, keywords }))}
|
|
113
|
+
iconR={'spin'}
|
|
114
|
+
/>
|
|
115
|
+
)}
|
|
116
|
+
|
|
117
|
+
{currentList.length !== 0 && (
|
|
118
|
+
<ul class="col menu">
|
|
119
|
+
{currentList.map(choice => (
|
|
120
|
+
<Button size="s" onClick={() => {
|
|
121
|
+
onChange( current => multiple
|
|
122
|
+
? current.filter(c => c.value !== choice.value)
|
|
123
|
+
: undefined
|
|
124
|
+
);
|
|
125
|
+
}} suffix={<i src="check" class="fg primary" />}>
|
|
126
|
+
{choice.label}
|
|
48
127
|
</Button>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
128
|
+
))}
|
|
129
|
+
</ul>
|
|
130
|
+
)}
|
|
131
|
+
|
|
132
|
+
{choices === null ? (
|
|
133
|
+
<div class="row h-3 al-center">
|
|
134
|
+
<i src="spin" />
|
|
135
|
+
</div>
|
|
136
|
+
) : (
|
|
137
|
+
<ul class="col menu">
|
|
138
|
+
{choices.map( choice => {
|
|
139
|
+
|
|
140
|
+
const isCurrent = currentList.some(c => c.value === choice.value);
|
|
53
141
|
|
|
54
|
-
|
|
142
|
+
return !isCurrent && (
|
|
143
|
+
<li>
|
|
144
|
+
<Button size="s" onClick={() => {
|
|
145
|
+
onChange( current => {
|
|
146
|
+
return multiple
|
|
147
|
+
? [...(current || []), choice]
|
|
148
|
+
: choice
|
|
149
|
+
});
|
|
150
|
+
}}>
|
|
151
|
+
{/*search.keywords ? (
|
|
152
|
+
<span>
|
|
153
|
+
|
|
154
|
+
<strong>{search.keywords}</strong>{choice.label.slice( search.keywords.length )}
|
|
155
|
+
|
|
156
|
+
</span>
|
|
157
|
+
) : */choice.label}
|
|
158
|
+
</Button>
|
|
159
|
+
</li>
|
|
160
|
+
)
|
|
161
|
+
})}
|
|
55
162
|
|
|
56
|
-
|
|
163
|
+
{((!required || !validator?.options.min) && noneSelection) && (
|
|
164
|
+
<li>
|
|
165
|
+
<Button size="s" onClick={() => onChange(multiple ? [] : undefined)}
|
|
166
|
+
suffix={(current === undefined || (multiple && current.length === 0)) && <i src="check" class="fg primary" />}>
|
|
167
|
+
{noneSelection}
|
|
168
|
+
</Button>
|
|
169
|
+
</li>
|
|
170
|
+
)}
|
|
171
|
+
</ul>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
57
174
|
)
|
|
175
|
+
|
|
176
|
+
return <>
|
|
177
|
+
{inline ? selector : (
|
|
178
|
+
<Dropdown {...props} content={selector} iconR="chevron-down" refModal={refModal}>
|
|
179
|
+
|
|
180
|
+
{currentList.length === 0
|
|
181
|
+
? title
|
|
182
|
+
: currentList.map(choice => choice.label).join(', ')}
|
|
183
|
+
|
|
184
|
+
</Dropdown>
|
|
185
|
+
)}
|
|
186
|
+
|
|
187
|
+
{errors?.length && (
|
|
188
|
+
<div class="fg error txt-left">
|
|
189
|
+
{errors.join('. ')}
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
</>
|
|
58
193
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// Npm
|
|
6
6
|
import React, { JSX } from 'react';
|
|
7
7
|
import { ComponentChild } from 'preact';
|
|
8
|
+
import type { StateUpdater } from 'preact/hooks';
|
|
8
9
|
|
|
9
10
|
// Composants
|
|
10
11
|
import Bouton, { Props as PropsBouton } from '@client/components/button';
|
|
@@ -24,34 +25,17 @@ import useContexte from '@/client/context';
|
|
|
24
25
|
export type Props = {
|
|
25
26
|
id?: string,
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
afficher?: boolean,
|
|
29
|
-
fermer?: (funcFermer: () => void) => void,
|
|
30
|
-
position?: TSide,
|
|
31
|
-
frame?: HTMLElement,
|
|
32
|
-
|
|
28
|
+
// Display
|
|
33
29
|
content?: JSX.Element,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
tag?: string,
|
|
30
|
+
state: [boolean, StateUpdater<boolean>],
|
|
37
31
|
width?: number | string,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
onClick?: (donnees: TDonnee, index: number) => void,
|
|
47
|
-
lien?: (donnees: TDonnee, index: number) => string,
|
|
48
|
-
bouton?: (donnees: TDonnee, index: number) => PropsBouton
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export type TActionsPopover = {
|
|
52
|
-
show: () => void,
|
|
53
|
-
hide: () => void,
|
|
54
|
-
toggle: () => void
|
|
32
|
+
disable?: boolean
|
|
33
|
+
// Position
|
|
34
|
+
frame?: HTMLElement,
|
|
35
|
+
side?: TSide,
|
|
36
|
+
// Tag
|
|
37
|
+
children: JSX.Element | [JSX.Element],
|
|
38
|
+
tag?: string,
|
|
55
39
|
}
|
|
56
40
|
|
|
57
41
|
/*----------------------------------
|
|
@@ -64,116 +48,58 @@ export default (props: Props) => {
|
|
|
64
48
|
|
|
65
49
|
let {
|
|
66
50
|
id,
|
|
67
|
-
|
|
68
|
-
|
|
51
|
+
|
|
52
|
+
content, state, width, disable,
|
|
53
|
+
|
|
54
|
+
frame, side,
|
|
55
|
+
|
|
56
|
+
children, tag,
|
|
57
|
+
|
|
69
58
|
...autresProps
|
|
70
59
|
} = props;
|
|
71
60
|
|
|
72
|
-
const [
|
|
73
|
-
aff: false,
|
|
74
|
-
position: undefined
|
|
75
|
-
});
|
|
76
|
-
|
|
61
|
+
const [position, setPosition] = React.useState(undefined);
|
|
77
62
|
const refCont = React.useRef<HTMLElement>(null);
|
|
78
63
|
const refPop = React.useRef<HTMLElement>(null);
|
|
79
64
|
|
|
80
|
-
const
|
|
81
|
-
if (!controleViaProps) {
|
|
82
|
-
afficher = state.aff;
|
|
83
|
-
fermer = () => setState((stateA) => ({ ...stateA, aff: false }));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// L'assignement d'un id à une popover permet de controler l'affichage de ce dernier depuis n'importe quel autre composant
|
|
87
|
-
if (id !== undefined) {
|
|
88
|
-
ctx.popovers[ id ] = {
|
|
89
|
-
show: () => setState(s => ({ ...s, aff: true })),
|
|
90
|
-
hide: () => setState(s => ({ ...s, aff: false })),
|
|
91
|
-
toggle: () => setState(s => ({ ...s, aff: !s.aff })),
|
|
92
|
-
}
|
|
93
|
-
}
|
|
65
|
+
const [shown, show] = state;
|
|
94
66
|
|
|
95
67
|
// Màj visibilite
|
|
96
68
|
React.useEffect(() => {
|
|
97
|
-
if (
|
|
98
|
-
|
|
69
|
+
if (shown === true) {
|
|
99
70
|
// Positionnement si affichage
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
position: getPosition(
|
|
71
|
+
setPosition(
|
|
72
|
+
getPosition(
|
|
103
73
|
refCont.current,
|
|
104
74
|
refPop.current,
|
|
105
75
|
false,
|
|
106
76
|
position,
|
|
107
77
|
frame || document.getElementById('page')
|
|
108
78
|
)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const isPageElement = deepContains([document.getElementById('page')], refPop.current);
|
|
112
|
-
if (!isPageElement)
|
|
113
|
-
document.body.classList.add('focus-popup');
|
|
114
|
-
|
|
115
|
-
} else {
|
|
116
|
-
|
|
117
|
-
document.body.classList.remove('focus-popup');
|
|
79
|
+
);
|
|
118
80
|
|
|
81
|
+
//return blurable([refCont, () => show(false)])
|
|
119
82
|
}
|
|
120
83
|
|
|
121
|
-
|
|
122
|
-
onVisibleChange(afficher);
|
|
123
|
-
|
|
124
|
-
if (afficher === true)
|
|
125
|
-
return blurable([refCont, () => fermer()])
|
|
126
|
-
|
|
127
|
-
}, [afficher]);
|
|
84
|
+
}, [shown]);
|
|
128
85
|
|
|
129
86
|
if (!autresProps.className)
|
|
130
87
|
autresProps.className = '';
|
|
131
88
|
|
|
132
89
|
autresProps.className += ' contPopover';
|
|
133
90
|
|
|
134
|
-
const active =
|
|
91
|
+
const active = shown && !disable;
|
|
135
92
|
if (active) {
|
|
136
93
|
autresProps.className += ' active';
|
|
137
94
|
}
|
|
138
95
|
|
|
139
|
-
/*if (props.nom === 'destType')
|
|
140
|
-
console.log('AFFICHER POPOVER', afficher, 'controleViaProps', controleViaProps, 'autresProps.className', autresProps.className);*/
|
|
141
|
-
|
|
142
96
|
const Tag = tag || 'div';
|
|
143
97
|
|
|
144
|
-
if (!Array.isArray( children ))
|
|
145
|
-
children = [children];
|
|
146
|
-
|
|
147
|
-
if (menu !== undefined)
|
|
148
|
-
content = (
|
|
149
|
-
<ul className="menu v">
|
|
150
|
-
{menu.actions.map(({ multi, bouton, lien, label, icone, onClick }: TAction<TDonnee>) => (
|
|
151
|
-
<li>
|
|
152
|
-
<Bouton
|
|
153
|
-
{...(bouton ? (multi
|
|
154
|
-
? bouton([menu.data], [menu.index])
|
|
155
|
-
: bouton(menu.data, menu.index)
|
|
156
|
-
) : {})}
|
|
157
|
-
icon={icone}
|
|
158
|
-
onClick={onClick && (() => multi
|
|
159
|
-
? onClick([menu.data], [menu.index])
|
|
160
|
-
: onClick(menu.data, menu.index)
|
|
161
|
-
)}
|
|
162
|
-
link={lien && lien(menu.data, menu.index)}
|
|
163
|
-
forme="lien"
|
|
164
|
-
>
|
|
165
|
-
{label}
|
|
166
|
-
</Bouton>
|
|
167
|
-
</li>
|
|
168
|
-
))}
|
|
169
|
-
</ul>
|
|
170
|
-
)
|
|
171
|
-
|
|
172
98
|
return (
|
|
173
99
|
<Tag
|
|
174
100
|
style={{
|
|
175
101
|
position: 'relative',
|
|
176
|
-
...(
|
|
102
|
+
...(shown ? {
|
|
177
103
|
zIndex: 11
|
|
178
104
|
} : {})
|
|
179
105
|
}}
|
|
@@ -183,33 +109,25 @@ export default (props: Props) => {
|
|
|
183
109
|
}}
|
|
184
110
|
{...autresProps}
|
|
185
111
|
>
|
|
186
|
-
{
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
setState({ aff: nouvEtat, position: state.position });
|
|
193
|
-
},
|
|
194
|
-
}))}
|
|
112
|
+
{React.cloneElement( children, {
|
|
113
|
+
onClick: (e) => {
|
|
114
|
+
show(isShown => !isShown);
|
|
115
|
+
}
|
|
116
|
+
})}
|
|
195
117
|
|
|
196
118
|
{active && React.cloneElement(content, {
|
|
197
|
-
className: (content.props.className || '') + ' card white popover' + (
|
|
198
|
-
onClick: () => {
|
|
199
|
-
if (fermer && !interactions)
|
|
200
|
-
fermer();
|
|
201
|
-
},
|
|
119
|
+
className: (content.props.className || '') + ' card white popover' + (position ? ' pos_' + position.cote : ''),
|
|
202
120
|
ref: (ref: any) => {
|
|
203
121
|
if (ref !== null)
|
|
204
122
|
refPop.current = ref
|
|
205
123
|
},
|
|
206
124
|
style: {
|
|
207
125
|
...(content.props.style || {}),
|
|
208
|
-
...(
|
|
209
|
-
top:
|
|
210
|
-
left:
|
|
211
|
-
right:
|
|
212
|
-
bottom:
|
|
126
|
+
...(position ? {
|
|
127
|
+
top: position.css.top,
|
|
128
|
+
left: position.css.left,
|
|
129
|
+
right: position.css.right,
|
|
130
|
+
bottom: position.css.bottom,
|
|
213
131
|
} : {}),
|
|
214
132
|
...(width !== undefined ? { width: typeof width === 'number' ? width + 'rem' : width } : {})
|
|
215
133
|
}
|
|
@@ -19,8 +19,6 @@ import {
|
|
|
19
19
|
- TYPES
|
|
20
20
|
----------------------------------*/
|
|
21
21
|
|
|
22
|
-
//import { TTailleComposant } from '@client/components/_base/types';
|
|
23
|
-
|
|
24
22
|
type TEtape = {
|
|
25
23
|
val: number,
|
|
26
24
|
label?: string
|
|
@@ -44,7 +42,7 @@ export default ({
|
|
|
44
42
|
ratioCercle: number,
|
|
45
43
|
epaisseur: number,
|
|
46
44
|
etapes?: TEtape[],
|
|
47
|
-
taille?:
|
|
45
|
+
taille?: TComponentSize
|
|
48
46
|
}) => {
|
|
49
47
|
|
|
50
48
|
const refCont = React.useRef<HTMLDivElement>(null);
|
|
@@ -9,15 +9,14 @@ import { ComponentChild, RefObject } from 'preact';
|
|
|
9
9
|
// Core
|
|
10
10
|
import Button, { Props as ButtonProps } from '../button';
|
|
11
11
|
import { TDialogControls } from '../Dialog/Manager';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// Libs
|
|
15
|
-
import useContexte from '@/client/context';
|
|
12
|
+
import Popover from '../containers/Popover';
|
|
16
13
|
|
|
17
14
|
/*----------------------------------
|
|
18
15
|
- TYPES
|
|
19
16
|
----------------------------------*/
|
|
20
17
|
|
|
18
|
+
export type { TDialogControls } from '../Dialog/Manager';
|
|
19
|
+
|
|
21
20
|
export type Props = ButtonProps & {
|
|
22
21
|
content: ComponentChild,
|
|
23
22
|
refModal?: RefObject<TDialogControls>
|
|
@@ -28,23 +27,19 @@ export type Props = ButtonProps & {
|
|
|
28
27
|
----------------------------------*/
|
|
29
28
|
export default (props: Props) => {
|
|
30
29
|
|
|
31
|
-
const { modal } = useContexte();
|
|
32
|
-
|
|
33
30
|
let {
|
|
34
31
|
content,
|
|
35
32
|
refModal,
|
|
36
33
|
...buttonProps
|
|
37
34
|
} = props;
|
|
38
35
|
|
|
39
|
-
const
|
|
36
|
+
const popoverState = React.useState(false);
|
|
40
37
|
|
|
41
|
-
const
|
|
42
|
-
const modalInstance = modal.show(() => content);
|
|
43
|
-
if (refModal)
|
|
44
|
-
refModal.current = modalInstance;
|
|
45
|
-
}
|
|
38
|
+
const refButton = React.useRef<HTMLElement>(null);
|
|
46
39
|
|
|
47
40
|
return (
|
|
48
|
-
<
|
|
41
|
+
<Popover content={content} state={popoverState}>
|
|
42
|
+
<Button {...buttonProps} refElem={refButton} />
|
|
43
|
+
</Popover>
|
|
49
44
|
)
|
|
50
45
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
// Npm
|
|
6
6
|
import React from 'react';
|
|
7
|
-
import {
|
|
7
|
+
import type { StateUpdater } from 'preact/hooks';
|
|
8
8
|
|
|
9
9
|
// Core libs
|
|
10
10
|
import { useState } from '@client/hooks';
|
|
@@ -14,9 +14,13 @@ import { useState } from '@client/hooks';
|
|
|
14
14
|
----------------------------------*/
|
|
15
15
|
|
|
16
16
|
export type InputBaseProps<TValue> = {
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
title: string, // Now mandatory
|
|
19
|
-
|
|
19
|
+
required?: boolean,
|
|
20
|
+
errors?: string[],
|
|
21
|
+
|
|
22
|
+
value: TValue,
|
|
23
|
+
onChange?: StateUpdater<TValue>,
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
export type TInputState<TValue> = {
|
|
@@ -24,6 +28,7 @@ export type TInputState<TValue> = {
|
|
|
24
28
|
fieldProps: {[key: string]: any},
|
|
25
29
|
valueSource: 'internal'|'external',
|
|
26
30
|
focus: boolean,
|
|
31
|
+
changed: boolean,
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
/*----------------------------------
|
|
@@ -43,12 +48,18 @@ export function useInput<TValue>(
|
|
|
43
48
|
value: externalValue !== undefined ? externalValue : defaultValue,
|
|
44
49
|
valueSource: 'external',
|
|
45
50
|
fieldProps: {},
|
|
46
|
-
focus: false
|
|
51
|
+
focus: false,
|
|
52
|
+
changed: false
|
|
47
53
|
});
|
|
48
54
|
|
|
49
|
-
const setValue = (value: TValue) => setState({ value, valueSource: 'internal' });
|
|
55
|
+
const setValue = (value: TValue) => setState({ value, valueSource: 'internal', changed: true });
|
|
50
56
|
|
|
51
57
|
const commitValue = () => {
|
|
58
|
+
|
|
59
|
+
// Avoid to change parent component state at first render
|
|
60
|
+
if (state.changed === false)
|
|
61
|
+
return;
|
|
62
|
+
|
|
52
63
|
console.log(`[input] Commit value:`, state.value);
|
|
53
64
|
if (onChange !== undefined)
|
|
54
65
|
onChange(state.value);
|
|
@@ -59,7 +70,7 @@ export function useInput<TValue>(
|
|
|
59
70
|
|
|
60
71
|
if (externalValue !== undefined && externalValue !== state.value) {
|
|
61
72
|
console.log("External value change", externalValue);
|
|
62
|
-
setState({ value: externalValue, valueSource: 'external' })
|
|
73
|
+
setState({ value: externalValue, valueSource: 'external', changed: true })
|
|
63
74
|
}
|
|
64
75
|
|
|
65
76
|
}, [externalValue]);
|
|
@@ -21,25 +21,29 @@ export type Props = {
|
|
|
21
21
|
suffix?: React.VNode,
|
|
22
22
|
iconR?: string,
|
|
23
23
|
|
|
24
|
+
// State
|
|
25
|
+
inputRef?: React.Ref<HTMLInputElement>
|
|
26
|
+
|
|
24
27
|
// Behavior
|
|
25
28
|
type?: 'email' | 'password' | 'longtext',
|
|
26
29
|
choice?: string[] | ((input: string) => Promise<string[]>),
|
|
27
|
-
multiple?: boolean,
|
|
28
30
|
|
|
29
31
|
// Actions
|
|
30
32
|
onPressEnter?: (value: string) => void,
|
|
31
|
-
inputRef?: React.Ref<HTMLInputElement>
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/*----------------------------------
|
|
35
36
|
- COMPOSANT
|
|
36
37
|
----------------------------------*/
|
|
37
38
|
export default ({
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
// Decoration
|
|
40
|
+
icon, prefix, suffix, iconR, required,
|
|
41
|
+
// State
|
|
42
|
+
inputRef, errors,
|
|
43
|
+
// Behavior
|
|
44
|
+
type, choice,
|
|
45
|
+
// Actions
|
|
41
46
|
onPressEnter,
|
|
42
|
-
inputRef,
|
|
43
47
|
...props
|
|
44
48
|
}: Props & InputBaseProps<string> & Omit<JSX.HTMLAttributes<HTMLInputElement>, 'onChange'>) => {
|
|
45
49
|
|
|
@@ -87,7 +91,7 @@ export default ({
|
|
|
87
91
|
} else if (type === 'longtext') {
|
|
88
92
|
|
|
89
93
|
prefix = prefix || <i src="text" />;
|
|
90
|
-
Tag = TextareaAutosize;
|
|
94
|
+
Tag = 'textarea'//TextareaAutosize;
|
|
91
95
|
|
|
92
96
|
}
|
|
93
97
|
|
|
@@ -100,6 +104,8 @@ export default ({
|
|
|
100
104
|
className += ' empty';
|
|
101
105
|
if (focus)
|
|
102
106
|
className += ' focus';
|
|
107
|
+
if (errors?.length)
|
|
108
|
+
className += ' error';
|
|
103
109
|
|
|
104
110
|
if (props.className !== undefined)
|
|
105
111
|
className += ' ' + props.className;
|
|
@@ -107,7 +113,7 @@ export default ({
|
|
|
107
113
|
/*----------------------------------
|
|
108
114
|
- RENDER
|
|
109
115
|
----------------------------------*/
|
|
110
|
-
return
|
|
116
|
+
return <>
|
|
111
117
|
<div class={className} onClick={() => refInput.current?.focus()}>
|
|
112
118
|
|
|
113
119
|
{prefix}
|
|
@@ -131,11 +137,18 @@ export default ({
|
|
|
131
137
|
}}
|
|
132
138
|
/>
|
|
133
139
|
|
|
134
|
-
<label>{props.title}
|
|
140
|
+
<label>{props.title}{required && (
|
|
141
|
+
<span class="fg error"> *</span>
|
|
142
|
+
)}</label>
|
|
135
143
|
</div>
|
|
136
144
|
|
|
137
145
|
{suffix}
|
|
138
146
|
|
|
139
147
|
</div>
|
|
140
|
-
|
|
148
|
+
{errors?.length && (
|
|
149
|
+
<div class="fg error txt-left">
|
|
150
|
+
{errors.join('. ')}
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
153
|
+
</>
|
|
141
154
|
}
|