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.
Files changed (49) hide show
  1. package/package.json +1 -1
  2. package/src/client/app/component.tsx +1 -2
  3. package/src/client/app/index.ts +2 -6
  4. package/src/client/assets/css/components/button.less +4 -3
  5. package/src/client/assets/css/components/card.less +26 -11
  6. package/src/client/assets/css/components/lists.less +1 -1
  7. package/src/client/assets/css/components/other.less +0 -11
  8. package/src/client/assets/css/components.less +17 -3
  9. package/src/client/assets/css/core.less +55 -0
  10. package/src/client/assets/css/theme.less +0 -4
  11. package/src/client/assets/css/utils/layouts.less +1 -1
  12. package/src/client/assets/css/utils/medias.less +0 -47
  13. package/src/client/components/Dialog/Manager.tsx +1 -4
  14. package/src/client/components/Dialog/card.tsx +1 -1
  15. package/src/client/components/Dialog/index.less +2 -2
  16. package/src/client/components/Form.ts +154 -0
  17. package/src/client/components/{Form → Form_old}/index.tsx +0 -0
  18. package/src/client/components/{Form → Form_old}/index.tsx.old +0 -0
  19. package/src/client/components/Select/index.tsx +159 -24
  20. package/src/client/components/containers/Popover/index.tsx +38 -120
  21. package/src/client/components/data/progressbar/circular/index.tsx +1 -3
  22. package/src/client/components/dropdown/index.tsx +8 -13
  23. package/src/client/components/inputv3/base.less +0 -1
  24. package/src/client/components/inputv3/base.tsx +17 -6
  25. package/src/client/components/inputv3/string/index.tsx +23 -10
  26. package/src/client/services/router/components/Page.tsx +16 -18
  27. package/src/client/services/router/components/router.tsx +3 -3
  28. package/src/client/services/router/index.tsx +17 -14
  29. package/src/client/services/router/request/api.ts +6 -3
  30. package/src/client/services/router/response/index.tsx +4 -0
  31. package/src/client/services/router/response/page.ts +2 -1
  32. package/src/common/router/index.ts +1 -1
  33. package/src/common/router/layouts.ts +38 -6
  34. package/src/common/router/register.ts +3 -9
  35. package/src/common/router/request/api.ts +3 -1
  36. package/src/common/validation/index.ts +1 -1
  37. package/src/common/validation/schema.ts +8 -3
  38. package/src/common/validation/validators.ts +21 -10
  39. package/src/server/app/index.ts +2 -1
  40. package/src/server/services/console/bugReporter.ts +2 -19
  41. package/src/server/services/database/index.ts +27 -19
  42. package/src/server/services/router/index.ts +3 -3
  43. package/src/server/services/router/request/api.ts +3 -0
  44. package/src/server/services/router/response/index.ts +7 -4
  45. package/src/server/services/router/response/mask/Filter.ts +16 -72
  46. package/src/server/services/router/response/mask/index.ts +4 -7
  47. package/src/server/services/router/response/page/document.tsx +2 -2
  48. package/src/server/services/router/response/page/index.tsx +2 -2
  49. 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 Choices = string[]
23
+ export type Choice = { label: ComponentChild, value: string }
21
24
 
22
- type SearchResultsFunction = (keywords: string) => Choices
25
+ export type Choices = Choice[]
23
26
 
24
- export type Props = DropdownProps & {
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
- return (
39
- <Dropdown {...dropDownProps} content={(
40
- <ul class="card col menu">
41
- {choices.map( jt => (
42
- <li>
43
- <Button active={jt === value} onClick={() => {
44
- onChange(jt);
45
- refModal.current?.close();
46
- }}>
47
- {jt}
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
- </li>
50
- ))}
51
- </ul>
52
- )} iconR="chevron-down" refModal={refModal}>
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
- {value || title}
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
- </Dropdown>
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
- children: JSX.Element | [JSX.Element],
28
- afficher?: boolean,
29
- fermer?: (funcFermer: () => void) => void,
30
- position?: TSide,
31
- frame?: HTMLElement,
32
-
28
+ // Display
33
29
  content?: JSX.Element,
34
- menu?: { actions: TAction<any>[], data: any },
35
-
36
- tag?: string,
30
+ state: [boolean, StateUpdater<boolean>],
37
31
  width?: number | string,
38
- desactiver?: boolean
39
- }
40
-
41
- export type TAction<TDonnee> = {
42
- icone?: TIcons,
43
- label: ComponentChild,
44
- multi?: boolean,
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
- children, content, menu, tag, width, onVisibleChange, interactions, desactiver, fermer, frame,
68
- position, afficher,
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 [state, setState] = React.useState({
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 controleViaProps = 'afficher' in props;
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 (afficher === true) {
98
-
69
+ if (shown === true) {
99
70
  // Positionnement si affichage
100
- setState((stateA) => ({
101
- ...stateA,
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
- if (onVisibleChange)
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 = afficher && !desactiver;
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
- ...(afficher ? {
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
- {controleViaProps
187
- ? children // Les events de clic sont controlés par les props afficher et fermer
188
- : children.map(child => React.cloneElement(child, {
189
- onClick: () => {
190
- const nouvEtat = !afficher;
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' + (state.position ? ' pos_' + state.position.cote : ''),
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
- ...(state.position ? {
209
- top: state.position.css.top,
210
- left: state.position.css.left,
211
- right: state.position.css.right,
212
- bottom: state.position.css.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?: TTailleComposant
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
- export type { TDialogControls } from '../Dialog/Manager';
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 refButton = React.useRef<HTMLElement>(null);
36
+ const popoverState = React.useState(false);
40
37
 
41
- const open = () => {
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
- <Button {...buttonProps} onClick={(open)} refElem={refButton} />
41
+ <Popover content={content} state={popoverState}>
42
+ <Button {...buttonProps} refElem={refButton} />
43
+ </Popover>
49
44
  )
50
45
  }
@@ -25,7 +25,6 @@
25
25
  //box-shadow: 1px 3px 0 fade(#000,4%), 0 1px 2px 0 fade(#000,2%);
26
26
 
27
27
  > i {
28
- margin-left: 0.5em;
29
28
  color: var(--cTxtDesc);
30
29
  }
31
30
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  // Npm
6
6
  import React from 'react';
7
- import { ComponentChild } from 'preact';
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
- value: TValue,
17
+
18
18
  title: string, // Now mandatory
19
- onChange?: (newValue: TValue) => void,
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
- // Behavoir
39
- type,
40
- icon, prefix, suffix, iconR,
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}</label>
140
+ <label>{props.title}{required && (
141
+ <span class="fg error">&nbsp;*</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
  }