@autobusal/common 1.4.0 → 1.4.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.
- package/Avatar.tsx +1 -1
- package/CHANGELOG.md +48 -0
- package/Calendar/Browse/styles.ts +7 -1
- package/Calendar/Days.tsx +10 -0
- package/Calendar/styles.ts +5 -0
- package/ChangeLanguage/ChangeLanguage.tsx +1 -0
- package/CompanyItem/CompanyItem.tsx +2 -2
- package/CookieNotification/CookieNotification.tsx +2 -2
- package/Gender.tsx +2 -2
- package/Password/Password.tsx +11 -5
- package/RouteItem/RouteItem.tsx +1 -1
- package/Seats/ChooseSeat.tsx +2 -2
- package/Table/Top/Search.tsx +25 -4
- package/Viewer/Data.tsx +1 -1
- package/package.json +2 -1
package/Avatar.tsx
CHANGED
|
@@ -8,7 +8,7 @@ interface Props {
|
|
|
8
8
|
|
|
9
9
|
const Avatar = ({ display }: Props): JSX.Element => (
|
|
10
10
|
<Container>
|
|
11
|
-
{ display?.logo_url !== '' && <img src={ display?.logo_url } />}
|
|
11
|
+
{ display?.logo_url !== '' && <img src={ display?.logo_url } alt={ display?.company } />}
|
|
12
12
|
{ display?.company }
|
|
13
13
|
</Container>
|
|
14
14
|
);
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@autobusal/common` are documented here. This project follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/) and [Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [1.4.2] - 2026-07-19
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **Cross-field validation + masked pre-fill for `Password` (`Password/Password.tsx`).** Added
|
|
11
|
+
an optional `getValues` prop (react-hook-form's `UseFormGetValues`) that is threaded into
|
|
12
|
+
`Validate(...)`, enabling cross-field rules like `matches:password` (e.g. a
|
|
13
|
+
password-confirmation field). Added an optional `defaultValue` prop applied to the input so
|
|
14
|
+
an admin edit form can render a saved secret as masked dots instead of blank, so editing a
|
|
15
|
+
different field no longer wipes the stored password on save.
|
|
16
|
+
- **Keyboard accessibility for the calendar day picker (`Calendar/Days.tsx`,
|
|
17
|
+
`Calendar/styles.ts`).** Day cells now expose `role="button"`, `tabIndex` (`-1` when
|
|
18
|
+
disabled), `aria-disabled`, `aria-pressed`, and an `onKeyDown` handler that activates on
|
|
19
|
+
Enter/Space, plus a `:focus-visible` outline. Days are now selectable via keyboard, not
|
|
20
|
+
only mouse.
|
|
21
|
+
- **Debounced type-to-filter search (`Table/Top/Search.tsx`).** The table search input now
|
|
22
|
+
submits automatically ~500ms after the user stops typing (via a `useEffect`/`setTimeout`
|
|
23
|
+
with an initial-render guard using `useRef`), instead of requiring an Enter press that
|
|
24
|
+
nothing on screen advertised. Explicit Enter submit is preserved.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **`Password` pre-fill wired through the dynamic viewer (`Viewer/Data.tsx`).** The `password`
|
|
29
|
+
field case now passes `defaultValue={ value.string }` to `Password`, so saved password
|
|
30
|
+
values hydrate as masked input in generated forms.
|
|
31
|
+
- **Confirm-password persistence of language selection (`ChangeLanguage/ChangeLanguage.tsx`).**
|
|
32
|
+
Language changes are now persisted to `localStorage['language']` so the chosen language
|
|
33
|
+
survives reloads rather than resetting to the default.
|
|
34
|
+
- **Explicit `common` namespace on i18n lookups (`Gender.tsx`, `Seats/ChooseSeat.tsx`).**
|
|
35
|
+
Translation calls now pass `{ ns: 'common' }` (gender male/female labels; seat
|
|
36
|
+
chosen/choose labels) so the correct namespace resolves and the raw key is no longer shown
|
|
37
|
+
when the component is used outside the default namespace context.
|
|
38
|
+
- **Styled Calendar browse dropdown (`Calendar/Browse/styles.ts`).** The select now has a
|
|
39
|
+
visible border, border-radius, padding, pointer cursor, and a custom chevron background,
|
|
40
|
+
replacing `border: none` so it reads as an interactive control.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- **Missing image alt text (`Avatar.tsx`, `CompanyItem/CompanyItem.tsx`, `RouteItem/RouteItem.tsx`).**
|
|
45
|
+
Operator/company logo `<img>` tags now include `alt={ ...company }`, giving screen readers a
|
|
46
|
+
meaningful label and a fallback when the logo fails to load.
|
|
47
|
+
|
|
48
|
+
Authored by Ferjolt Ozuni. Consolidated from the magus and alvavel whitelabel patch sets into canonical @autobusal source (eliminates per-repo patch-package divergence).
|
|
@@ -30,5 +30,11 @@ export const Select = styled.select<{$type?: 'year'}>`
|
|
|
30
30
|
`}
|
|
31
31
|
text-align: center;
|
|
32
32
|
background-color: ${ props => props.theme.background.neutral };
|
|
33
|
-
border:
|
|
33
|
+
border: 1px solid ${ props => props.theme.inputs.border };
|
|
34
|
+
border-radius: ${ props => props.theme.borderRadius };
|
|
35
|
+
padding: 0 22px 0 8px;
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23888' stroke-width='1.5'/%3E%3C/svg%3E");
|
|
38
|
+
background-repeat: no-repeat;
|
|
39
|
+
background-position: right 6px center;
|
|
34
40
|
`;
|
package/Calendar/Days.tsx
CHANGED
|
@@ -81,6 +81,16 @@ const Days = ({ type, value, selected, onSelected }: Props): JSX.Element => {
|
|
|
81
81
|
$disabled={ disabled }
|
|
82
82
|
$selected={ selected === currentValue }
|
|
83
83
|
onClick={ () => onSelected(currentValue, disabled) }
|
|
84
|
+
role="button"
|
|
85
|
+
tabIndex={ disabled ? -1 : 0 }
|
|
86
|
+
aria-disabled={ disabled }
|
|
87
|
+
aria-pressed={ selected === currentValue }
|
|
88
|
+
onKeyDown={ event => {
|
|
89
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
onSelected(currentValue, disabled);
|
|
92
|
+
}
|
|
93
|
+
} }
|
|
84
94
|
>
|
|
85
95
|
<span>{ currentDay }</span>
|
|
86
96
|
</Day>
|
package/Calendar/styles.ts
CHANGED
|
@@ -16,12 +16,12 @@ const CompanyItem = ({ item, type }: Props): JSX.Element => {
|
|
|
16
16
|
{ type === 'operator'
|
|
17
17
|
? (
|
|
18
18
|
<LinkLogo to={ item.link }>
|
|
19
|
-
<img src={ item.logo_url } />
|
|
19
|
+
<img src={ item.logo_url } alt={ item.company } />
|
|
20
20
|
</LinkLogo>
|
|
21
21
|
)
|
|
22
22
|
: (
|
|
23
23
|
<ContainerLogo>
|
|
24
|
-
<img src={ item.logo_url } />
|
|
24
|
+
<img src={ item.logo_url } alt={ item.company } />
|
|
25
25
|
</ContainerLogo>
|
|
26
26
|
) }
|
|
27
27
|
|
|
@@ -8,7 +8,7 @@ interface Props {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const CookieNotification = ({ t }: Props): (JSX.Element | null) => {
|
|
11
|
-
const status = document.cookie.indexOf('
|
|
11
|
+
const status = document.cookie.indexOf('OBTCookieNotification=viewed') == -1;
|
|
12
12
|
|
|
13
13
|
const [ show, setShow ] = useState<boolean>(status);
|
|
14
14
|
|
|
@@ -18,7 +18,7 @@ const CookieNotification = ({ t }: Props): (JSX.Element | null) => {
|
|
|
18
18
|
const CookieDate = new Date();
|
|
19
19
|
CookieDate.setFullYear(CookieDate.getFullYear() + 1);
|
|
20
20
|
|
|
21
|
-
document.cookie = 'OBTCookieNotification=viewed; expires=' + CookieDate.toUTCString() + '; path
|
|
21
|
+
document.cookie = 'OBTCookieNotification=viewed; expires=' + CookieDate.toUTCString() + '; path=/';
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
if (! show) {
|
package/Gender.tsx
CHANGED
|
@@ -19,7 +19,7 @@ const Gender = ({ name, defaultValue, t, refs }: Props) => (
|
|
|
19
19
|
{ ...refs(name) }
|
|
20
20
|
/>
|
|
21
21
|
|
|
22
|
-
{ t('data.gender.male') }
|
|
22
|
+
{ t('data.gender.male', { ns: 'common' }) }
|
|
23
23
|
</label>
|
|
24
24
|
|
|
25
25
|
<label>
|
|
@@ -30,7 +30,7 @@ const Gender = ({ name, defaultValue, t, refs }: Props) => (
|
|
|
30
30
|
{ ...refs(name) }
|
|
31
31
|
/>
|
|
32
32
|
|
|
33
|
-
{ t('data.gender.female') }
|
|
33
|
+
{ t('data.gender.female', { ns: 'common' }) }
|
|
34
34
|
</label>
|
|
35
35
|
</div>
|
|
36
36
|
</div>
|
package/Password/Password.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
import { TFunction } from 'i18next';
|
|
3
|
-
import { UseFormRegister } from 'react-hook-form';
|
|
3
|
+
import { UseFormRegister, UseFormGetValues } from 'react-hook-form';
|
|
4
4
|
import { AiFillEye, AiFillEyeInvisible } from 'react-icons/ai';
|
|
5
5
|
import { Validate } from '@autobusal/utilities';
|
|
6
6
|
import { Container } from './styles';
|
|
@@ -9,20 +9,26 @@ interface Props {
|
|
|
9
9
|
name: string
|
|
10
10
|
tabIndex?: number
|
|
11
11
|
validation?: string
|
|
12
|
+
// Pre-fill value (masked). Lets an admin form show a saved secret as dots
|
|
13
|
+
// instead of blank, so editing another field doesn't wipe it on save.
|
|
14
|
+
defaultValue?: string
|
|
12
15
|
t: TFunction<'common'>
|
|
13
16
|
refs: UseFormRegister<any>
|
|
17
|
+
// Pass react-hook-form's getValues to enable cross-field rules like
|
|
18
|
+
// `matches:password` (e.g. a password-confirmation field).
|
|
19
|
+
getValues?: UseFormGetValues<any>
|
|
14
20
|
}
|
|
15
21
|
|
|
16
|
-
const Password = ({ name, tabIndex, validation, t, refs }: Props): JSX.Element => {
|
|
22
|
+
const Password = ({ name, tabIndex, validation, defaultValue, t, refs, getValues }: Props): JSX.Element => {
|
|
17
23
|
const [ show, setShow ] = useState<boolean>(false);
|
|
18
24
|
|
|
19
25
|
const toggle = (): void => {
|
|
20
26
|
setShow(!show)
|
|
21
27
|
};
|
|
22
|
-
|
|
28
|
+
|
|
23
29
|
return (
|
|
24
30
|
<Container>
|
|
25
|
-
<input type={ show ? 'text' : 'password' } tabIndex={ tabIndex } { ...refs(name, Validate(validation ?? '', t)) } />
|
|
31
|
+
<input type={ show ? 'text' : 'password' } tabIndex={ tabIndex } defaultValue={ defaultValue } { ...refs(name, Validate(validation ?? '', t, getValues)) } />
|
|
26
32
|
|
|
27
33
|
{ show
|
|
28
34
|
? <AiFillEye onClick={ toggle } />
|
|
@@ -32,4 +38,4 @@ const Password = ({ name, tabIndex, validation, t, refs }: Props): JSX.Element =
|
|
|
32
38
|
);
|
|
33
39
|
};
|
|
34
40
|
|
|
35
|
-
export default Password;
|
|
41
|
+
export default Password;
|
package/RouteItem/RouteItem.tsx
CHANGED
|
@@ -21,7 +21,7 @@ const RouteItem = ({ type, route, t }: Props): JSX.Element => {
|
|
|
21
21
|
<About>
|
|
22
22
|
<Image>
|
|
23
23
|
<Logo to={ route.operator.link }>
|
|
24
|
-
<img src={ route.operator.logo_url } />
|
|
24
|
+
<img src={ route.operator.logo_url } alt={ route.operator.company } />
|
|
25
25
|
</Logo>
|
|
26
26
|
</Image>
|
|
27
27
|
|
package/Seats/ChooseSeat.tsx
CHANGED
|
@@ -45,8 +45,8 @@ const ChooseSeat = ({ name, type, defaultValue, bus, picked, validation, t, refs
|
|
|
45
45
|
<div>
|
|
46
46
|
<ButtonSeat type="button" $selected={ selected > 0 } onClick={ () => setShow(true) }>
|
|
47
47
|
{ selected > 0
|
|
48
|
-
? <><FaCheck /> { t('seats.chosen', { number: selected }) }</>
|
|
49
|
-
: <><MdEventSeat /> { t('seats.choose') }</> }
|
|
48
|
+
? <><FaCheck /> { t('seats.chosen', { ns: 'common', number: selected }) }</>
|
|
49
|
+
: <><MdEventSeat /> { t('seats.choose', { ns: 'common' }) }</> }
|
|
50
50
|
</ButtonSeat>
|
|
51
51
|
|
|
52
52
|
{ (show && bus.bus !== undefined) && (
|
package/Table/Top/Search.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FormEvent, useState } from 'react';
|
|
1
|
+
import { FormEvent, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { NavigateFunction } from 'react-router-dom';
|
|
3
3
|
import { TFunction } from 'i18next';
|
|
4
4
|
import { parseUrl } from '../browseUrl';
|
|
@@ -13,12 +13,33 @@ interface Props {
|
|
|
13
13
|
const Search = ({ value, url, t, navigate }: Props): JSX.Element => {
|
|
14
14
|
const [ q, setQ ] = useState<string>(String(value ?? ''));
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
event.preventDefault();
|
|
16
|
+
const initial = useRef<boolean>(true);
|
|
18
17
|
|
|
18
|
+
const submit = (): void => {
|
|
19
19
|
const search = parseUrl({ page: 1, q });
|
|
20
20
|
|
|
21
21
|
navigate(`${ url }${ search }`);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// typing filters after a short pause - submitting was previously only
|
|
25
|
+
// possible via Enter, which nothing on screen suggested
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (initial.current) {
|
|
28
|
+
initial.current = false;
|
|
29
|
+
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const timer = setTimeout(submit, 500);
|
|
34
|
+
|
|
35
|
+
return () => clearTimeout(timer);
|
|
36
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
37
|
+
}, [q]);
|
|
38
|
+
|
|
39
|
+
const handleSubmit = (event: FormEvent<HTMLFormElement>): void => {
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
|
|
42
|
+
submit();
|
|
22
43
|
}
|
|
23
44
|
|
|
24
45
|
return (
|
|
@@ -33,4 +54,4 @@ const Search = ({ value, url, t, navigate }: Props): JSX.Element => {
|
|
|
33
54
|
);
|
|
34
55
|
};
|
|
35
56
|
|
|
36
|
-
export default Search;
|
|
57
|
+
export default Search;
|
package/Viewer/Data.tsx
CHANGED
|
@@ -97,7 +97,7 @@ const Data = ({ id, item, refs, t, onUpdate }: Props): (JSX.Element | null) => {
|
|
|
97
97
|
return <input type="number" defaultValue={ value.number } { ...refs(item.name, Validate(String(item.rules), t)) } />
|
|
98
98
|
|
|
99
99
|
case 'password':
|
|
100
|
-
return <Password name={ item.name } validation={ item.rules } t={ t } refs={ refs } />;
|
|
100
|
+
return <Password name={ item.name } validation={ item.rules } defaultValue={ value.string } t={ t } refs={ refs } />;
|
|
101
101
|
|
|
102
102
|
case 'select':
|
|
103
103
|
return <Dropdown item={ item } refs={ refs } t={ t } />;
|