5htp-core 0.5.0 → 0.5.1-2

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 (39) hide show
  1. package/package.json +1 -1
  2. package/src/client/assets/css/components/button.less +0 -1
  3. package/src/client/assets/css/core.less +5 -3
  4. package/src/client/assets/css/text/icons.less +4 -1
  5. package/src/client/assets/css/text/text.less +1 -1
  6. package/src/client/assets/css/text/titres.less +4 -3
  7. package/src/client/assets/css/theme.less +2 -1
  8. package/src/client/assets/css/utils/layouts.less +1 -0
  9. package/src/client/assets/css/utils/sizing.less +15 -15
  10. package/src/client/components/Dialog/Manager.tsx +1 -3
  11. package/src/client/components/Dialog/index.less +6 -9
  12. package/src/client/components/Form.ts +62 -31
  13. package/src/client/components/Select/index.tsx +1 -1
  14. package/src/client/components/Table/index.tsx +40 -6
  15. package/src/client/components/button.tsx +40 -31
  16. package/src/client/components/containers/Popover/getPosition.ts +48 -28
  17. package/src/client/components/containers/Popover/index.tsx +9 -3
  18. package/src/client/components/inputv3/Rte/Editor.tsx +64 -5
  19. package/src/client/components/inputv3/Rte/ToolbarPlugin/BlockFormat.tsx +1 -1
  20. package/src/client/components/inputv3/Rte/index.tsx +11 -76
  21. package/src/client/components/inputv3/Rte/plugins/DraggableBlockPlugin/index.css +1 -1
  22. package/src/client/components/inputv3/Rte/style.less +1 -25
  23. package/src/client/components/inputv3/base.tsx +1 -1
  24. package/src/client/components/inputv3/index.tsx +7 -1
  25. package/src/client/services/router/components/Link.tsx +9 -5
  26. package/src/client/services/router/components/router.tsx +4 -3
  27. package/src/client/services/router/index.tsx +2 -1
  28. package/src/client/utils/dom.ts +1 -1
  29. package/src/common/errors/index.tsx +19 -7
  30. package/src/common/router/index.ts +2 -0
  31. package/src/common/validation/validators.ts +1 -0
  32. package/src/server/services/auth/index.ts +0 -9
  33. package/src/server/services/database/index.ts +2 -2
  34. package/src/server/services/router/http/index.ts +8 -11
  35. package/src/server/services/router/index.ts +62 -56
  36. package/src/server/services/router/request/index.ts +11 -0
  37. package/src/server/services/router/response/index.ts +21 -0
  38. package/src/server/services/router/response/page/document.tsx +1 -4
  39. 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.0",
4
+ "version": "0.5.1-2",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/5htp-core.git",
7
7
  "license": "MIT",
@@ -240,7 +240,6 @@
240
240
 
241
241
  // Give less imortance to buttons which are in lists
242
242
  color: var(--cTxtBase);
243
- box-shadow: none;
244
243
  padding: 0 1em; // Row display = more condensed
245
244
 
246
245
  &.active,
@@ -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 {
@@ -89,7 +89,7 @@ p, .p {
89
89
  .txt-base { color: var(--cTxtBase); }
90
90
  .txt-contenu { color: var(--cTxtPost); }
91
91
 
92
- strong,
92
+ //strong,
93
93
  .active,
94
94
  .txtImportant {
95
95
  color: var(--cTxtImportant);
@@ -20,9 +20,10 @@ h3 {
20
20
  color: var(--cTxtAccent);
21
21
  font-weight: inherit;
22
22
 
23
- .bg.img & {
24
- color: var(--cTxtImportant);
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
 
@@ -51,7 +51,8 @@
51
51
  --cBgActive: @bgActive;
52
52
  --cBgPressed: @bgPressed;
53
53
  & when (@apply = true) {
54
- background: var(--cBg);
54
+ // Don't overflow the other background image props with background:
55
+ background-color: var(--cBg);
55
56
  }
56
57
 
57
58
  // Accent
@@ -253,6 +253,7 @@
253
253
 
254
254
  &.sep-1 { // Bordure interne
255
255
 
256
+ align-items: stretch;
256
257
  background-color: var(--cLine2);
257
258
  gap: 1px;
258
259
  &.card {
@@ -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%; // We take the maximum space wecan
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%; // We take the maximum space wecan
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%; // We take the maximum space wecan
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%; // We take the maximum space wecan
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" onClick={e =>
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: flex-end;
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: 90vh;
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
- max-width: 500px;
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
- validate: (data: Partial<TFormData>) => TValidationResult<{}>,
42
- set: (data: Partial<TFormData>) => void,
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, FieldsAttrs<TFormData> ] {
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
- // Validate data when it changes
85
+ // When typed data changes
82
86
  React.useEffect(() => {
83
87
 
84
88
  // Validate
85
- validate(data, false);
89
+ validate(data, { ignoreMissing: true });
86
90
 
87
91
  // Autosave
88
- if (options.autoSave !== undefined) {
89
-
90
- if (state.hasChanged)
91
- saveLocally(data, options.autoSave.id);
92
- else {
93
- const autosaved = localStorage.getItem('form.' + options.autoSave.id);
94
- if (autosaved !== null) {
95
- try {
96
- console.log('[form] Parse autosaved from json:', autosaved);
97
- setData( JSON.parse(autosaved) );
98
- } catch (error) {
99
- console.error('[form] Failed to decode autosaved data from json:', autosaved);
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
- }, [data]);
118
+ }, []);
106
119
 
107
120
  /*----------------------------------
108
121
  - ACTIONS
109
122
  ----------------------------------*/
110
- const validate = (allData: Partial<TFormData> = data, validateAll: boolean = true) => {
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: !validateAll,
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
- setState(current => ({
246
+ set: (data, merge = true) => {
247
+
248
+ setState( current => ({
232
249
  ...current,
233
250
  hasChanged: true
234
251
  }));
235
- setData(data);
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
 
@@ -249,7 +249,7 @@ export default (props: Props) => {
249
249
 
250
250
  {Search}
251
251
 
252
- <ul class="row al-left wrap sp-05 scrollable mgt-1" style={{
252
+ <ul class="row al-left wrap sp-05 scrollable" style={{
253
253
  maxHeight: '30vh',
254
254
  }}>
255
255
  {selectedItems.map( choice => (
@@ -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
- data: TRow[],
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
- className?: string,
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
- {label}
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
 
@@ -7,6 +7,7 @@ import React from 'react';
7
7
  import { VNode, RefObject,ComponentChild } from 'preact';
8
8
 
9
9
  // Core
10
+ import { shouldOpenNewTab } from '@client/services/router/components/Link';
10
11
  import { history } from '@client/services/router/request/history';
11
12
  import useContext from '@/client/context';
12
13
 
@@ -19,11 +20,10 @@ export type Props = {
19
20
  id?: string,
20
21
  refElem?: RefObject<HTMLElement>,
21
22
 
22
- icon?: TIcons | ComponentChild,
23
+ icon?: ComponentChild,
23
24
  iconR?: ComponentChild,
24
25
 
25
26
  prefix?: ComponentChild,
26
- children?: ComponentChild | ComponentChild[],
27
27
  suffix?: ComponentChild,
28
28
 
29
29
  tag?: "a" | "button",
@@ -31,7 +31,6 @@ export type Props = {
31
31
  shape?: 'default' | 'icon' | 'tile' | 'pill',
32
32
  size?: TComponentSize,
33
33
  class?: string,
34
- title?: string,
35
34
 
36
35
  state?: [string, React.StateUpdater<string>],
37
36
  active?: boolean,
@@ -45,16 +44,18 @@ export type Props = {
45
44
  submenu?: ComponentChild,
46
45
  nav?: boolean | 'exact'
47
46
 
48
- } & (TButtonProps | TLinkProps)
47
+ // SEO: if icon only, should provinde a hint (aria-label)
48
+ } & ({
49
+ hint: string,
50
+ children?: ComponentChild | ComponentChild[],
51
+ } | {
52
+ children: ComponentChild | ComponentChild[],
53
+ hint?: string,
54
+ }) & (TButtonProps | TLinkProps)
49
55
 
50
- export type TButtonProps = {
51
-
52
- }
56
+ export type TButtonProps = React.JSX.HTMLAttributes<HTMLButtonElement>
53
57
 
54
- export type TLinkProps = {
55
- link: string, // Link
56
- target?: string,
57
- }
58
+ export type TLinkProps = React.JSX.HTMLAttributes<HTMLAnchorElement>
58
59
 
59
60
  /*----------------------------------
60
61
  - HELPERS
@@ -84,6 +85,7 @@ export default ({
84
85
  iconR, suffix,
85
86
  submenu,
86
87
  nav,
88
+ hint,
87
89
 
88
90
  // Style
89
91
  class: className,
@@ -125,6 +127,12 @@ export default ({
125
127
  props.onClick = () => setActive(id);
126
128
  }
127
129
 
130
+ // Hint
131
+ if (hint !== undefined) {
132
+ props['aria-label'] = hint;
133
+ props.title = hint;
134
+ }
135
+
128
136
  // Shape classes
129
137
  const classNames: string[] = ['btn'];
130
138
  if (className)
@@ -171,35 +179,36 @@ export default ({
171
179
  // Render
172
180
  if ('link' in props || Tag === "a") {
173
181
 
174
- props.href = props.link;
175
-
176
- // External = open in new tab by default
177
- if (props.href && (props.href[0] !== '/' || props.href.startsWith('//')))
178
- props.target = '_blank';
182
+ // Link (only if enabled)
183
+ if (!disabled) {
179
184
 
180
- if (props.target === undefined) {
185
+ props.href = props.link;
186
+
187
+ // External = open in new tab by default
188
+ if (shouldOpenNewTab( props.href, props.target ))
189
+ props.target = '_blank';
190
+ }
181
191
 
182
- if (nav) {
192
+ // Nav
193
+ if (nav && props.target === undefined) {
183
194
 
184
- const checkIfCurrentUrl = (url: string) =>
185
- isCurrentUrl(url, props.link, nav === 'exact');
195
+ const checkIfCurrentUrl = (url: string) =>
196
+ isCurrentUrl(url, props.link, nav === 'exact');
186
197
 
187
- React.useEffect(() => {
198
+ React.useEffect(() => {
188
199
 
189
- // Init
190
- if (checkIfCurrentUrl(ctx.request.path))
191
- setIsActive(true);
200
+ // Init
201
+ if (checkIfCurrentUrl(ctx.request.path))
202
+ setIsActive(true);
192
203
 
193
- // On location change
194
- return history?.listen(({ location }) => {
204
+ // On location change
205
+ return history?.listen(({ location }) => {
195
206
 
196
- setIsActive( checkIfCurrentUrl(location.pathname) );
197
-
198
- })
207
+ setIsActive( checkIfCurrentUrl(location.pathname) );
199
208
 
200
- }, []);
201
- }
209
+ })
202
210
 
211
+ }, []);
203
212
  }
204
213
 
205
214
  Tag = 'a';