box-annotations 5.2.1-beta.1 → 5.2.1-beta.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 (38) hide show
  1. package/dist/annotations.css +1 -1
  2. package/dist/annotations.js +1 -1
  3. package/i18n/en-US.js +2 -0
  4. package/i18n/en-US.properties +4 -0
  5. package/i18n/json/src/components/BoundingBoxHighlight/messages.json +1 -0
  6. package/package.json +1 -1
  7. package/src/@types/events.ts +4 -0
  8. package/src/boundingBoxHighlight/BoundingBoxHighlightContainer.tsx +33 -0
  9. package/src/boundingBoxHighlight/BoundingBoxHighlightManager.tsx +19 -0
  10. package/src/boundingBoxHighlight/index.ts +1 -0
  11. package/src/common/BaseAnnotator.ts +78 -0
  12. package/src/components/BoundingBoxHighlight/BoundingBoxHighlightList.scss +8 -0
  13. package/src/components/BoundingBoxHighlight/BoundingBoxHighlightList.tsx +67 -0
  14. package/src/components/BoundingBoxHighlight/BoundingBoxHighlightNav.scss +73 -0
  15. package/src/components/BoundingBoxHighlight/BoundingBoxHighlightNav.tsx +49 -0
  16. package/src/components/BoundingBoxHighlight/BoundingBoxHighlightRect.scss +22 -0
  17. package/src/components/BoundingBoxHighlight/BoundingBoxHighlightRect.tsx +77 -0
  18. package/src/components/BoundingBoxHighlight/index.ts +4 -0
  19. package/src/components/BoundingBoxHighlight/messages.ts +14 -0
  20. package/src/document/DocumentAnnotator.ts +69 -4
  21. package/src/icons/ChevronLeft.tsx +24 -0
  22. package/src/icons/ChevronRight.tsx +24 -0
  23. package/src/store/annotations/reducer.ts +6 -1
  24. package/src/store/boundingBoxHighlights/actions.ts +6 -0
  25. package/src/store/boundingBoxHighlights/index.ts +4 -0
  26. package/src/store/boundingBoxHighlights/reducer.ts +33 -0
  27. package/src/store/boundingBoxHighlights/selectors.ts +12 -0
  28. package/src/store/boundingBoxHighlights/types.ts +13 -0
  29. package/src/store/createRootReducer.ts +2 -0
  30. package/src/store/eventing/boundingBoxHighlightNav.ts +13 -0
  31. package/src/store/eventing/middleware.ts +3 -0
  32. package/src/store/index.ts +1 -0
  33. package/src/store/options/actions.ts +2 -0
  34. package/src/store/options/reducer.ts +6 -1
  35. package/src/store/options/selectors.ts +2 -0
  36. package/src/store/options/types.ts +4 -0
  37. package/src/store/types.ts +2 -0
  38. package/src/utils/scroll.ts +8 -3
package/i18n/en-US.js CHANGED
@@ -4,6 +4,8 @@ export default {
4
4
  "ba.annotationsLoadError": "We’re sorry, the annotations failed to load for this file.",
5
5
  "ba.annotationsPost": "Post",
6
6
  "ba.annotationsSave": "Save",
7
+ "ba.boundingBoxHighlight.viewNextReference": "View next reference",
8
+ "ba.boundingBoxHighlight.viewPrevReference": "View previous reference",
7
9
  "ba.popup.reply.comment": "Comment",
8
10
  "ba.popup.reply.field": "Type a comment",
9
11
  "ba.popups.cancel": "Cancel",
@@ -8,6 +8,10 @@ ba.annotationsLoadError = We’re sorry, the annotations failed to load for this
8
8
  ba.annotationsPost = Post
9
9
  # Label for the save button
10
10
  ba.annotationsSave = Save
11
+ # Aria label for the button to view the next reference
12
+ ba.boundingBoxHighlight.viewNextReference = View next reference
13
+ # Aria label for the button to view the previous reference
14
+ ba.boundingBoxHighlight.viewPrevReference = View previous reference
11
15
  # Aria label description for reply popup
12
16
  ba.popup.reply.comment = Comment
13
17
  # Aria label description for annotation comment field
@@ -0,0 +1 @@
1
+ [{"id":"ba.boundingBoxHighlight.viewNextReference","description":"Aria label for the button to view the next reference","defaultMessage":"View next reference"},{"id":"ba.boundingBoxHighlight.viewPrevReference","description":"Aria label for the button to view the previous reference","defaultMessage":"View previous reference"}]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "box-annotations",
3
- "version": "5.2.1-beta.1",
3
+ "version": "5.2.1-beta.4",
4
4
  "description": "Box Annotations",
5
5
  "author": "Box (https://www.box.com/)",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -9,7 +9,11 @@ enum Event {
9
9
  ANNOTATIONS_INITIALIZED = 'annotations_initialized',
10
10
  ANNOTATIONS_MODE_CHANGE = 'annotations_mode_change',
11
11
  COLOR_SET = 'annotations_color_set',
12
+ BOUNDING_BOX_HIGHLIGHTS_SET = 'bounding_box_highlights_set',
13
+ BOUNDING_BOX_HIGHLIGHT_NAVIGATE = 'bounding_box_highlight_navigate',
14
+ BOUNDING_BOX_HIGHLIGHT_SELECT = 'bounding_box_highlight_select',
12
15
  VISIBLE_SET = 'annotations_visible_set',
16
+ VIEW_MODE_SET = 'view_mode_set',
13
17
  }
14
18
 
15
19
  // Existing legacy events, don't rename
@@ -0,0 +1,33 @@
1
+ import { connect } from 'react-redux';
2
+ import { BoundingBoxHighlightList } from '../components/BoundingBoxHighlight';
3
+ import withProviders from '../common/withProviders';
4
+ import {
5
+ AppState,
6
+ BoundingBox,
7
+ getBoundingBoxHighlights,
8
+ getBoundingBoxHighlightsForPage,
9
+ getSelectedBoundingBoxHighlightId,
10
+ navigateBoundingBoxHighlightAction,
11
+ setSelectedBoundingBoxHighlightAction,
12
+ } from '../store';
13
+
14
+ export type Props = {
15
+ location: number;
16
+ };
17
+
18
+ const mapStateToProps = (state: AppState, { location }: Props): {
19
+ allBoundingBoxes: BoundingBox[];
20
+ boundingBoxes: BoundingBox[];
21
+ selectedId: string | null;
22
+ } => ({
23
+ allBoundingBoxes: getBoundingBoxHighlights(state),
24
+ boundingBoxes: getBoundingBoxHighlightsForPage(state, location),
25
+ selectedId: getSelectedBoundingBoxHighlightId(state),
26
+ });
27
+
28
+ const mapDispatchToProps = {
29
+ onNavigate: navigateBoundingBoxHighlightAction,
30
+ onSelect: setSelectedBoundingBoxHighlightAction,
31
+ };
32
+
33
+ export default connect(mapStateToProps, mapDispatchToProps)(withProviders(BoundingBoxHighlightList));
@@ -0,0 +1,19 @@
1
+ import * as React from 'react';
2
+ import * as ReactDOM from 'react-dom/client';
3
+ import BaseManager, { Props } from '../common/BaseManager';
4
+ import BoundingBoxHighlightContainer from './BoundingBoxHighlightContainer';
5
+
6
+ export default class BoundingBoxHighlightManager extends BaseManager {
7
+ decorate(): void {
8
+ this.reactEl.classList.add('ba-Layer--boundingBoxHighlight');
9
+ this.reactEl.dataset.testid = 'ba-Layer--boundingBoxHighlight';
10
+ }
11
+
12
+ render(props: Props): void {
13
+ if (!this.root) {
14
+ this.root = ReactDOM.createRoot(this.reactEl);
15
+ }
16
+
17
+ this.root.render(<BoundingBoxHighlightContainer location={this.location} {...props} />);
18
+ }
19
+ }
@@ -0,0 +1 @@
1
+ export { default as BoundingBoxHighlightManager } from './BoundingBoxHighlightManager';
@@ -7,6 +7,8 @@ import EventEmitter from './EventEmitter';
7
7
  import i18n from '../utils/i18n';
8
8
  import messages from '../messages';
9
9
  import { Event, IntlOptions, LegacyEvent, Permissions } from '../@types';
10
+ import { BoundingBox } from '../store/boundingBoxHighlights/types';
11
+ import { ViewMode } from '../store/options/types';
10
12
  import { Features } from '../BoxAnnotations';
11
13
  import './BaseAnnotator.scss';
12
14
 
@@ -37,6 +39,8 @@ export type Options = {
37
39
  hasTouch?: boolean;
38
40
  initialColor?: string;
39
41
  initialMode?: store.Mode;
42
+ /** View mode: annotations and bounding boxes are mutually exclusive. Defaults to 'annotations'. */
43
+ initialViewMode?: ViewMode;
40
44
  intl: IntlOptions;
41
45
  locale?: string;
42
46
  token: string;
@@ -72,6 +76,7 @@ export default class BaseAnnotator extends EventEmitter {
72
76
  fileOptions,
73
77
  initialColor,
74
78
  initialMode,
79
+ initialViewMode = 'annotations',
75
80
  intl,
76
81
  token,
77
82
  }: Options) {
@@ -93,6 +98,7 @@ export default class BaseAnnotator extends EventEmitter {
93
98
  fileVersionId: fileOptionsVersionId ?? fileVersionId,
94
99
  isCurrentFileVersion: !fileOptionsVersionId || fileOptionsVersionId === currentFileVersionId,
95
100
  permissions: file.permissions,
101
+ viewMode: initialViewMode,
96
102
  },
97
103
  };
98
104
 
@@ -109,6 +115,10 @@ export default class BaseAnnotator extends EventEmitter {
109
115
  this.addListener(Event.ANNOTATION_REMOVE, this.handleRemove);
110
116
  this.addListener(Event.COLOR_SET, this.handleSetColor);
111
117
  this.addListener(Event.VISIBLE_SET, this.handleSetVisible);
118
+ this.addListener(Event.BOUNDING_BOX_HIGHLIGHTS_SET, this.handleSetBoundingBoxHighlights);
119
+ this.addListener(Event.BOUNDING_BOX_HIGHLIGHT_NAVIGATE, this.handleNavigateBoundingBoxHighlight);
120
+ this.addListener(Event.BOUNDING_BOX_HIGHLIGHT_SELECT, this.handleSelectBoundingBoxHighlight);
121
+ this.addListener(Event.VIEW_MODE_SET, this.handleSetViewMode);
112
122
 
113
123
  // Load any required data at startup
114
124
  this.hydrate();
@@ -134,6 +144,10 @@ export default class BaseAnnotator extends EventEmitter {
134
144
  this.removeListener(Event.ANNOTATION_REMOVE, this.handleRemove);
135
145
  this.removeListener(Event.COLOR_SET, this.handleSetColor);
136
146
  this.removeListener(Event.VISIBLE_SET, this.handleSetVisible);
147
+ this.removeListener(Event.BOUNDING_BOX_HIGHLIGHTS_SET, this.handleSetBoundingBoxHighlights);
148
+ this.removeListener(Event.BOUNDING_BOX_HIGHLIGHT_NAVIGATE, this.handleNavigateBoundingBoxHighlight);
149
+ this.removeListener(Event.BOUNDING_BOX_HIGHLIGHT_SELECT, this.handleSelectBoundingBoxHighlight);
150
+ this.removeListener(Event.VIEW_MODE_SET, this.handleSetViewMode);
137
151
  }
138
152
 
139
153
  public init(scale = 1, rotation = 0): void {
@@ -171,6 +185,54 @@ export default class BaseAnnotator extends EventEmitter {
171
185
  // Called by box-content-preview
172
186
  }
173
187
 
188
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
189
+ public scrollToBoundingBoxHighlight(highlightId: string | null): void {
190
+ // Implemented in DocumentAnnotator
191
+ }
192
+
193
+ /**
194
+ * Sets the bounding box highlights to display. This replaces all existing bounding boxes.
195
+ * Triggers a re-render.
196
+ *
197
+ * Note: Bounding boxes are only visible when viewMode is set to 'boundingBoxes'.
198
+ *
199
+ * @param {BoundingBox[]} boundingBoxes - Array of bounding box definitions with coordinates and page numbers
200
+ */
201
+ public setBoundingBoxHighlights(boundingBoxes: BoundingBox[]): void {
202
+ this.store.dispatch(store.setBoundingBoxHighlightsAction(boundingBoxes));
203
+ this.render();
204
+ }
205
+
206
+ /**
207
+ * Sets the view mode for the annotator. Annotations and bounding boxes are mutually exclusive.
208
+ * Triggers a re-render.
209
+ *
210
+ * Side effects:
211
+ * - Switching to 'boundingBoxes': Clears active annotation ID
212
+ * - Switching to 'annotations': Clears selected bounding box ID
213
+ *
214
+ * @param {ViewMode} viewMode - Either 'annotations' (default) or 'boundingBoxes'
215
+ */
216
+ public setViewMode(viewMode: ViewMode): void {
217
+ this.store.dispatch(store.setViewModeAction(viewMode));
218
+ this.render();
219
+ }
220
+
221
+ /**
222
+ * Selects a bounding box highlight and scrolls to it. Pass null to deselect.
223
+ *
224
+ * Note: For programmatic navigation (prev/next), use the navigateBoundingBoxHighlightAction
225
+ * which emits BOUNDING_BOX_HIGHLIGHT_NAVIGATE events.
226
+ *
227
+ * @param {string | null} highlightId - The ID of the bounding box to select, or null to clear selection
228
+ */
229
+ public selectBoundingBoxHighlight(highlightId: string | null): void {
230
+ this.store.dispatch(store.setSelectedBoundingBoxHighlightAction(highlightId));
231
+ if (highlightId) {
232
+ this.scrollToBoundingBoxHighlight(highlightId);
233
+ }
234
+ }
235
+
174
236
  public setActiveId(annotationId: string | null): void {
175
237
  this.store.dispatch(store.setActiveAnnotationIdAction(annotationId));
176
238
  }
@@ -223,6 +285,22 @@ export default class BaseAnnotator extends EventEmitter {
223
285
  this.setColor(color);
224
286
  };
225
287
 
288
+ protected handleSetBoundingBoxHighlights = (boundingBoxes: BoundingBox[]): void => {
289
+ this.setBoundingBoxHighlights(boundingBoxes);
290
+ };
291
+
292
+ protected handleNavigateBoundingBoxHighlight = (highlightId: string): void => {
293
+ this.scrollToBoundingBoxHighlight(highlightId);
294
+ };
295
+
296
+ protected handleSelectBoundingBoxHighlight = (highlightId: string | null): void => {
297
+ this.selectBoundingBoxHighlight(highlightId);
298
+ };
299
+
300
+ protected handleSetViewMode = ({ viewMode }: { viewMode: ViewMode }): void => {
301
+ this.setViewMode(viewMode);
302
+ };
303
+
226
304
  protected hydrate(): void {
227
305
  // Redux dispatch method signature doesn't seem to like async actions
228
306
  this.store.dispatch<any>(store.fetchAnnotationsAction()); // eslint-disable-line @typescript-eslint/no-explicit-any
@@ -0,0 +1,8 @@
1
+ .ba-BoundingBoxHighlightList {
2
+ position: absolute;
3
+ top: 0;
4
+ left: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+ pointer-events: none;
8
+ }
@@ -0,0 +1,67 @@
1
+ import * as React from 'react';
2
+ import BoundingBoxHighlightRect from './BoundingBoxHighlightRect';
3
+ import { BoundingBox } from '../../store/boundingBoxHighlights/types';
4
+ import './BoundingBoxHighlightList.scss';
5
+
6
+ type Props = {
7
+ /**
8
+ * All bounding box highlights in the document.
9
+ */
10
+ allBoundingBoxes: BoundingBox[];
11
+ /**
12
+ * Bounding boxes on the current page.
13
+ */
14
+ boundingBoxes: BoundingBox[];
15
+ /**
16
+ * Callback invoked to navigate to the previous or next bounding box highlight.
17
+ */
18
+ onNavigate?: (id: string) => void;
19
+ /**
20
+ * Callback invoked when the user clicks on a bounding box highlight.
21
+ */
22
+ onSelect?: (id: string) => void;
23
+ /**
24
+ * The ID of the selected bounding box highlight.
25
+ */
26
+ selectedId: string | null;
27
+ };
28
+
29
+ const BoundingBoxHighlightList = ({
30
+ allBoundingBoxes,
31
+ boundingBoxes,
32
+ onNavigate,
33
+ onSelect,
34
+ selectedId,
35
+ }: Props): React.ReactElement | null => {
36
+ const total = allBoundingBoxes.length;
37
+
38
+ if (total === 0) {
39
+ return null;
40
+ }
41
+
42
+ const selectedIndex = allBoundingBoxes.findIndex(h => h.id === selectedId);
43
+ const prevId = selectedIndex > 0 ? allBoundingBoxes[selectedIndex - 1].id : undefined;
44
+ const nextId = selectedIndex < total - 1 ? allBoundingBoxes[selectedIndex + 1].id : undefined;
45
+
46
+ return (
47
+ <div className="ba-BoundingBoxHighlightList" data-testid="ba-BoundingBoxHighlightList">
48
+ {boundingBoxes.map(bbox => {
49
+ return (
50
+ <BoundingBoxHighlightRect
51
+ key={bbox.id}
52
+ boundingBox={bbox}
53
+ currentIndex={selectedIndex}
54
+ isSelected={selectedId === bbox.id}
55
+ nextId={nextId}
56
+ onNavigate={onNavigate}
57
+ onSelect={onSelect}
58
+ prevId={prevId}
59
+ total={total}
60
+ />
61
+ );
62
+ })}
63
+ </div>
64
+ );
65
+ };
66
+
67
+ export default React.memo(BoundingBoxHighlightList);
@@ -0,0 +1,73 @@
1
+ $ba-BoundingBoxHighlightNav-bg: #fff;
2
+ $ba-BoundingBoxHighlightNav-shadow: 0 6px 20px rgba(0, 0, 0, .1);
3
+ $ba-BoundingBoxHighlightNav-arrow: #909090;
4
+ $ba-BoundingBoxHighlightNav-border: #e8e8e8;
5
+ $ba-BoundingBoxHighlightNav-counter: #222;
6
+
7
+ .ba-BoundingBoxHighlightNav {
8
+ position: absolute;
9
+ bottom: 100%;
10
+ left: 50%;
11
+ z-index: 1;
12
+ display: flex;
13
+ gap: 4px;
14
+ align-items: center;
15
+ margin-bottom: 6px;
16
+ padding: 3px;
17
+ white-space: nowrap;
18
+ background: $ba-BoundingBoxHighlightNav-bg;
19
+ border: 1px solid $ba-BoundingBoxHighlightNav-border;
20
+ border-radius: 20px;
21
+ box-shadow: $ba-BoundingBoxHighlightNav-shadow;
22
+ transform: translateX(-50%);
23
+ cursor: default;
24
+ pointer-events: auto;
25
+ }
26
+
27
+ .ba-BoundingBoxHighlightNav-btn {
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ width: 32px;
32
+ height: 32px;
33
+ padding: 0;
34
+ line-height: 1;
35
+ background: transparent;
36
+ border: none;
37
+ border-radius: 50%;
38
+ cursor: pointer;
39
+ transition: background-color 150ms ease;
40
+
41
+ svg {
42
+ path {
43
+ fill: $ba-BoundingBoxHighlightNav-arrow;
44
+ }
45
+ }
46
+
47
+ &:hover:not(:disabled) {
48
+ background: rgba(0, 0, 0, .06);
49
+ }
50
+
51
+ &:active:not(:disabled) {
52
+ background: rgba(0, 0, 0, .12);
53
+ }
54
+
55
+ &:disabled {
56
+ cursor: default;
57
+
58
+ svg {
59
+ path {
60
+ fill: $ba-BoundingBoxHighlightNav-border;
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ .ba-BoundingBoxHighlightNav-counter {
67
+ padding: 0 4px;
68
+ color: $ba-BoundingBoxHighlightNav-counter;
69
+ font-weight: normal;
70
+ font-size: 15px;
71
+ font-family: Lato, Arial, sans-serif;
72
+ user-select: none;
73
+ }
@@ -0,0 +1,49 @@
1
+ import * as React from 'react';
2
+ import { useIntl } from 'react-intl';
3
+ import ChevronLeft from '../../icons/ChevronLeft';
4
+ import ChevronRight from '../../icons/ChevronRight';
5
+ import messages from './messages';
6
+ import './BoundingBoxHighlightNav.scss';
7
+
8
+ type Props = {
9
+ currentIndex: number;
10
+ total: number;
11
+ onPrev: (event: React.MouseEvent) => void;
12
+ onNext: (event: React.MouseEvent) => void;
13
+ };
14
+
15
+ const BoundingBoxHighlightNav = ({ currentIndex, total, onPrev, onNext }: Props): JSX.Element => {
16
+ const intl = useIntl();
17
+ const isPrevDisabled = currentIndex === 0;
18
+ const isNextDisabled = currentIndex === total - 1;
19
+
20
+ return (
21
+ <div className="ba-BoundingBoxHighlightNav" data-testid="ba-BoundingBoxHighlightNav">
22
+ <button
23
+ aria-label={intl.formatMessage(messages.ariaLabelViewPrevReference)}
24
+ className="ba-BoundingBoxHighlightNav-btn"
25
+ data-testid="ba-BoundingBoxHighlightNav-prev"
26
+ disabled={isPrevDisabled}
27
+ onClick={onPrev}
28
+ type="button"
29
+ >
30
+ <ChevronLeft width={20} height={20} />
31
+ </button>
32
+ <span className="ba-BoundingBoxHighlightNav-counter">
33
+ {currentIndex + 1} / {total}
34
+ </span>
35
+ <button
36
+ aria-label={intl.formatMessage(messages.ariaLabelViewNextReference)}
37
+ className="ba-BoundingBoxHighlightNav-btn"
38
+ data-testid="ba-BoundingBoxHighlightNav-next"
39
+ disabled={isNextDisabled}
40
+ onClick={onNext}
41
+ type="button"
42
+ >
43
+ <ChevronRight width={20} height={20} />
44
+ </button>
45
+ </div>
46
+ );
47
+ };
48
+
49
+ export default BoundingBoxHighlightNav;
@@ -0,0 +1,22 @@
1
+ $ba-BoundingBoxHighlight-color: #0061d5;
2
+ $ba-BoundingBoxHighlight-bgColor--default: rgba($ba-BoundingBoxHighlight-color, .05);
3
+ $ba-BoundingBoxHighlight-bgColor--hover: rgba($ba-BoundingBoxHighlight-color, .16);
4
+ $ba-BoundingBoxHighlight-bgColor--selected: rgba($ba-BoundingBoxHighlight-color, .1);
5
+
6
+ .ba-BoundingBoxHighlightRect {
7
+ position: absolute;
8
+ box-sizing: border-box;
9
+ background-color: $ba-BoundingBoxHighlight-bgColor--default;
10
+ border: 2px solid rgba($ba-BoundingBoxHighlight-color, .2);
11
+ border-radius: 8px;
12
+ outline: none;
13
+ cursor: pointer;
14
+ transition: background-color 200ms ease, border-color 200ms ease, box-shadow 200ms ease;
15
+ pointer-events: auto;
16
+
17
+ &.is-selected {
18
+ background-color: $ba-BoundingBoxHighlight-bgColor--selected;
19
+ border-color: $ba-BoundingBoxHighlight-color;
20
+ box-shadow: 0 0 0 2px rgba($ba-BoundingBoxHighlight-color, .3), 0 4px 12px rgba(0, 0, 0, .15);
21
+ }
22
+ }
@@ -0,0 +1,77 @@
1
+ import * as React from 'react';
2
+ import classNames from 'classnames';
3
+ import BoundingBoxHighlightNav from './BoundingBoxHighlightNav';
4
+ import { BoundingBox } from '../../store/boundingBoxHighlights/types';
5
+ import './BoundingBoxHighlightRect.scss';
6
+
7
+ type Props = {
8
+ boundingBox: BoundingBox;
9
+ currentIndex: number;
10
+ isSelected?: boolean;
11
+ onNavigate?: (id: string) => void;
12
+ onSelect?: (id: string) => void;
13
+ total: number;
14
+ prevId?: string;
15
+ nextId?: string;
16
+ };
17
+
18
+ const BoundingBoxHighlightRect = ({
19
+ boundingBox,
20
+ currentIndex,
21
+ isSelected,
22
+ onNavigate,
23
+ onSelect,
24
+ total,
25
+ prevId,
26
+ nextId,
27
+ }: Props): JSX.Element => {
28
+ const { id, x, y, width, height } = boundingBox;
29
+
30
+ const style: React.CSSProperties = {
31
+ display: 'block',
32
+ left: `${x}%`,
33
+ top: `${y}%`,
34
+ width: `${width}%`,
35
+ height: `${height}%`,
36
+ };
37
+
38
+ const handleClick = (event: React.MouseEvent): void => {
39
+ event.stopPropagation();
40
+ onSelect?.(id);
41
+ };
42
+
43
+ const handlePrev = (event: React.MouseEvent): void => {
44
+ event.stopPropagation();
45
+ if (prevId) {
46
+ onNavigate?.(prevId);
47
+ }
48
+ };
49
+
50
+ const handleNext = (event: React.MouseEvent): void => {
51
+ event.stopPropagation();
52
+ if (nextId) {
53
+ onNavigate?.(nextId);
54
+ }
55
+ };
56
+
57
+ return (
58
+ <div
59
+ className={classNames('ba-BoundingBoxHighlightRect', { 'is-selected': isSelected })}
60
+ data-testid={`ba-BoundingBoxHighlightRect-${id}`}
61
+ onClick={handleClick}
62
+ role="presentation"
63
+ style={style}
64
+ >
65
+ {isSelected && total > 1 && (
66
+ <BoundingBoxHighlightNav
67
+ currentIndex={currentIndex}
68
+ onNext={handleNext}
69
+ onPrev={handlePrev}
70
+ total={total}
71
+ />
72
+ )}
73
+ </div>
74
+ );
75
+ };
76
+
77
+ export default BoundingBoxHighlightRect;
@@ -0,0 +1,4 @@
1
+ export { default as BoundingBoxHighlightList } from './BoundingBoxHighlightList';
2
+ export { default as BoundingBoxHighlightNav } from './BoundingBoxHighlightNav';
3
+ export { default as BoundingBoxHighlightRect } from './BoundingBoxHighlightRect';
4
+ export type { BoundingBox } from '../../store/boundingBoxHighlights/types';
@@ -0,0 +1,14 @@
1
+ import { defineMessages } from 'react-intl';
2
+
3
+ export default defineMessages({
4
+ ariaLabelViewNextReference: {
5
+ id: 'ba.boundingBoxHighlight.viewNextReference',
6
+ description: 'Aria label for the button to view the next reference',
7
+ defaultMessage: 'View next reference',
8
+ },
9
+ ariaLabelViewPrevReference: {
10
+ id: 'ba.boundingBoxHighlight.viewPrevReference',
11
+ description: 'Aria label for the button to view the previous reference',
12
+ defaultMessage: 'View previous reference',
13
+ },
14
+ });