Skip to content

Commit 738cf98

Browse files
authored
Merge pull request #29 from eBay/update-touch-target
Update: Touch target
2 parents b2c6156 + 0c02de3 commit 738cf98

File tree

7 files changed

+149
-62
lines changed

7 files changed

+149
-62
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ an accessibility annotation Figma plugin
1111

1212
## Intro
1313

14-
<img alt="plugin version 9" src="previews/v10/include_banner.png" />
14+
<img alt="plugin version 11" src="previews/v11/include_banner.png" />
1515

1616
The eBay Include accessibility annotation Figma plugin is a tool to make annotating for accessibility (a11y) easier — easier for designers to spec and easier for developers to understand what is required.
1717

previews/v11/include_banner.png

104 KB
Loading

src/data/touch-target-types.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,26 @@ import SvgTouchTargets from '../icons/touch-target';
66
// setup touch target options
77
export default {
88
min24: {
9-
icon: <SvgTouchTargets.SvgMin48 />,
9+
icon: <SvgTouchTargets.SvgMin48 size={24} />,
1010
id: '1',
1111
value: 'min24',
12-
label: 'min 24px'
12+
label: '24px<br />(WCAG min)'
13+
},
14+
min44: {
15+
icon: <SvgTouchTargets.SvgMin48 size={54} />,
16+
id: '2',
17+
value: 'min44',
18+
label: '44px<br />(iOS min)'
19+
},
20+
min48: {
21+
icon: <SvgTouchTargets.SvgMin48 size={60} />,
22+
id: '3',
23+
value: 'min48',
24+
label: '48px<br />(Android min)'
1325
},
1426
custom: {
1527
icon: <SvgTouchTargets.SvgCustom />,
16-
id: '2',
28+
id: '4',
1729
value: 'custom',
1830
label: 'custom'
1931
}

src/figma-code/steps/reading-order.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ export const addArrow = (msg) => {
4747
const arrowsLength = readingOrderFrame.children.length;
4848
const arrowNumber = arrowsLength + 1;
4949
const isFirst = arrowNumber === 1;
50-
const arrowName = isFirst ? 'Start Arrow' : `Arrow ${arrowNumber}`;
50+
const arrowName = isFirst
51+
? `Start Arrow | ${arrowType}`
52+
: `Arrow ${arrowNumber} | ${arrowType}`;
5153

5254
let xStart = 50;
5355
let yStart = 50;
@@ -57,10 +59,16 @@ export const addArrow = (msg) => {
5759
const lastChild = readingOrderFrame.children[arrowsLength - 1];
5860
const { absoluteBoundingBox } = lastChild;
5961

62+
// if | is found, get the prevType
63+
const hasPipe = lastChild.name.includes('|');
64+
const prevType = hasPipe
65+
? lastChild.name.split('|')[1].trim()
66+
: 'downRight';
67+
6068
let xDiff = 0;
6169
let yDiff = 0;
6270

63-
switch (arrowType) {
71+
switch (prevType) {
6472
case 'downLeft':
6573
xDiff = figmaLayer.arrowSize;
6674
break;

src/figma-code/steps/touch-target.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,10 @@ export const add = (msg) => {
9595
// set selection of new touch target layer
9696
figma.currentPage.selection = [targetNode];
9797

98+
const labelFormatted = label.replace(/<br \/>/g, ' ');
99+
98100
// let the user know rectangle has been added
99-
figma.notify(`${label} overlay added successfully!`, {
101+
figma.notify(`${labelFormatted} overlay added successfully!`, {
100102
timeout: config.notifyTime
101103
});
102104

@@ -132,6 +134,14 @@ export const checkTouchTargets = (msg) => {
132134
return overlapX && overlapY;
133135
};
134136

137+
const tooSmall = (nodeId) => {
138+
const node = figma.getNodeById(nodeId);
139+
140+
const isTooSmall = node.width < 24 || node.height < 24;
141+
142+
return isTooSmall;
143+
};
144+
135145
const checkOverlap = (nodes) => {
136146
const overlappingNodes = [];
137147

@@ -155,13 +165,31 @@ export const checkTouchTargets = (msg) => {
155165
return overlappingNodes;
156166
};
157167

168+
const checkMinSize = (nodes) => {
169+
const tooSmallNodes = [];
170+
171+
// check if any nodes are too small
172+
for (let i = 0; i < nodes.length; i += 1) {
173+
const node = nodes[i];
174+
175+
// check if nodes overlap
176+
if (tooSmall(node)) {
177+
tooSmallNodes.push(node);
178+
}
179+
}
180+
181+
return tooSmallNodes;
182+
};
183+
158184
const overlaps = checkOverlap(Object.keys(touchTargets));
185+
const tooSmallNodes = checkMinSize(Object.keys(touchTargets));
159186

160187
// send message response back to plugin frontend (ui.js)
161188
figma.ui.postMessage({
162189
type: 'touch-targets-checked',
163190
data: {
164-
overlapsFound: overlaps
191+
overlapsFound: overlaps,
192+
tooSmallFound: tooSmallNodes
165193
}
166194
});
167195
};

src/icons/touch-target/SvgMin48.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
11
import * as React from 'react';
2+
import PropTypes from 'prop-types';
23

3-
function SvgMin48() {
4+
function SvgMin48({ size }) {
45
return (
5-
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" fill="none">
6+
<svg
7+
xmlns="http://www.w3.org/2000/svg"
8+
width={size}
9+
height={size}
10+
viewBox="0 0 60 60"
11+
fill="none"
12+
>
613
<circle cx="30.25" cy="30" r="12" fill="#000" />
714
</svg>
815
);
916
}
1017

18+
SvgMin48.defaultProps = {
19+
size: 60
20+
};
21+
22+
SvgMin48.propTypes = {
23+
size: PropTypes.number
24+
};
25+
1126
export default React.memo(SvgMin48);

src/pages/TouchTarget.js

Lines changed: 76 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { utils } from '../constants';
55
import {
66
Alert,
77
AnnotationStepPage,
8+
BannerSuccess,
89
EmptyStepSelection,
910
HeadingStep
1011
} from '../components';
@@ -61,7 +62,7 @@ function TouchTarget() {
6162

6263
// local state
6364
const [checkedOverlap, setCheckedOverlap] = React.useState(false);
64-
const [overlaps, setOverlaps] = React.useState([]);
65+
const [errors, setErrorsFound] = React.useState({});
6566
const [noTargets, setNoTargets] = React.useState(defaultNoTargets);
6667

6768
const onEmptySelected = () => {
@@ -133,13 +134,25 @@ function TouchTarget() {
133134

134135
// only listen for this response type on this step
135136
if (type === 'touch-targets-checked') {
136-
const { overlapsFound } = data;
137+
const { overlapsFound, tooSmallFound } = data;
137138

138-
setOverlaps(overlapsFound);
139+
const newErrors = {};
140+
overlapsFound.forEach((id) => {
141+
newErrors[id] = { id, type: 'overlap' };
142+
});
143+
144+
tooSmallFound.forEach((id) => {
145+
newErrors[id] = { id, type: 'too-small' };
146+
});
147+
148+
setErrorsFound(newErrors);
139149

140150
// no issues found
141-
if (overlapsFound.length === 0) {
151+
if (Object.keys(newErrors).length === 0) {
142152
setCheckedOverlap(true);
153+
} else {
154+
// scroll to bottom
155+
utils.scrollToBottomOfAnnotationStep();
143156
}
144157
}
145158
};
@@ -154,7 +167,7 @@ function TouchTarget() {
154167
};
155168
}, []);
156169

157-
const checkText = overlaps.length > 0 ? 'Re-check' : 'Check';
170+
const checkText = Object.keys(errors).length > 0 ? 'Re-check' : 'Check';
158171

159172
return (
160173
<AnnotationStepPage
@@ -180,12 +193,7 @@ function TouchTarget() {
180193
<React.Fragment>
181194
<HeadingStep
182195
number={1}
183-
text="Check if there are any small elements that need to have the touch target marked up (e.g. an icon without background)."
184-
/>
185-
186-
<HeadingStep
187-
number={2}
188-
text="Add any additional annotation for elements to be regarded as one area (e.g. an image + CTA tile)."
196+
text="Identify areas that need touch target annotations."
189197
/>
190198

191199
{!targetsAreSet && (
@@ -197,7 +205,51 @@ function TouchTarget() {
197205
/>
198206
)}
199207

200-
{overlaps.length > 0 && (
208+
{noTargets === false && (
209+
<div className="button-group" role="radiogroup">
210+
{targetTypesArray.map((type) => {
211+
const { label, icon } = touchTargetsTypes[type];
212+
213+
const onClick = () => {
214+
onAddTouchTarget(type);
215+
};
216+
217+
return (
218+
<div key={label} className="container-selection-button">
219+
<div
220+
className="selection-button"
221+
onClick={onClick}
222+
onKeyDown={(e) => {
223+
if (utils.isEnterKey(e.key)) onClick();
224+
}}
225+
role="button"
226+
tabIndex={0}
227+
>
228+
<div>{icon}</div>
229+
</div>
230+
231+
<div
232+
className="selection-button-label"
233+
dangerouslySetInnerHTML={{ __html: label }}
234+
/>
235+
</div>
236+
);
237+
})}
238+
</div>
239+
)}
240+
241+
{targetsArray.length > 1 && (
242+
<React.Fragment>
243+
<div className="spacer1" />
244+
<HeadingStep number={2} text="Check the touch points" />
245+
</React.Fragment>
246+
)}
247+
248+
{checkedOverlap === true && Object.keys(errors).length === 0 && (
249+
<BannerSuccess text="All touch points meet accessibility criteria" />
250+
)}
251+
252+
{Object.keys(errors).length > 0 && (
201253
<React.Fragment>
202254
<Alert
203255
icon={<SvgWarning />}
@@ -210,15 +262,21 @@ function TouchTarget() {
210262

211263
<HeadingStep number={3} text="Fix the issues" />
212264

213-
{targetsArray.map((key) => {
214-
const { id, type } = touchTargets[key];
215-
const { label: labelType } = touchTargetsTypes[type];
265+
{targetsArray.map((key, index) => {
266+
const { id } = touchTargets[key];
267+
const num = index + 1;
216268

217269
// only show issues
218-
if (overlaps.includes(key) === false) {
270+
if (Object.keys(errors).includes(key) === false) {
219271
return null;
220272
}
221273

274+
const { type } = errors[key];
275+
276+
const Icon =
277+
type === 'overlap'
278+
? SvgTouchTarget.SvgOverlap
279+
: SvgTouchTarget.SvgResize;
222280
const onClick = () => zoomTo([key], true);
223281

224282
return (
@@ -232,8 +290,8 @@ function TouchTarget() {
232290
role="button"
233291
tabIndex="0"
234292
>
235-
<SvgTouchTarget.SvgOverlap />
236-
<div className="touch-target-type">{`Touch target (${labelType})`}</div>
293+
<Icon />
294+
<div className="ml1">{`${num} Touch target`}</div>
237295
</div>
238296

239297
<div
@@ -257,40 +315,6 @@ function TouchTarget() {
257315
<div className="divider" />
258316
</React.Fragment>
259317
)}
260-
261-
{noTargets === false && (
262-
<React.Fragment>
263-
<div className="spacer2" />
264-
265-
<div className="button-group" role="radiogroup">
266-
{targetTypesArray.map((type) => {
267-
const { label, icon } = touchTargetsTypes[type];
268-
269-
const onClick = () => {
270-
onAddTouchTarget(type);
271-
};
272-
273-
return (
274-
<div key={label} className="container-selection-button">
275-
<div
276-
className="selection-button"
277-
onClick={onClick}
278-
onKeyDown={(e) => {
279-
if (utils.isEnterKey(e.key)) onClick();
280-
}}
281-
role="button"
282-
tabIndex={0}
283-
>
284-
<div>{icon}</div>
285-
</div>
286-
287-
<div className="selection-button-label">{label}</div>
288-
</div>
289-
);
290-
})}
291-
</div>
292-
</React.Fragment>
293-
)}
294318
</React.Fragment>
295319
</AnnotationStepPage>
296320
);

0 commit comments

Comments
 (0)