@autobusal/common 1.33.7 → 1.33.9
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/CHANGELOG.md +16 -0
- package/Table/Actions/Actions.tsx +13 -1
- package/Table/Actions/Get.tsx +13 -2
- package/Table/Header/Header.tsx +2 -2
- package/Table/Header/styles.ts +13 -2
- package/Table/Rows/Rows.tsx +3 -1
- package/Table/Table.tsx +22 -2
- package/Viewer/Actions.tsx +4 -2
- package/Viewer/Data.tsx +17 -8
- package/Viewer/Item.tsx +64 -23
- package/Viewer/Items/Dropdown.tsx +4 -2
- package/Viewer/Viewer.tsx +9 -1
- package/Viewer/styles.ts +8 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.33.9
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Every field built through `Viewer` was unnamed.** The label was a `<span>` beside an input with no id, so each one announced as a bare "edit text" and clicking a label did nothing - across every admin and account form in the app, not one screen. `Item` now mints a `useId`-prefixed id for single-control rows and the label is a real `<label htmlFor>`. Additive: no caller changes, and the styled rule set was empty either way.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `Table` and `Viewer` take an optional `confirm`, so a destructive action can ask a question specific to what it destroys rather than the generic one. Existing callers keep the generic question.
|
|
12
|
+
|
|
13
|
+
## 1.33.8
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`Table` headers can sort.** A header cell now takes an optional sort key and direction and renders as a control when it does, so a table backed by a paginated, server-sorted endpoint can drive it from the header rather than a separate control. Tables that pass no sort key are untouched and render exactly as before.
|
|
18
|
+
|
|
3
19
|
## 1.33.7
|
|
4
20
|
|
|
5
21
|
### Changed
|
|
@@ -10,11 +10,22 @@ interface Props {
|
|
|
10
10
|
urlWith?: string
|
|
11
11
|
available?: string[]
|
|
12
12
|
t: TFunction<'common'>
|
|
13
|
+
/*
|
|
14
|
+
* Edited: Claude - Date: 2026-08-20
|
|
15
|
+
* What to ASK before a row is deleted, replacing the generic "are you sure
|
|
16
|
+
* you want to delete this?". Optional, so every existing table keeps the
|
|
17
|
+
* question it has always asked - and worth having for the tables where the
|
|
18
|
+
* generic wording understates what is about to happen: deleting a saved
|
|
19
|
+
* passenger destroys a stored passport number outright, with no
|
|
20
|
+
* soft-delete behind it to undo from.
|
|
21
|
+
*/
|
|
22
|
+
confirm?: string
|
|
23
|
+
|
|
13
24
|
handlers?: any
|
|
14
25
|
navigate: NavigateFunction
|
|
15
26
|
}
|
|
16
27
|
|
|
17
|
-
const Actions = ({ id, url, urlWith, available, t, handlers, navigate }: Props): JSX.Element => {
|
|
28
|
+
const Actions = ({ id, url, urlWith, available, t, confirm, handlers, navigate }: Props): JSX.Element => {
|
|
18
29
|
const actions = available?.map((action, index) => (
|
|
19
30
|
<Get
|
|
20
31
|
key={ index }
|
|
@@ -23,6 +34,7 @@ const Actions = ({ id, url, urlWith, available, t, handlers, navigate }: Props):
|
|
|
23
34
|
urlWith={ urlWith }
|
|
24
35
|
type={ action }
|
|
25
36
|
t={ t }
|
|
37
|
+
confirm={ confirm }
|
|
26
38
|
handlers={ handlers }
|
|
27
39
|
navigate={ navigate }
|
|
28
40
|
/>
|
package/Table/Actions/Get.tsx
CHANGED
|
@@ -9,11 +9,22 @@ interface Props {
|
|
|
9
9
|
urlWith?: string
|
|
10
10
|
type: string
|
|
11
11
|
t: TFunction<'common'>
|
|
12
|
+
/*
|
|
13
|
+
* Edited: Claude - Date: 2026-08-20
|
|
14
|
+
* What to ASK before a row is deleted, replacing the generic "are you sure
|
|
15
|
+
* you want to delete this?". Optional, so every existing table keeps the
|
|
16
|
+
* question it has always asked - and worth having for the tables where the
|
|
17
|
+
* generic wording understates what is about to happen: deleting a saved
|
|
18
|
+
* passenger destroys a stored passport number outright, with no
|
|
19
|
+
* soft-delete behind it to undo from.
|
|
20
|
+
*/
|
|
21
|
+
confirm?: string
|
|
22
|
+
|
|
12
23
|
handlers?: any
|
|
13
24
|
navigate: NavigateFunction
|
|
14
25
|
}
|
|
15
26
|
|
|
16
|
-
const Get = ({ id, url, urlWith, type, t, handlers, navigate }: Props): (JSX.Element | null) => {
|
|
27
|
+
const Get = ({ id, url, urlWith, type, t, confirm, handlers, navigate }: Props): (JSX.Element | null) => {
|
|
17
28
|
// Edited: Ferjolt Ozuni - Date: 2026-08-02
|
|
18
29
|
// 'extra' belongs with 'create' and 'search': all three are TOP-of-table
|
|
19
30
|
// slots, not per-row actions. Without it here, any table that opts into
|
|
@@ -27,7 +38,7 @@ const Get = ({ id, url, urlWith, type, t, handlers, navigate }: Props): (JSX.Ele
|
|
|
27
38
|
|
|
28
39
|
const onAskDeletion = (): void => {
|
|
29
40
|
// ask for the deletion of the object
|
|
30
|
-
if (window.confirm(t('table.confirm', { ns: 'common' }))) {
|
|
41
|
+
if (window.confirm(confirm ?? t('table.confirm', { ns: 'common' }))) {
|
|
31
42
|
handlers.delete(id);
|
|
32
43
|
}
|
|
33
44
|
};
|
package/Table/Header/Header.tsx
CHANGED
|
@@ -17,7 +17,7 @@ const Header = ({ url, data, sorting, t, actions }: Props): JSX.Element => {
|
|
|
17
17
|
const params = getUrl();
|
|
18
18
|
|
|
19
19
|
const columns = data.map((item, index) => (
|
|
20
|
-
<Td key={ index } $width={ item.width }>
|
|
20
|
+
<Td key={ index } scope="col" $width={ item.width }>
|
|
21
21
|
<Column>
|
|
22
22
|
{ item.slug
|
|
23
23
|
? <Sort item={ item } url={ url } params={ params } sorting={ sorting } />
|
|
@@ -33,7 +33,7 @@ const Header = ({ url, data, sorting, t, actions }: Props): JSX.Element => {
|
|
|
33
33
|
{ columns }
|
|
34
34
|
|
|
35
35
|
{ rowActions(actions).length > 0 && (
|
|
36
|
-
<Td $width={ 10 }>
|
|
36
|
+
<Td scope="col" $width={ 10 }>
|
|
37
37
|
<Column>
|
|
38
38
|
<Text>{ t('table.options', { ns: 'common' }) }</Text>
|
|
39
39
|
</Column>
|
package/Table/Header/styles.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { Link } from 'react-router-dom';
|
|
|
4
4
|
export const Container = styled.thead`
|
|
5
5
|
background: ${ props => props.theme.background.neutral };
|
|
6
6
|
|
|
7
|
-
& > tr >
|
|
7
|
+
& > tr > th:first-of-type > div {
|
|
8
8
|
justify-content: left;
|
|
9
9
|
}
|
|
10
10
|
`;
|
|
@@ -13,7 +13,18 @@ interface TdData {
|
|
|
13
13
|
$width?: number
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Edited: Claude - Date: 2026-08-20
|
|
18
|
+
*
|
|
19
|
+
* A <th>, not a <td>. Every table on the platform announced its column
|
|
20
|
+
* headings as ordinary cells, so a screen reader reading down a data table
|
|
21
|
+
* had nothing to say which column a value belonged to - the one piece of
|
|
22
|
+
* structure a data table exists to carry. Purely a markup change: the
|
|
23
|
+
* heading's own look comes entirely from the Column div inside (its own
|
|
24
|
+
* font-weight, colour and flex centring), so th's user-agent bold and
|
|
25
|
+
* centred text change nothing visible.
|
|
26
|
+
*/
|
|
27
|
+
export const Td = styled.th<TdData>`
|
|
17
28
|
${ props => props.$width && css<TdData>`
|
|
18
29
|
width: ${ props => props.$width }%;
|
|
19
30
|
` }
|
package/Table/Rows/Rows.tsx
CHANGED
|
@@ -17,12 +17,13 @@ interface Props {
|
|
|
17
17
|
empty?: string
|
|
18
18
|
t: TFunction<'common'>
|
|
19
19
|
actions?: string[]
|
|
20
|
+
confirm?: string
|
|
20
21
|
handlers?: any
|
|
21
22
|
navigate: NavigateFunction
|
|
22
23
|
onReorder?: (draggedId: number, targetId: number) => void
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
const Rows = ({ url, urlWith, data, loading, fetching, colLength, empty, t, actions, handlers, navigate, onReorder }: Props): JSX.Element => {
|
|
26
|
+
const Rows = ({ url, urlWith, data, loading, fetching, colLength, empty, t, actions, confirm, handlers, navigate, onReorder }: Props): JSX.Element => {
|
|
26
27
|
const [ draggedId, setDraggedId ] = useState<number | null>(null);
|
|
27
28
|
if (loading) {
|
|
28
29
|
return <Loading length={ colLength } t={ t } />;
|
|
@@ -77,6 +78,7 @@ const Rows = ({ url, urlWith, data, loading, fetching, colLength, empty, t, acti
|
|
|
77
78
|
urlWith={ urlWith }
|
|
78
79
|
available={ available }
|
|
79
80
|
t={ t }
|
|
81
|
+
confirm={ confirm }
|
|
80
82
|
handlers={ handlers }
|
|
81
83
|
navigate={ navigate }
|
|
82
84
|
/>
|
package/Table/Table.tsx
CHANGED
|
@@ -19,6 +19,15 @@ interface Props {
|
|
|
19
19
|
loading: boolean
|
|
20
20
|
fetching: boolean
|
|
21
21
|
t: TFunction<'common'>
|
|
22
|
+
/*
|
|
23
|
+
* Edited: Claude - Date: 2026-08-20
|
|
24
|
+
* What this table IS, as one short phrase - it becomes the table's
|
|
25
|
+
* accessible name. A screen reader announcing "table, 6 columns, 15 rows"
|
|
26
|
+
* with no name is the same table on every screen; with one it is "Occupancy
|
|
27
|
+
* by departure". Optional, so every existing caller is unchanged (and an
|
|
28
|
+
* unnamed table is no worse off than it was).
|
|
29
|
+
*/
|
|
30
|
+
label?: string
|
|
22
31
|
actions?: string[]
|
|
23
32
|
extra?: JSX.Element
|
|
24
33
|
/*
|
|
@@ -32,6 +41,16 @@ interface Props {
|
|
|
32
41
|
* Optional: a table that does not pass one keeps the generic message.
|
|
33
42
|
*/
|
|
34
43
|
empty?: string
|
|
44
|
+
/*
|
|
45
|
+
* Edited: Claude - Date: 2026-08-20
|
|
46
|
+
* What to ASK before a row is deleted, replacing the generic "are you sure
|
|
47
|
+
* you want to delete this?". Optional, so every existing table keeps the
|
|
48
|
+
* question it has always asked - and worth having for the tables where the
|
|
49
|
+
* generic wording understates what is about to happen: deleting a saved
|
|
50
|
+
* passenger destroys a stored passport number outright, with no
|
|
51
|
+
* soft-delete behind it to undo from.
|
|
52
|
+
*/
|
|
53
|
+
confirm?: string
|
|
35
54
|
handlers?: any
|
|
36
55
|
/*
|
|
37
56
|
* Edited: Ferjolt Ozuni - Date: 2026-08-10
|
|
@@ -44,7 +63,7 @@ interface Props {
|
|
|
44
63
|
onReorder?: (draggedId: number, targetId: number) => void
|
|
45
64
|
}
|
|
46
65
|
|
|
47
|
-
const Table = ({ url, urlWith, columns, rows, pages, sorting, search, loading, fetching, t, actions, extra, empty, handlers, onReorder }: Props): JSX.Element => {
|
|
66
|
+
const Table = ({ url, urlWith, columns, rows, pages, sorting, search, loading, fetching, t, label, actions, extra, empty, confirm, handlers, onReorder }: Props): JSX.Element => {
|
|
48
67
|
const navigate = useNavigate();
|
|
49
68
|
|
|
50
69
|
const colLength = columns.length + 1;
|
|
@@ -63,7 +82,7 @@ const Table = ({ url, urlWith, columns, rows, pages, sorting, search, loading, f
|
|
|
63
82
|
|
|
64
83
|
<Content>
|
|
65
84
|
<Inner>
|
|
66
|
-
<ContainerTable cellPadding={ 0 } cellSpacing={ 0 }>
|
|
85
|
+
<ContainerTable cellPadding={ 0 } cellSpacing={ 0 } aria-label={ label }>
|
|
67
86
|
<Header
|
|
68
87
|
url={ url }
|
|
69
88
|
data={ columns }
|
|
@@ -82,6 +101,7 @@ const Table = ({ url, urlWith, columns, rows, pages, sorting, search, loading, f
|
|
|
82
101
|
empty={ empty }
|
|
83
102
|
t={ t }
|
|
84
103
|
actions={ actions }
|
|
104
|
+
confirm={ confirm }
|
|
85
105
|
handlers={ handlers }
|
|
86
106
|
navigate={ navigate }
|
|
87
107
|
onReorder={ onReorder }
|
package/Viewer/Actions.tsx
CHANGED
|
@@ -10,15 +10,17 @@ interface Props {
|
|
|
10
10
|
id: number
|
|
11
11
|
available: string[]
|
|
12
12
|
pending?: boolean
|
|
13
|
+
/* Edited: Claude - Date: 2026-08-20: see Viewer's own prop */
|
|
14
|
+
confirm?: string
|
|
13
15
|
t: TFunction<'common'>
|
|
14
16
|
onDelete?: () => void
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
const Actions = ({ id, available, pending, t, onDelete }: Props): JSX.Element => {
|
|
19
|
+
const Actions = ({ id, available, pending, confirm, t, onDelete }: Props): JSX.Element => {
|
|
18
20
|
const options: ActionData[] = [];
|
|
19
21
|
|
|
20
22
|
const onAskDelete = (): void => {
|
|
21
|
-
if (window.confirm(t('table.confirm', { ns: 'common' }))) {
|
|
23
|
+
if (window.confirm(confirm ?? t('table.confirm', { ns: 'common' }))) {
|
|
22
24
|
if (onDelete) {
|
|
23
25
|
onDelete();
|
|
24
26
|
}
|
package/Viewer/Data.tsx
CHANGED
|
@@ -14,13 +14,20 @@ import { ViewData } from './types';
|
|
|
14
14
|
|
|
15
15
|
interface Props {
|
|
16
16
|
id: number
|
|
17
|
+
/**
|
|
18
|
+
* Edited: Claude - Date: 2026-08-20
|
|
19
|
+
* The DOM id of this row's control, so the visible label above it can
|
|
20
|
+
* point at it (see Item.tsx). Undefined for the row types that are not a
|
|
21
|
+
* single control - those inputs are unchanged.
|
|
22
|
+
*/
|
|
23
|
+
field?: string
|
|
17
24
|
item: ViewData
|
|
18
25
|
refs: UseFormRegister<any>
|
|
19
26
|
t: TFunction<'common'>
|
|
20
27
|
onUpdate: UseFormSetValue<any>
|
|
21
28
|
}
|
|
22
29
|
|
|
23
|
-
const Data = ({ id, item, refs, t, onUpdate }: Props): (JSX.Element | null) => {
|
|
30
|
+
const Data = ({ id, field, item, refs, t, onUpdate }: Props): (JSX.Element | null) => {
|
|
24
31
|
// Edited: Ferjolt Ozuni - Date: 2026-08-08
|
|
25
32
|
// A 'component' row is not a registered form field - it never had a
|
|
26
33
|
// name to give, and the name-required guard below (meant to stop every
|
|
@@ -66,6 +73,7 @@ const Data = ({ id, item, refs, t, onUpdate }: Props): (JSX.Element | null) => {
|
|
|
66
73
|
return (
|
|
67
74
|
<input
|
|
68
75
|
type="checkbox"
|
|
76
|
+
id={ field }
|
|
69
77
|
value={ 1 }
|
|
70
78
|
defaultChecked={ Number(item.value) === 1 }
|
|
71
79
|
{ ...refs(item.name, {
|
|
@@ -98,6 +106,7 @@ const Data = ({ id, item, refs, t, onUpdate }: Props): (JSX.Element | null) => {
|
|
|
98
106
|
<Calendar
|
|
99
107
|
type={ item.type }
|
|
100
108
|
name={ item.name }
|
|
109
|
+
id={ field }
|
|
101
110
|
defaultValue={ value.string }
|
|
102
111
|
t={ t }
|
|
103
112
|
validation={ item.rules }
|
|
@@ -116,7 +125,7 @@ const Data = ({ id, item, refs, t, onUpdate }: Props): (JSX.Element | null) => {
|
|
|
116
125
|
return <DatePicker item={ item } t={ t } refs={ refs } onUpdate={ onUpdate } />;
|
|
117
126
|
|
|
118
127
|
case 'email':
|
|
119
|
-
return <input type="email" defaultValue={ value.string } { ...refs(item.name, Validate(String(item.rules), t)) } />;
|
|
128
|
+
return <input type="email" id={ field } defaultValue={ value.string } { ...refs(item.name, Validate(String(item.rules), t)) } />;
|
|
120
129
|
|
|
121
130
|
case 'file':
|
|
122
131
|
case 'files':
|
|
@@ -132,7 +141,7 @@ const Data = ({ id, item, refs, t, onUpdate }: Props): (JSX.Element | null) => {
|
|
|
132
141
|
);
|
|
133
142
|
|
|
134
143
|
case 'float':
|
|
135
|
-
return <input type="number" defaultValue={ value.number } { ...refs(item.name, Validate(String(item.rules), t)) } step="0.01" />;
|
|
144
|
+
return <input type="number" id={ field } defaultValue={ value.number } { ...refs(item.name, Validate(String(item.rules), t)) } step="0.01" />;
|
|
136
145
|
|
|
137
146
|
case 'link':
|
|
138
147
|
// The function-value variant only ever arrives on a 'component' row -
|
|
@@ -140,19 +149,19 @@ const Data = ({ id, item, refs, t, onUpdate }: Props): (JSX.Element | null) => {
|
|
|
140
149
|
return <Linked id={ id } value={ item.value as (string | number | JSX.Element | undefined) } url={ item.url } />;
|
|
141
150
|
|
|
142
151
|
case 'number':
|
|
143
|
-
return <input type="number" defaultValue={ value.number } { ...refs(item.name, Validate(String(item.rules), t)) } />
|
|
152
|
+
return <input type="number" id={ field } defaultValue={ value.number } { ...refs(item.name, Validate(String(item.rules), t)) } />
|
|
144
153
|
|
|
145
154
|
case 'password':
|
|
146
|
-
return <Password name={ item.name } validation={ item.rules } defaultValue={ value.string } t={ t } refs={ refs } />;
|
|
155
|
+
return <Password name={ item.name } id={ field } validation={ item.rules } defaultValue={ value.string } t={ t } refs={ refs } />;
|
|
147
156
|
|
|
148
157
|
case 'select':
|
|
149
|
-
return <Dropdown item={ item } refs={ refs } t={ t } />;
|
|
158
|
+
return <Dropdown item={ item } field={ field } refs={ refs } t={ t } />;
|
|
150
159
|
|
|
151
160
|
case 'text':
|
|
152
|
-
return <input type="text" defaultValue={ value.string } { ...refs(item.name, Validate(String(item.rules), t)) } />;
|
|
161
|
+
return <input type="text" id={ field } defaultValue={ value.string } { ...refs(item.name, Validate(String(item.rules), t)) } />;
|
|
153
162
|
|
|
154
163
|
case 'textarea':
|
|
155
|
-
return <textarea defaultValue={ value.string } { ...refs(item.name, Validate(String(item.rules), t)) }></textarea>;
|
|
164
|
+
return <textarea id={ field } defaultValue={ value.string } { ...refs(item.name, Validate(String(item.rules), t)) }></textarea>;
|
|
156
165
|
|
|
157
166
|
case 'textarea-html':
|
|
158
167
|
return (
|
package/Viewer/Item.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useId } from 'react';
|
|
1
2
|
import { FieldErrors, UseFormRegister, UseFormSetValue } from 'react-hook-form';
|
|
2
3
|
import { Display } from '@autobusal/utilities';
|
|
3
4
|
import Data from './Data';
|
|
@@ -14,8 +15,46 @@ interface Props {
|
|
|
14
15
|
onUpdate: UseFormSetValue<any>
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* The row types that ARE exactly one control, and can therefore be pointed
|
|
20
|
+
* at by a label.
|
|
21
|
+
*
|
|
22
|
+
* Edited: Claude - Date: 2026-08-20
|
|
23
|
+
*
|
|
24
|
+
* Everything else in this switch is either not a control at all ('display',
|
|
25
|
+
* 'link', 'component') or is several of them ('checkbox-list', 'files') -
|
|
26
|
+
* and `htmlFor` naming one arbitrary member of a group is worse than no
|
|
27
|
+
* association, because it reads as a promise that the group has a name.
|
|
28
|
+
* Those rows keep the label they had; this list is the set that gains one.
|
|
29
|
+
*/
|
|
30
|
+
const SINGLE = [ 'text', 'email', 'password', 'number', 'float', 'textarea', 'select', 'date', 'dob', 'picker', 'checkbox' ];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Edited: Claude - Date: 2026-08-20
|
|
34
|
+
*
|
|
35
|
+
* EVERY FIELD IN THIS FORM WAS UNNAMED. The label was a <span> beside an
|
|
36
|
+
* <input> with no id, so the two were adjacent and nothing more: a screen
|
|
37
|
+
* reader announced the whole of any admin or account form as a run of
|
|
38
|
+
* anonymous "edit text", and clicking a label did nothing. Fixed here, once,
|
|
39
|
+
* rather than in each of the dozens of screens that build their fields
|
|
40
|
+
* through this component.
|
|
41
|
+
*
|
|
42
|
+
* The id comes from useId and is therefore page-unique. The field NAME would
|
|
43
|
+
* have been the obvious thing to use and is the wrong thing: names belong to
|
|
44
|
+
* the form payload and repeat across forms ('email' is a field on three
|
|
45
|
+
* screens), while ids are page-global - two forms on one page would emit
|
|
46
|
+
* duplicate ids, which is its own defect. This is the same rule
|
|
47
|
+
* @autobusal/routes-order's passenger block and @autobusal/auth follow.
|
|
48
|
+
*/
|
|
49
|
+
const Item = ({ id, data, refs, t, errors, onUpdate }: Props): JSX.Element => {
|
|
50
|
+
const uid = useId();
|
|
51
|
+
|
|
52
|
+
const field = data.type !== undefined && SINGLE.includes(data.type) && data.name !== undefined
|
|
53
|
+
? `${ uid }${ data.name }`
|
|
54
|
+
: undefined;
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className={ `row row-${ data.type } ${ data.label === undefined && 'spacer' }` }>
|
|
19
58
|
{/*
|
|
20
59
|
* Edited: Ferjolt Ozuni - Date: 2026-08-07
|
|
21
60
|
*
|
|
@@ -31,26 +70,28 @@ const Item = ({ id, data, refs, t, errors, onUpdate }: Props): JSX.Element => (
|
|
|
31
70
|
* spacer, and an empty element there would take a flex slot and open a
|
|
32
71
|
* gap where the divider should be.
|
|
33
72
|
*/}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
73
|
+
{ data.label !== undefined && (
|
|
74
|
+
<Label htmlFor={ field }>
|
|
75
|
+
{ data.label }
|
|
76
|
+
|
|
77
|
+
{ data.rules?.includes('required') && <Required aria-hidden="true">*</Required> }
|
|
78
|
+
</Label>
|
|
79
|
+
) }
|
|
80
|
+
|
|
81
|
+
<Data
|
|
82
|
+
id={ id }
|
|
83
|
+
field={ field }
|
|
84
|
+
item={ data }
|
|
85
|
+
refs={ refs }
|
|
86
|
+
t={ t }
|
|
87
|
+
onUpdate={ onUpdate }
|
|
88
|
+
/>
|
|
89
|
+
|
|
90
|
+
{ data.name && Display(errors[data.name]) }
|
|
91
|
+
|
|
92
|
+
{ data.notice && <Notice>* { data.notice }</Notice> }
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
55
96
|
|
|
56
97
|
export default Item;
|
|
@@ -5,11 +5,13 @@ import { ViewData } from '../types';
|
|
|
5
5
|
|
|
6
6
|
interface Props {
|
|
7
7
|
item: ViewData
|
|
8
|
+
/* Edited: Claude - Date: 2026-08-20: the id the row's label points at */
|
|
9
|
+
field?: string
|
|
8
10
|
refs: UseFormRegister<any>
|
|
9
11
|
t: TFunction<'common'>
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
const Dropdown = ({ item, refs, t }: Props): (JSX.Element | null) => {
|
|
14
|
+
const Dropdown = ({ item, field, refs, t }: Props): (JSX.Element | null) => {
|
|
13
15
|
if (item.name === undefined) {
|
|
14
16
|
return null;
|
|
15
17
|
}
|
|
@@ -46,7 +48,7 @@ const Dropdown = ({ item, refs, t }: Props): (JSX.Element | null) => {
|
|
|
46
48
|
* want.
|
|
47
49
|
*/
|
|
48
50
|
return (
|
|
49
|
-
<select defaultValue={ item?.value ?? '' } { ...refs(item.name, rules) }>
|
|
51
|
+
<select id={ field } defaultValue={ item?.value ?? '' } { ...refs(item.name, rules) }>
|
|
50
52
|
{ item.placeholder !== undefined && (
|
|
51
53
|
<option value="">{ item.placeholder }</option>
|
|
52
54
|
) }
|
package/Viewer/Viewer.tsx
CHANGED
|
@@ -13,6 +13,13 @@ interface Props {
|
|
|
13
13
|
fetching?: boolean
|
|
14
14
|
pending?: boolean
|
|
15
15
|
actions?: string[]
|
|
16
|
+
/*
|
|
17
|
+
* Edited: Claude - Date: 2026-08-20
|
|
18
|
+
* What to ASK before the delete button acts, replacing the generic
|
|
19
|
+
* question. Same prop, same reason, as Table's - a form that destroys an
|
|
20
|
+
* identity document should say so.
|
|
21
|
+
*/
|
|
22
|
+
confirm?: string
|
|
16
23
|
t: TFunction<'common'>
|
|
17
24
|
onSave?: (data: any) => void
|
|
18
25
|
onDelete?: (data?: any) => void
|
|
@@ -23,7 +30,7 @@ interface Props {
|
|
|
23
30
|
onTest?: (data: any) => void
|
|
24
31
|
}
|
|
25
32
|
|
|
26
|
-
const Viewer = ({ id, type, data, fetching, pending, actions, t, onSave, onDelete, onTest }: Props): JSX.Element => {
|
|
33
|
+
const Viewer = ({ id, type, data, fetching, pending, actions, confirm, t, onSave, onDelete, onTest }: Props): JSX.Element => {
|
|
27
34
|
const { register, handleSubmit, formState: { errors }, setValue } = useForm<any>();
|
|
28
35
|
|
|
29
36
|
if (fetching) {
|
|
@@ -81,6 +88,7 @@ const Viewer = ({ id, type, data, fetching, pending, actions, t, onSave, onDelet
|
|
|
81
88
|
id={ id }
|
|
82
89
|
available={ actions }
|
|
83
90
|
pending={ pending }
|
|
91
|
+
confirm={ confirm }
|
|
84
92
|
t={ t }
|
|
85
93
|
onDelete={ onDelete }
|
|
86
94
|
/>
|
package/Viewer/styles.ts
CHANGED
|
@@ -108,7 +108,14 @@ export const Notice = styled.div`
|
|
|
108
108
|
* used - the requirement is already carried to assistive tech by the
|
|
109
109
|
* field's own validation, and a lone "*" read aloud says nothing.
|
|
110
110
|
*/
|
|
111
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Edited: Claude - Date: 2026-08-20
|
|
113
|
+
* A real <label>, not a span. It carries `htmlFor` now (see Item.tsx), and
|
|
114
|
+
* only a label element makes that an association rather than an attribute
|
|
115
|
+
* the browser ignores. Visually identical - both are inline elements, and
|
|
116
|
+
* this rule set was and remains empty.
|
|
117
|
+
*/
|
|
118
|
+
export const Label = styled.label``;
|
|
112
119
|
|
|
113
120
|
export const Required = styled.span`
|
|
114
121
|
margin-left: 3px;
|