5htp-core 0.5.0 → 0.5.1-1
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/assets/css/core.less +5 -3
- package/src/client/assets/css/text/icons.less +4 -1
- package/src/client/assets/css/text/text.less +1 -1
- package/src/client/assets/css/text/titres.less +4 -3
- package/src/client/assets/css/theme.less +2 -1
- package/src/client/assets/css/utils/layouts.less +1 -0
- package/src/client/assets/css/utils/sizing.less +15 -15
- package/src/client/components/Dialog/Manager.tsx +1 -3
- package/src/client/components/Dialog/index.less +6 -9
- package/src/client/components/Form.ts +62 -31
- package/src/client/components/Select/index.tsx +1 -1
- package/src/client/components/Table/index.tsx +40 -6
- package/src/client/components/button.tsx +36 -23
- package/src/client/components/containers/Popover/getPosition.ts +48 -28
- package/src/client/components/containers/Popover/index.tsx +9 -3
- package/src/client/components/inputv3/Rte/Editor.tsx +64 -5
- package/src/client/components/inputv3/Rte/ToolbarPlugin/BlockFormat.tsx +1 -1
- package/src/client/components/inputv3/Rte/index.tsx +11 -76
- package/src/client/components/inputv3/Rte/plugins/DraggableBlockPlugin/index.css +1 -1
- package/src/client/components/inputv3/Rte/style.less +1 -25
- package/src/client/components/inputv3/base.tsx +1 -1
- package/src/client/components/inputv3/index.tsx +7 -1
- package/src/client/services/router/components/router.tsx +4 -3
- package/src/client/services/router/index.tsx +2 -1
- package/src/client/utils/dom.ts +1 -1
- package/src/common/errors/index.tsx +18 -6
- package/src/common/router/index.ts +2 -0
- package/src/common/validation/validators.ts +1 -0
- package/src/server/services/auth/index.ts +0 -9
- package/src/server/services/database/index.ts +2 -2
- package/src/server/services/router/http/index.ts +5 -9
- package/src/server/services/router/index.ts +62 -56
- package/src/server/services/router/request/index.ts +11 -0
- package/src/server/services/router/response/index.ts +21 -0
- package/src/server/services/router/response/page/document.tsx +1 -4
- package/src/server/services/router/response/page/index.tsx +4 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "5htp-core",
|
|
3
3
|
"description": "Convenient TypeScript framework designed for Performance and Productivity.",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.1-1",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/5htp-core.git",
|
|
7
7
|
"license": "MIT",
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
@import (reference) '@/client/assets/vars.less';
|
|
2
2
|
|
|
3
3
|
// Utils
|
|
4
|
-
@import './utils/sizing.less';
|
|
5
|
-
@import './utils/spacing.less';
|
|
6
4
|
@import (reference) "./theme.less";
|
|
7
5
|
|
|
8
6
|
// Apply the theme class
|
|
9
7
|
.bg {
|
|
10
|
-
background: var(--cBg);
|
|
8
|
+
background-color: var(--cBg);
|
|
11
9
|
color: var(--cTxtBase);
|
|
12
10
|
|
|
13
11
|
&.img {
|
|
@@ -120,4 +118,8 @@ body {
|
|
|
120
118
|
// Import components style (always after variables declaration)
|
|
121
119
|
@import "@client/assets/css/components.less";
|
|
122
120
|
|
|
121
|
+
// Make utilisy classes priority compared to components
|
|
122
|
+
@import './utils/sizing.less';
|
|
123
|
+
@import './utils/spacing.less';
|
|
124
|
+
|
|
123
125
|
@import '@/client/assets/theme.less';
|
|
@@ -46,7 +46,6 @@ i {
|
|
|
46
46
|
flex: 0 0 @sizeComponent * 0.75;
|
|
47
47
|
height: @sizeComponent * 0.75;
|
|
48
48
|
line-height: @sizeComponent * 0.75;
|
|
49
|
-
//font-size: 0.9em;
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
&.unit {
|
|
@@ -88,6 +87,10 @@ i.logo {
|
|
|
88
87
|
border: none; // For img``
|
|
89
88
|
|
|
90
89
|
background-color: var(--cBg);
|
|
90
|
+
|
|
91
|
+
&.circle {
|
|
92
|
+
border-radius: 50%;
|
|
93
|
+
}
|
|
91
94
|
}
|
|
92
95
|
|
|
93
96
|
i.logo {
|
|
@@ -20,9 +20,10 @@ h3 {
|
|
|
20
20
|
color: var(--cTxtAccent);
|
|
21
21
|
font-weight: inherit;
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
// Targets the whte card inside bg images, which makes no sense
|
|
24
|
+
// .bg.img & {
|
|
25
|
+
// color: var(--cTxtImportant);
|
|
26
|
+
// }
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -47,52 +47,52 @@
|
|
|
47
47
|
|
|
48
48
|
// Fixes
|
|
49
49
|
.w-@{taille1} {
|
|
50
|
-
width: @taille1 * @sizingUnit;
|
|
50
|
+
width: @taille1 * @sizingUnit !important;
|
|
51
51
|
}
|
|
52
52
|
.h-@{taille1} {
|
|
53
|
-
height: @taille1 * @sizingUnit;
|
|
53
|
+
height: @taille1 * @sizingUnit !important;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
.row > .w-@{taille1},
|
|
57
57
|
.col > .h-@{taille1} {
|
|
58
|
-
flex: 0 0 @taille1 * @sizingUnit;
|
|
58
|
+
flex: 0 0 @taille1 * @sizingUnit !important;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
// Min - max
|
|
62
62
|
.w-@{taille1}-a {
|
|
63
|
-
min-width: @taille1 * @sizingUnit;
|
|
63
|
+
min-width: @taille1 * @sizingUnit !important;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
.w-a-@{taille1} {
|
|
67
|
-
max-width: @taille1 * @sizingUnit;
|
|
68
|
-
width: 100
|
|
67
|
+
max-width: @taille1 * @sizingUnit !important;
|
|
68
|
+
width: 100% !important; // We take the maximum space wecan
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
.h-@{taille1}-a {
|
|
72
|
-
min-height: @taille1 * @sizingUnit;
|
|
72
|
+
min-height: @taille1 * @sizingUnit !important;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
.h-a-@{taille1} {
|
|
76
|
-
max-height: @taille1 * @sizingUnit;
|
|
77
|
-
height: 100
|
|
76
|
+
max-height: @taille1 * @sizingUnit !important;
|
|
77
|
+
height: 100% !important; // We take the maximum space wecan
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// Ranges
|
|
81
81
|
.taillesMax(@tailleMax2, @taille2: 0) when (@taille2 <= @tailleMax2) {
|
|
82
82
|
|
|
83
83
|
.w-@{taille1}-@{taille2} {
|
|
84
|
-
min-width: @taille1 * @sizingUnit;
|
|
85
|
-
max-width: @taille2 * @sizingUnit;
|
|
86
|
-
width: 100
|
|
84
|
+
min-width: @taille1 * @sizingUnit !important;
|
|
85
|
+
max-width: @taille2 * @sizingUnit !important;
|
|
86
|
+
width: 100% !important; // We take the maximum space wecan
|
|
87
87
|
|
|
88
88
|
.row > & {
|
|
89
89
|
flex: 1;
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
.h-@{taille1}-@{taille2} {
|
|
93
|
-
min-height: @taille1 * @sizingUnit;
|
|
94
|
-
max-height: @taille2 * @sizingUnit;
|
|
95
|
-
height: 100
|
|
93
|
+
min-height: @taille1 * @sizingUnit !important;
|
|
94
|
+
max-height: @taille2 * @sizingUnit !important;
|
|
95
|
+
height: 100% !important; // We take the maximum space wecan
|
|
96
96
|
|
|
97
97
|
.col > & {
|
|
98
98
|
flex: 1;
|
|
@@ -179,9 +179,7 @@ export const createDialog = (app: Application, isToast: boolean): DialogActions
|
|
|
179
179
|
|
|
180
180
|
if (!isToast)
|
|
181
181
|
render = (
|
|
182
|
-
<div class="modal"
|
|
183
|
-
e.target.classList.contains('modal') && close(false)
|
|
184
|
-
}>
|
|
182
|
+
<div class="modal">
|
|
185
183
|
{render}
|
|
186
184
|
</div>
|
|
187
185
|
)
|
|
@@ -53,19 +53,13 @@
|
|
|
53
53
|
display: flex;
|
|
54
54
|
flex-direction: column;
|
|
55
55
|
align-items: center;
|
|
56
|
-
justify-content:
|
|
56
|
+
justify-content: center;
|
|
57
57
|
gap: @spacing;
|
|
58
58
|
z-index: @modal-zindex;
|
|
59
59
|
|
|
60
60
|
background: fade(#000, 20%);
|
|
61
61
|
border-radius: @radius;
|
|
62
62
|
|
|
63
|
-
// Desktop = vertically center the modal
|
|
64
|
-
@media (min-width: 900px) {
|
|
65
|
-
justify-content: center;
|
|
66
|
-
//padding: @spacing;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
63
|
// Pour les animations (ex: conffetis
|
|
70
64
|
> canvas {
|
|
71
65
|
position: absolute;
|
|
@@ -79,7 +73,9 @@
|
|
|
79
73
|
|
|
80
74
|
position: relative;
|
|
81
75
|
min-width: 300px;
|
|
82
|
-
max-height:
|
|
76
|
+
max-height: 99vh;
|
|
77
|
+
max-width: 99vw;
|
|
78
|
+
width: 400px; // Default width
|
|
83
79
|
box-shadow: none;
|
|
84
80
|
overflow-y: auto;
|
|
85
81
|
|
|
@@ -148,5 +144,6 @@
|
|
|
148
144
|
|
|
149
145
|
// Selecteur moins profond pour que les clases utilitaires (w-a-x) soient prioritaires
|
|
150
146
|
.modal > .card {
|
|
151
|
-
|
|
147
|
+
// Modal content should always be whiteys adapt from content width
|
|
148
|
+
//max-width: 500px;
|
|
152
149
|
}
|
|
@@ -8,7 +8,7 @@ import React from 'react';
|
|
|
8
8
|
// Core
|
|
9
9
|
import { InputErrorSchema } from '@common/errors';
|
|
10
10
|
import type { Schema } from '@common/validation';
|
|
11
|
-
import type { TValidationResult } from '@common/validation/schema';
|
|
11
|
+
import type { TValidationResult, TValidateOptions } from '@common/validation/schema';
|
|
12
12
|
import useContext from '@/client/context';
|
|
13
13
|
|
|
14
14
|
// Exports
|
|
@@ -36,19 +36,22 @@ export type Form<TFormData extends {} = {}> = {
|
|
|
36
36
|
fields: FieldsAttrs<TFormData>,
|
|
37
37
|
data: TFormData,
|
|
38
38
|
options: TFormOptions<TFormData>,
|
|
39
|
+
backup?: Partial<TFormData>,
|
|
39
40
|
|
|
40
41
|
// Actions
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
setBackup: (backup: Partial<TFormData>) => void,
|
|
43
|
+
validate: (data: Partial<TFormData>, validateAll?: boolean) => TValidationResult<{}>,
|
|
44
|
+
set: (data: Partial<TFormData>, merge?: boolean) => void,
|
|
43
45
|
submit: (additionnalData?: Partial<TFormData>) => Promise<any>,
|
|
44
46
|
|
|
45
47
|
} & FormState
|
|
46
48
|
|
|
47
|
-
type FormState = {
|
|
49
|
+
type FormState<TFormData extends {} = {}> = {
|
|
48
50
|
isLoading: boolean,
|
|
49
51
|
hasChanged: boolean,
|
|
50
52
|
errorsCount: number,
|
|
51
53
|
errors: { [fieldName: string]: string[] },
|
|
54
|
+
backup?: Partial<TFormData>,
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
/*----------------------------------
|
|
@@ -57,7 +60,7 @@ type FormState = {
|
|
|
57
60
|
export default function useForm<TFormData extends {}>(
|
|
58
61
|
schema: Schema<TFormData>,
|
|
59
62
|
options: TFormOptions<TFormData> = {}
|
|
60
|
-
): [ Form
|
|
63
|
+
): [ Form<TFormData>, FieldsAttrs<TFormData> ] {
|
|
61
64
|
|
|
62
65
|
const context = useContext();
|
|
63
66
|
|
|
@@ -65,11 +68,12 @@ export default function useForm<TFormData extends {}>(
|
|
|
65
68
|
- INIT
|
|
66
69
|
----------------------------------*/
|
|
67
70
|
|
|
68
|
-
const [state, setState] = React.useState<FormState
|
|
69
|
-
hasChanged: options.data !== undefined,
|
|
71
|
+
const [state, setState] = React.useState<FormState<TFormData>>({
|
|
72
|
+
hasChanged: false,//options.data !== undefined,
|
|
70
73
|
isLoading: false,
|
|
71
74
|
errorsCount: 0,
|
|
72
|
-
errors: {}
|
|
75
|
+
errors: {},
|
|
76
|
+
backup: undefined
|
|
73
77
|
});
|
|
74
78
|
|
|
75
79
|
const initialData: Partial<TFormData> = options.data || {};
|
|
@@ -78,43 +82,54 @@ export default function useForm<TFormData extends {}>(
|
|
|
78
82
|
const fields = React.useRef<FieldsAttrs<TFormData> | null>(null);
|
|
79
83
|
const [data, setData] = React.useState< Partial<TFormData> >(initialData);
|
|
80
84
|
|
|
81
|
-
//
|
|
85
|
+
// When typed data changes
|
|
82
86
|
React.useEffect(() => {
|
|
83
87
|
|
|
84
88
|
// Validate
|
|
85
|
-
validate(data,
|
|
89
|
+
validate(data, { ignoreMissing: true });
|
|
86
90
|
|
|
87
91
|
// Autosave
|
|
88
|
-
if (options.autoSave !== undefined) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
92
|
+
if (options.autoSave !== undefined && state.hasChanged) {
|
|
93
|
+
saveLocally(data, options.autoSave.id);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
}, [data]);
|
|
97
|
+
|
|
98
|
+
// On start
|
|
99
|
+
React.useEffect(() => {
|
|
100
|
+
|
|
101
|
+
// Restore backup
|
|
102
|
+
if (options.autoSave !== undefined && !state.hasChanged) {
|
|
103
|
+
|
|
104
|
+
const autosaved = localStorage.getItem('form.' + options.autoSave.id);
|
|
105
|
+
if (autosaved !== null) {
|
|
106
|
+
try {
|
|
107
|
+
console.log('[form] Parse autosaved from json:', autosaved);
|
|
108
|
+
setState(c => ({
|
|
109
|
+
...c,
|
|
110
|
+
backup: JSON.parse(autosaved)
|
|
111
|
+
}));
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error('[form] Failed to decode autosaved data from json:', autosaved);
|
|
101
114
|
}
|
|
102
115
|
}
|
|
103
116
|
}
|
|
104
117
|
|
|
105
|
-
}, [
|
|
118
|
+
}, []);
|
|
106
119
|
|
|
107
120
|
/*----------------------------------
|
|
108
121
|
- ACTIONS
|
|
109
122
|
----------------------------------*/
|
|
110
|
-
const validate = (allData: Partial<TFormData> = data,
|
|
123
|
+
const validate = (allData: Partial<TFormData> = data, opts: TValidateOptions<TFormData> = {}) => {
|
|
111
124
|
|
|
112
125
|
const validated = schema.validateWithDetails(allData, allData, {}, {
|
|
113
126
|
// Ignore the fields where the vlaue has not been changed
|
|
114
127
|
// if the validation was triggered via onChange
|
|
115
|
-
ignoreMissing:
|
|
128
|
+
ignoreMissing: false,
|
|
116
129
|
// The list of fields we should only validate
|
|
117
|
-
only: options.autoValidateOnly
|
|
130
|
+
only: options.autoValidateOnly,
|
|
131
|
+
// Custom options
|
|
132
|
+
...opts
|
|
118
133
|
});
|
|
119
134
|
|
|
120
135
|
// Update errors
|
|
@@ -224,19 +239,35 @@ export default function useForm<TFormData extends {}>(
|
|
|
224
239
|
- EXPOSE
|
|
225
240
|
----------------------------------*/
|
|
226
241
|
|
|
227
|
-
const form = {
|
|
242
|
+
const form: Form<TFormData> = {
|
|
243
|
+
|
|
228
244
|
fields: fields.current,
|
|
229
245
|
data,
|
|
230
|
-
set: data => {
|
|
231
|
-
|
|
246
|
+
set: (data, merge = true) => {
|
|
247
|
+
|
|
248
|
+
setState( current => ({
|
|
232
249
|
...current,
|
|
233
250
|
hasChanged: true
|
|
234
251
|
}));
|
|
235
|
-
|
|
252
|
+
|
|
253
|
+
setData( merge
|
|
254
|
+
? c => ({ ...c, ...data })
|
|
255
|
+
: data
|
|
256
|
+
);
|
|
236
257
|
},
|
|
258
|
+
|
|
237
259
|
validate,
|
|
238
260
|
submit,
|
|
239
261
|
options,
|
|
262
|
+
|
|
263
|
+
setBackup: (backup: Partial<TFormData>) => {
|
|
264
|
+
|
|
265
|
+
setState(c => ({ ...c, backup }));
|
|
266
|
+
|
|
267
|
+
if (options.autoSave)
|
|
268
|
+
localStorage.setItem('form.' + options.autoSave.id, JSON.stringify(backup));
|
|
269
|
+
},
|
|
270
|
+
|
|
240
271
|
...state
|
|
241
272
|
}
|
|
242
273
|
|
|
@@ -20,14 +20,20 @@ import Checkbox from '../inputv3/Checkbox';
|
|
|
20
20
|
export type TDonneeInconnue = { id: any } & {[cle: string]: any};
|
|
21
21
|
|
|
22
22
|
export type Props<TRow> = {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
columns: (row: TRow, rows: TRow[], index: number) => TColumn[];
|
|
23
|
+
|
|
24
|
+
// Appearence
|
|
26
25
|
stickyHeader?: boolean,
|
|
26
|
+
className?: string,
|
|
27
27
|
|
|
28
|
+
// Data
|
|
29
|
+
data: TRow[],
|
|
28
30
|
setData?: (rows: TRow[]) => void,
|
|
31
|
+
columns: (row: TRow, rows: TRow[], index: number) => TColumn[];
|
|
29
32
|
empty?: ComponentChild | false,
|
|
30
|
-
|
|
33
|
+
|
|
34
|
+
// Interactions
|
|
35
|
+
sort?: TSortOptions,
|
|
36
|
+
onSort?: (columnId: string | null, order: TSortOptions["order"]) => void,
|
|
31
37
|
|
|
32
38
|
selection?: [TRow[], React.SetStateAction<TRow[]>],
|
|
33
39
|
maxSelection?: number,
|
|
@@ -38,13 +44,19 @@ export type TColumn = JSX.HTMLAttributes<HTMLElement> & {
|
|
|
38
44
|
cell: ComponentChild,
|
|
39
45
|
raw?: number | string | boolean,
|
|
40
46
|
stick?: boolean,
|
|
47
|
+
sort?: TSortOptions
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type TSortOptions = {
|
|
51
|
+
id: string,
|
|
52
|
+
order: 'desc' | 'asc'
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
/*----------------------------------
|
|
44
56
|
- COMPOSANTS
|
|
45
57
|
----------------------------------*/
|
|
46
58
|
export default function Liste<TRow extends TDonneeInconnue>({
|
|
47
|
-
stickyHeader,
|
|
59
|
+
stickyHeader, onSort, sort: sorted,
|
|
48
60
|
data: rows, setData, empty,
|
|
49
61
|
selection: selectionState, maxSelection,
|
|
50
62
|
columns, ...props
|
|
@@ -99,6 +111,7 @@ export default function Liste<TRow extends TDonneeInconnue>({
|
|
|
99
111
|
|
|
100
112
|
{columns(row, rows, iDonnee).map(({
|
|
101
113
|
label, cell, class: className, raw,
|
|
114
|
+
sort,
|
|
102
115
|
stick, width, ...cellProps
|
|
103
116
|
}) => {
|
|
104
117
|
|
|
@@ -123,9 +136,30 @@ export default function Liste<TRow extends TDonneeInconnue>({
|
|
|
123
136
|
}
|
|
124
137
|
}
|
|
125
138
|
|
|
139
|
+
const isCurrentlySorted = sort && sorted && sorted.id === sort.id;
|
|
140
|
+
const isSortable = sort && onSort;
|
|
141
|
+
if (isSortable) {
|
|
142
|
+
classe += ' clickable';
|
|
143
|
+
cellProps.onClick = () => {
|
|
144
|
+
if (isCurrentlySorted)
|
|
145
|
+
onSort(null, sort.order);
|
|
146
|
+
else
|
|
147
|
+
onSort(sort.id, sort.order);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
126
151
|
if (iDonnee === 0) renduColonnes.push(
|
|
127
152
|
<th class={classe} {...cellProps}>
|
|
128
|
-
|
|
153
|
+
<div class="row sp-btw">
|
|
154
|
+
|
|
155
|
+
{isSortable ? (
|
|
156
|
+
<a>{label}</a>
|
|
157
|
+
) : label}
|
|
158
|
+
|
|
159
|
+
{isCurrentlySorted && (
|
|
160
|
+
<i src={sort.order === "asc" ? "caret-up" : "caret-down"} />
|
|
161
|
+
)}
|
|
162
|
+
</div>
|
|
129
163
|
</th>
|
|
130
164
|
);
|
|
131
165
|
|
|
@@ -23,7 +23,6 @@ export type Props = {
|
|
|
23
23
|
iconR?: ComponentChild,
|
|
24
24
|
|
|
25
25
|
prefix?: ComponentChild,
|
|
26
|
-
children?: ComponentChild | ComponentChild[],
|
|
27
26
|
suffix?: ComponentChild,
|
|
28
27
|
|
|
29
28
|
tag?: "a" | "button",
|
|
@@ -31,7 +30,6 @@ export type Props = {
|
|
|
31
30
|
shape?: 'default' | 'icon' | 'tile' | 'pill',
|
|
32
31
|
size?: TComponentSize,
|
|
33
32
|
class?: string,
|
|
34
|
-
title?: string,
|
|
35
33
|
|
|
36
34
|
state?: [string, React.StateUpdater<string>],
|
|
37
35
|
active?: boolean,
|
|
@@ -45,7 +43,14 @@ export type Props = {
|
|
|
45
43
|
submenu?: ComponentChild,
|
|
46
44
|
nav?: boolean | 'exact'
|
|
47
45
|
|
|
48
|
-
|
|
46
|
+
// SEO: if icon only, should provinde a hint (aria-label)
|
|
47
|
+
} & ({
|
|
48
|
+
hint: string,
|
|
49
|
+
children?: ComponentChild | ComponentChild[],
|
|
50
|
+
} | {
|
|
51
|
+
children: ComponentChild | ComponentChild[],
|
|
52
|
+
hint?: string,
|
|
53
|
+
}) & (TButtonProps | TLinkProps)
|
|
49
54
|
|
|
50
55
|
export type TButtonProps = {
|
|
51
56
|
|
|
@@ -84,6 +89,7 @@ export default ({
|
|
|
84
89
|
iconR, suffix,
|
|
85
90
|
submenu,
|
|
86
91
|
nav,
|
|
92
|
+
hint,
|
|
87
93
|
|
|
88
94
|
// Style
|
|
89
95
|
class: className,
|
|
@@ -125,6 +131,12 @@ export default ({
|
|
|
125
131
|
props.onClick = () => setActive(id);
|
|
126
132
|
}
|
|
127
133
|
|
|
134
|
+
// Hint
|
|
135
|
+
if (hint !== undefined) {
|
|
136
|
+
props['aria-label'] = hint;
|
|
137
|
+
props.title = hint;
|
|
138
|
+
}
|
|
139
|
+
|
|
128
140
|
// Shape classes
|
|
129
141
|
const classNames: string[] = ['btn'];
|
|
130
142
|
if (className)
|
|
@@ -171,35 +183,36 @@ export default ({
|
|
|
171
183
|
// Render
|
|
172
184
|
if ('link' in props || Tag === "a") {
|
|
173
185
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
// External = open in new tab by default
|
|
177
|
-
if (props.href && (props.href[0] !== '/' || props.href.startsWith('//')))
|
|
178
|
-
props.target = '_blank';
|
|
186
|
+
// Link (only if enabled)
|
|
187
|
+
if (!disabled) {
|
|
179
188
|
|
|
180
|
-
|
|
189
|
+
props.href = props.link;
|
|
190
|
+
|
|
191
|
+
// External = open in new tab by default
|
|
192
|
+
if (props.href && (props.href[0] !== '/' || props.href.startsWith('//')))
|
|
193
|
+
props.target = '_blank';
|
|
194
|
+
}
|
|
181
195
|
|
|
182
|
-
|
|
196
|
+
// Nav
|
|
197
|
+
if (nav && props.target === undefined) {
|
|
183
198
|
|
|
184
|
-
|
|
185
|
-
|
|
199
|
+
const checkIfCurrentUrl = (url: string) =>
|
|
200
|
+
isCurrentUrl(url, props.link, nav === 'exact');
|
|
186
201
|
|
|
187
|
-
|
|
202
|
+
React.useEffect(() => {
|
|
188
203
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
204
|
+
// Init
|
|
205
|
+
if (checkIfCurrentUrl(ctx.request.path))
|
|
206
|
+
setIsActive(true);
|
|
192
207
|
|
|
193
|
-
|
|
194
|
-
|
|
208
|
+
// On location change
|
|
209
|
+
return history?.listen(({ location }) => {
|
|
195
210
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
})
|
|
211
|
+
setIsActive( checkIfCurrentUrl(location.pathname) );
|
|
199
212
|
|
|
200
|
-
}
|
|
201
|
-
}
|
|
213
|
+
})
|
|
202
214
|
|
|
215
|
+
}, []);
|
|
203
216
|
}
|
|
204
217
|
|
|
205
218
|
Tag = 'a';
|