├── .env-example
├── .eslintrc
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── ----feature-request.md
│ ├── ---bug-report-.md
│ └── -question.md
└── pull_request_template.md
├── .gitignore
├── .npmrc
├── .nvmrc
├── .stylelintignore
├── .stylelintrc
├── DOCS.md
├── LICENSE
├── README.md
├── babel.config.js
├── contributing.md
├── custom-rules
└── hex-lowercase.js
├── jsdoc.json
├── manifest.json
├── package-lock.json
├── package.json
├── previews
├── import-manifest.png
├── include_logo.png
├── v10
│ └── include_banner.png
├── v11
│ └── include_banner.png
├── v12
│ └── include_banner.png
├── v14
│ └── include_banner.png
├── v15
│ └── include_banner.png
├── v16
│ └── include_banner.png
├── v8
│ └── include_banner.png
└── v9
│ └── include_banner.png
├── src
├── code.js
├── components
│ ├── Alert
│ │ ├── index.js
│ │ └── styles.scss
│ ├── AltTextRow
│ │ ├── index.js
│ │ └── styles.scss
│ ├── AnnotationStepPage.js
│ ├── BannerAlert
│ │ ├── index.js
│ │ └── styles.scss
│ ├── BannerSuccess.js
│ ├── BannerTip.js
│ ├── BannerTipText
│ │ ├── index.js
│ │ └── styles.scss
│ ├── Checkbox
│ │ ├── index.js
│ │ └── styles.scss
│ ├── ColorBlindnessFilter.js
│ ├── ContrastScreenshot
│ │ ├── index.js
│ │ └── styles.scss
│ ├── Dropdown
│ │ ├── index.js
│ │ └── styles.scss
│ ├── EmptyStepSelection.js
│ ├── ErrorBoundary.js
│ ├── Footer.js
│ ├── FooterActionButton.js
│ ├── HeadingStep
│ │ ├── index.js
│ │ └── styles.scss
│ ├── LoadingSpinner
│ │ ├── index.js
│ │ └── styles.scss
│ ├── NavLeft
│ │ ├── index.js
│ │ └── styles.scss
│ ├── ProgressLine.js
│ ├── ProgressPieChart
│ │ ├── index.js
│ │ └── styles.scss
│ ├── Toggle
│ │ ├── index.js
│ │ └── styles.scss
│ └── index.js
├── constants
│ ├── analytics.js
│ ├── colors.js
│ ├── contrast.js
│ ├── figma-layer.js
│ ├── index.js
│ └── utilities.js
├── context
│ ├── AppState.js
│ └── index.js
├── data
│ ├── color-blindness-types.js
│ ├── dropdown-heading-types.json
│ ├── dropdown-image-types.json
│ ├── focus-order-types.js
│ ├── gesture-types.js
│ ├── heading-types-native.js
│ ├── heading-types.js
│ ├── landmark-types.js
│ ├── reading-order-types.js
│ ├── responsive-reflow-default-breakpoints.json
│ ├── routes-native.json
│ ├── routes.json
│ ├── sample-structures.json
│ ├── tips.json
│ └── touch-target-types.js
├── figma-code
│ ├── config.js
│ ├── designer-checks.js
│ ├── frame-helpers.js
│ ├── index.js
│ ├── initialize-page.js
│ ├── on-selection-change.js
│ ├── onload-plugin.js
│ └── steps
│ │ ├── alt-text.js
│ │ ├── checkmark.js
│ │ ├── color-blindness.js
│ │ ├── color-contrast.js
│ │ ├── complex-gestures.js
│ │ ├── focus-grouping.js
│ │ ├── headings.js
│ │ ├── index.js
│ │ ├── landmarks.js
│ │ ├── reading-order.js
│ │ ├── responsive-reflow.js
│ │ ├── text-zoom.js
│ │ └── touch-target.js
├── icons
│ ├── SvgArrowRight.js
│ ├── SvgArrowWidth.js
│ ├── SvgCarrot.js
│ ├── SvgCheck.js
│ ├── SvgCheckSm.js
│ ├── SvgChevronDown.js
│ ├── SvgChevronLeft.js
│ ├── SvgClose.js
│ ├── SvgDownCarrot.js
│ ├── SvgEmojiCelebrate.js
│ ├── SvgEmojiMonocle.js
│ ├── SvgEmojiStarEyes.js
│ ├── SvgEyeClosed.js
│ ├── SvgEyeOpened.js
│ ├── SvgFocusGroup.js
│ ├── SvgFrame.js
│ ├── SvgImage.js
│ ├── SvgImageSm.js
│ ├── SvgInfo.js
│ ├── SvgInfoFill.js
│ ├── SvgLoadingSpinner.js
│ ├── SvgMobile.js
│ ├── SvgPlus.js
│ ├── SvgReorder.js
│ ├── SvgSettings.js
│ ├── SvgSlashNone.js
│ ├── SvgStepSlash.js
│ ├── SvgText.js
│ ├── SvgVector.js
│ ├── SvgWarning.js
│ ├── SvgWavy.js
│ ├── SvgWeb.js
│ ├── color-blindness
│ │ ├── SvgAchromatomaly.js
│ │ ├── SvgAchromatopsia.js
│ │ ├── SvgDeuteranomaly.js
│ │ ├── SvgDeuteranopia.js
│ │ ├── SvgOriginal.js
│ │ ├── SvgProtanopia.js
│ │ ├── SvgProtonomaly.js
│ │ ├── SvgTritanopia.js
│ │ └── index.js
│ ├── complex-gestures
│ │ ├── SvgDragDrop.js
│ │ ├── SvgMultiFingerTap.js
│ │ ├── SvgPinchZoom.js
│ │ ├── SvgRotate.js
│ │ ├── SvgSwipe.js
│ │ └── index.js
│ ├── focus-grouping
│ │ ├── SvgHelp1.js
│ │ ├── SvgHelp2.js
│ │ └── index.js
│ ├── focus-order
│ │ ├── SvgArrowKeys.js
│ │ ├── SvgTabStops.js
│ │ └── index.js
│ ├── headings
│ │ ├── SvgH.js
│ │ ├── SvgH1.js
│ │ ├── SvgH2.js
│ │ ├── SvgH3.js
│ │ ├── SvgH4.js
│ │ ├── SvgH5.js
│ │ ├── SvgH6.js
│ │ └── index.js
│ ├── index.js
│ ├── landmarks
│ │ ├── SvgBanner.js
│ │ ├── SvgComplimentary.js
│ │ ├── SvgContentInfo.js
│ │ ├── SvgForm.js
│ │ ├── SvgMain.js
│ │ ├── SvgNavigation.js
│ │ ├── SvgRegion.js
│ │ ├── SvgSearch.js
│ │ └── index.js
│ ├── reading-order
│ │ ├── SvgDown.js
│ │ ├── SvgDownLeft.js
│ │ ├── SvgDownRight.js
│ │ ├── SvgHelp1.js
│ │ ├── SvgHelp2.js
│ │ ├── SvgLeft.js
│ │ ├── SvgRight.js
│ │ ├── SvgUp.js
│ │ ├── SvgUpLeft.js
│ │ ├── SvgUpRight.js
│ │ └── index.js
│ └── touch-target
│ │ ├── SvgCustom.js
│ │ ├── SvgHelp1.js
│ │ ├── SvgHelp2.js
│ │ ├── SvgMin48.js
│ │ ├── SvgOverlap.js
│ │ ├── SvgResize.js
│ │ └── index.js
├── pages
│ ├── AltText.js
│ ├── ChooseYourOwnAdventure.js
│ ├── ColorBlindness.js
│ ├── ComplexGestures.js
│ ├── Contrast.js
│ ├── Dashboard.js
│ ├── FocusGrouping.js
│ ├── Headings.js
│ ├── InteractiveElements.js
│ ├── Landmarks.js
│ ├── PageChange.js
│ ├── ProgressLoading.js
│ ├── ReadingOrder.js
│ ├── ResponsiveReflow.js
│ ├── SelectFrameToStart.js
│ ├── Settings.js
│ ├── TextZoom.js
│ └── TouchTarget.js
├── styles
│ ├── animations.scss
│ ├── base.scss
│ ├── mixins.scss
│ ├── pages.scss
│ ├── reset.scss
│ ├── scaffolding.scss
│ ├── shared.scss
│ └── variables.scss
├── ui.html
└── ui.js
├── webpack.config.js
└── zip.js
/.env-example:
--------------------------------------------------------------------------------
1 | ANALYTICS_URL=
2 | FEEDBACK_FORM_URL=
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["airbnb", "prettier"],
3 | "plugins": ["prettier", "react", "eslint-plugin-import-helpers"],
4 | "parserOptions": {
5 | "ecmaVersion": 2020
6 | },
7 | "ignorePatterns": ["src/data/*.js"],
8 | "rules": {
9 | "global-require": 0,
10 | "prettier/prettier": ["error"],
11 | "no-case-declarations": 0,
12 | "no-param-reassign": 0,
13 | "no-promise-executor-return": 0,
14 | "no-restricted-globals": 0,
15 | "react/forbid-prop-types": 0,
16 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
17 | "react/jsx-fragments": 0,
18 | "react/jsx-no-constructed-context-values": 0,
19 | "react/jsx-props-no-spreading": 0,
20 | "react/no-danger": 0,
21 | "react/require-default-props": 0
22 | },
23 | "globals": {
24 | "__html__": "readonly",
25 | "document": "readonly",
26 | "fetch": "readonly",
27 | "figma": "readonly",
28 | "parent": "writable",
29 | "window": "readonly",
30 | "Blob": "readonly",
31 | "Image": "readonly"
32 | },
33 | "settings": {
34 | "import/resolver": {
35 | "webpack": {
36 | "config": "./webpack.config.js"
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/----feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F4BB Feature request"
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: calebnance
7 |
8 | ---
9 |
10 | ### Description
11 |
12 |
13 |
14 | ### Why
15 |
16 |
17 |
18 |
19 |
20 | ### Possible Implementation & Open Questions
21 |
22 |
23 |
24 |
25 |
26 | ### Is this something you're interested in working on?
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/---bug-report-.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F41B Bug report "
3 | about: Something isn't working right
4 | title: "[BUG]"
5 | labels: bug
6 | assignees: calebnance
7 |
8 | ---
9 |
10 | ### Details
11 |
12 |
13 |
14 | ### Expected Behavior
15 |
16 |
17 |
18 | ### Actual Behavior
19 |
20 |
21 |
22 | ### Possible Fix
23 |
24 |
25 |
26 | Additional Info
27 |
28 | ### Your Environment
29 |
30 |
31 |
32 | - Operating System and version (desktop or mobile):
33 | - Link to your project and/or Figma file:
34 |
35 | ### Steps to Reproduce
36 |
37 |
38 |
39 |
40 |
41 | 1. first...
42 | 2.
43 | 3.
44 | 4.
45 |
46 | ### Stack Trace
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/-question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "❓Question"
3 | about: A support question that isn't a bug or feature request
4 | title: "[QUESTION]"
5 | labels: ''
6 | assignees: calebnance
7 |
8 | ---
9 |
10 | ### Description
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Description
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | ## Checklist:
12 |
13 |
14 |
15 |
16 |
17 | - [ ] I have read the **CONTRIBUTING** document and agree to the project's Code of Conduct
18 | - [ ] I have updated/added documentation affected by my changes (in DOCS.md).
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 | dist_zips/
4 | docs/
5 |
6 | # macOS
7 | .DS_Store
8 |
9 | *.env
10 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org/
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 20.18.1
--------------------------------------------------------------------------------
/.stylelintignore:
--------------------------------------------------------------------------------
1 | **/*.js
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["stylelint-config-standard-scss"],
3 | "plugins": [
4 | "./custom-rules/hex-lowercase.js",
5 | "stylelint-order"
6 | ],
7 | "rules": {
8 | "custom-rules/hex-lowercase": true,
9 | "order/properties-alphabetical-order": true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
4 | # Include
5 |
6 | an accessibility annotation Figma plugin
7 |
8 | [Docs](https://include.ebaydesign.tech) ∙ [Try in Figma](https://www.figma.com/community/plugin/1208180794570801545/Include%3A-an-accessibility-annotation-tool) ∙ [Roadmap](#roadmap) ∙ [Contribute](#contributing)
9 |
10 |
11 |
12 | ## Intro
13 |
14 |
15 |
16 | 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.
17 |
18 | The plugin was developed by members of the accessibility and design teams at eBay and is released for public use on Figma. You can view and install the latest version of the plugin [here](https://www.figma.com/community/plugin/1208180794570801545/Include%3A-an-accessibility-annotation-tool).
19 |
20 | ## Roadmap
21 |
22 | **Near term bug fixes & improvements**
23 |
24 | - [ ] Scan for svg (alternative text)
25 | - [X] Add delete in multiple steps (v14)
26 | - [X] Add images manually in Alternative text step (v14)
27 | - [X] Add ability to edit landmarks (v12)
28 | - [X] Placing new arrow annotation below at end of previously placed arrow (v11)
29 | - [X] Touch target (v11)
30 | - [X] Updates for keyboard navigation (v10)
31 | - [X] Generate Responsive Designs from a single design (v10)
32 | - [X] Rename landmarks to use the HTML names (e.g. footer) and not aria roles (e.g. contentinfo).
33 |
34 | **Future explorations**
35 |
36 | - [X] Touch target revision (v15)
37 | - [X] Split between focus & reading order (v16)
38 | - [ ] Pointer gestures
39 | - [ ] Interactive elements step
40 | - [ ] Use of AI to generate labels
41 | - [ ] Cognitive step
42 | - [ ] Hearing step
43 |
44 | ## Installation
45 |
46 | ```
47 | npm i
48 | ```
49 |
50 | ## Development
51 |
52 | ```
53 | npm run dev
54 | ```
55 |
56 | To open **Inspect mode**
57 |
58 | ⌘ Command + ⌥ Option + I
59 |
60 | With the iframe of web app in a Figma plugin, hot-reloading doesn't really work, so to re-start the plugin quickly:
61 |
62 | ⌘ Command + ⌥ Option + P
63 |
64 | To open the plugin in development mode on Figma, map the manifest file at the root of this project.
65 |
66 |
67 |
68 | See [docs](https://include.ebaydesign.tech) for more details on the project file structure and Figma layer methods used in this project.
69 |
70 | ## Contributing
71 |
72 | The main purpose of this repository is to provide a jumping-off point for developers and designers who want to expand upon or customize the plugin's accessibility annotation functionality. We welcome pull requests, feature ideas, and bug reports.
73 |
74 | - Pull requests are always welcome
75 | - Submit GitHub issues for any feature enhancements, bugs, or documentation problems
76 | - Read the [Contribution Tips and Guidelines](/contributing.md)
77 | - Participants in this project agree to abide by its [Code of Conduct](https://github.com/eBay/.github/blob/main/CODE_OF_CONDUCT.md)
78 |
79 | ## License
80 |
81 | Apache 2.0 - See [LICENSE](/LICENSE) for more information.
82 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | '@babel/preset-env',
5 | {
6 | modules: false
7 | }
8 | ],
9 | '@babel/preset-react'
10 | ],
11 | plugins: [
12 | '@babel/plugin-transform-runtime',
13 | '@babel/plugin-syntax-dynamic-import',
14 | '@babel/plugin-proposal-class-properties'
15 | ],
16 | env: {
17 | production: {
18 | only: ['src'],
19 | plugins: [
20 | [
21 | 'transform-react-remove-prop-types',
22 | {
23 | removeImport: true
24 | }
25 | ],
26 | '@babel/plugin-transform-react-inline-elements',
27 | '@babel/plugin-transform-react-constant-elements'
28 | ]
29 | }
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/contributing.md:
--------------------------------------------------------------------------------
1 | # Contribution Tips and Guidelines
2 |
3 | The main purpose of this repository is to provide a jumping-off point for developers and designers who want to expand upon or customize the plugin's accessibility annotation functionality. We welcome pull requests, feature ideas, and bug reports.
4 |
5 | ## Pull requests are always welcome
6 |
7 | We are always thrilled to receive pull requests. We will do our best to process them quickly.
8 |
9 | ## Reporting Issues
10 |
11 | A great way to contribute to the project is to file a report when you encounter an issue. Check that our issue database doesn't already include that problem or suggestion before submitting an issue. If you find a match, you can use the "subscribe" button to get notified on updates.
12 |
13 | ## Code of Conduct
14 |
15 | Participants in this project agree to abide by its [Code of Conduct](https://github.com/eBay/.github/blob/main/CODE_OF_CONDUCT.md).
16 |
--------------------------------------------------------------------------------
/custom-rules/hex-lowercase.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable-next-line */
2 | const stylelint = require('stylelint');
3 |
4 | // pulled from
5 | // https://medium.com/swlh/writing-your-first-custom-stylelint-rule-a9620bb2fb73
6 | const { report, ruleMessages, validateOptions } = stylelint.utils;
7 |
8 | const ruleName = 'custom-rules/hex-lowercase';
9 |
10 | const messages = ruleMessages(ruleName, {
11 | expected: (unfixed, fixed) => `Expected "${unfixed}" to be "${fixed}"`
12 | });
13 |
14 | module.exports = stylelint.createPlugin(
15 | ruleName,
16 | (primaryOption, secondaryOptionObject, context) =>
17 | function lint(postcssRoot, postcssResult) {
18 | const validOptions = validateOptions(postcssResult, ruleName, {
19 | // no options for now...
20 | });
21 |
22 | if (!validOptions) {
23 | return;
24 | }
25 |
26 | const isAutoFixing = Boolean(context.fix);
27 | postcssRoot.walkDecls((decl) => {
28 | const hasCapitalLetters =
29 | /^#([A-F0-9]{6}|[A-F0-9]{3})$/.test(decl.value) &&
30 | /[A-Z]/.test(decl.value);
31 |
32 | if (!hasCapitalLetters) {
33 | return;
34 | }
35 |
36 | const { value } = decl;
37 | const newValue = value.toLowerCase();
38 |
39 | if (isAutoFixing) {
40 | if (decl.raws.value) {
41 | decl.raws.value.raw = newValue;
42 | } else {
43 | decl.value = newValue;
44 | }
45 | } else {
46 | report({
47 | ruleName,
48 | result: postcssResult,
49 | message: messages.expected(value, newValue),
50 | node: decl,
51 | word: value
52 | });
53 | }
54 | });
55 | }
56 | );
57 |
58 | module.exports.ruleName = ruleName;
59 | module.exports.messages = messages;
60 |
--------------------------------------------------------------------------------
/jsdoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "tags": {
3 | "allowUnknownTags": false
4 | },
5 | "source": {
6 | "include": "./src",
7 | "includePattern": "\\.js$",
8 | "excludePattern": "(node_modules/|docs)"
9 | },
10 | "plugins": ["plugins/markdown"],
11 | "opts": {
12 | "template": "node_modules/docdash",
13 | "encoding": "utf8",
14 | "destination": "docs/",
15 | "recurse": true,
16 | "verbose": true
17 | },
18 | "templates": {
19 | "cleverLinks": false,
20 | "monospaceLinks": false
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Include – Accessibility Annotations",
3 | "id": "1208180794570801545",
4 | "api": "1.0.0",
5 | "main": "dist/code.js",
6 | "ui": "dist/ui.html",
7 | "editorType": ["figma"],
8 | "permissions": ["currentuser"],
9 | "documentAccess": "dynamic-page",
10 | "networkAccess": {
11 | "allowedDomains": ["https://include-analytics.ebaydesign.tech"],
12 | "reasoning": "No file data is collected at all. The only analytics we track are: who is using it (Figma user ID used for anonymized usage), when a new annotation is started, a step is completed, and dashboard is opened."
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "figma-include-accessibility-annotations",
3 | "version": "16",
4 | "description": "Include is a tool built to make annotating for accessibility (a11y) easier",
5 | "main": "code.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "webpack --mode=development --watch",
9 | "dev": "webpack --mode=development --watch",
10 | "serve": "webpack serve",
11 | "bundle": "webpack --mode=production && node zip.js",
12 | "lint": "eslint ./src",
13 | "lint-scss": "stylelint \"**/*.scss\"",
14 | "docs": "node_modules/.bin/jsdoc --readme DOCS.md -c jsdoc.json"
15 | },
16 | "author": "eBay Design Technology",
17 | "devDependencies": {
18 | "@babel/core": "^7.27.1",
19 | "@babel/eslint-parser": "^7.27.1",
20 | "@babel/plugin-proposal-class-properties": "^7.18.6",
21 | "@babel/plugin-syntax-dynamic-import": "^7.8.3",
22 | "@babel/plugin-transform-react-constant-elements": "^7.27.1",
23 | "@babel/plugin-transform-react-inline-elements": "^7.27.1",
24 | "@babel/plugin-transform-runtime": "^7.27.1",
25 | "@babel/preset-env": "^7.27.2",
26 | "@babel/preset-react": "^7.27.1",
27 | "@babel/runtime": "^7.27.1",
28 | "adm-zip": "^0.5.16",
29 | "babel-loader": "^9.2.1",
30 | "babel-plugin-transform-react-remove-prop-types": "^0.4.24",
31 | "chalk": "^4.1.2",
32 | "css-loader": "^6.11.0",
33 | "docdash": "^1.2.0",
34 | "eslint": "^8.57.1",
35 | "eslint-config-airbnb": "^19.0.4",
36 | "eslint-config-prettier": "^9.1.0",
37 | "eslint-import-resolver-webpack": "^0.13.10",
38 | "eslint-plugin-import": "^2.31.0",
39 | "eslint-plugin-import-helpers": "^1.3.1",
40 | "eslint-plugin-jsx-a11y": "^6.10.2",
41 | "eslint-plugin-prettier": "^5.4.0",
42 | "eslint-plugin-react": "^7.37.5",
43 | "eslint-plugin-react-hooks": "^4.6.2",
44 | "html-webpack-inline-source-plugin": "^0.0.10",
45 | "html-webpack-plugin": "^5.6.3",
46 | "jsdoc": "~4.0.4",
47 | "prettier": "^3.5.3",
48 | "sass": "^1.87.0",
49 | "sass-loader": "^16.0.5",
50 | "style-loader": "^3.3.4",
51 | "stylelint": "^15.11.0",
52 | "stylelint-config-standard-scss": "^11.1.0",
53 | "stylelint-order": "^6.0.4",
54 | "stylelint-prettier": "^4.1.0",
55 | "url-loader": "^4.1.1",
56 | "webpack": "^5.99.8",
57 | "webpack-cli": "^5.1.4",
58 | "webpack-dev-server": "^4.15.2"
59 | },
60 | "dependencies": {
61 | "dotenv": "^16.5.0",
62 | "prop-types": "^15.8.1",
63 | "react": "^18.3.1",
64 | "react-beautiful-dnd": "^13.1.1",
65 | "react-dev-utils": "^12.0.1",
66 | "react-dom": "^18.3.1",
67 | "react-router-dom": "^6.30.0"
68 | },
69 | "prettier": {
70 | "singleQuote": true,
71 | "trailingComma": "none"
72 | },
73 | "engines": {
74 | "node": ">=20.18.1"
75 | }
76 | }
--------------------------------------------------------------------------------
/previews/import-manifest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eBay/figma-include-accessibility-annotations/4d03c8c824a928980e58f3aa0e83bf0765fce98d/previews/import-manifest.png
--------------------------------------------------------------------------------
/previews/include_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eBay/figma-include-accessibility-annotations/4d03c8c824a928980e58f3aa0e83bf0765fce98d/previews/include_logo.png
--------------------------------------------------------------------------------
/previews/v10/include_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eBay/figma-include-accessibility-annotations/4d03c8c824a928980e58f3aa0e83bf0765fce98d/previews/v10/include_banner.png
--------------------------------------------------------------------------------
/previews/v11/include_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eBay/figma-include-accessibility-annotations/4d03c8c824a928980e58f3aa0e83bf0765fce98d/previews/v11/include_banner.png
--------------------------------------------------------------------------------
/previews/v12/include_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eBay/figma-include-accessibility-annotations/4d03c8c824a928980e58f3aa0e83bf0765fce98d/previews/v12/include_banner.png
--------------------------------------------------------------------------------
/previews/v14/include_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eBay/figma-include-accessibility-annotations/4d03c8c824a928980e58f3aa0e83bf0765fce98d/previews/v14/include_banner.png
--------------------------------------------------------------------------------
/previews/v15/include_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eBay/figma-include-accessibility-annotations/4d03c8c824a928980e58f3aa0e83bf0765fce98d/previews/v15/include_banner.png
--------------------------------------------------------------------------------
/previews/v16/include_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eBay/figma-include-accessibility-annotations/4d03c8c824a928980e58f3aa0e83bf0765fce98d/previews/v16/include_banner.png
--------------------------------------------------------------------------------
/previews/v8/include_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eBay/figma-include-accessibility-annotations/4d03c8c824a928980e58f3aa0e83bf0765fce98d/previews/v8/include_banner.png
--------------------------------------------------------------------------------
/previews/v9/include_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eBay/figma-include-accessibility-annotations/4d03c8c824a928980e58f3aa0e83bf0765fce98d/previews/v9/include_banner.png
--------------------------------------------------------------------------------
/src/components/Alert/index.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | // styles
5 | import './styles.scss';
6 |
7 | function Alert({ icon = null, style = {}, text, type = 'info' }) {
8 | return (
9 |
10 | {icon &&
{icon}
}
11 |
12 |
{text}
13 |
14 | );
15 | }
16 |
17 | Alert.propTypes = {
18 | // required
19 | text: PropTypes.string.isRequired,
20 |
21 | // optional
22 | icon: PropTypes.element,
23 | style: PropTypes.object,
24 | type: PropTypes.oneOf(['info', 'warning'])
25 | };
26 |
27 | export default React.memo(Alert);
28 |
--------------------------------------------------------------------------------
/src/components/Alert/styles.scss:
--------------------------------------------------------------------------------
1 | .alert {
2 | align-items: center;
3 | color: var(--figma-color-text);
4 | display: flex;
5 | padding: var(--spacing-sm);
6 |
7 | &.warning {
8 | color: var(--figma-color-bg-danger);
9 | }
10 | }
--------------------------------------------------------------------------------
/src/components/AltTextRow/index.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { utils } from '@/constants';
4 |
5 | // components
6 | import Dropdown from '@/components/Dropdown';
7 |
8 | // data
9 | import imageTypesArray from '@/data/dropdown-image-types.json';
10 |
11 | // app state
12 | import Context from '@/context';
13 |
14 | // styles
15 | import './styles.scss';
16 |
17 | function AltTextRow(props) {
18 | // main app state
19 | const { zoomTo } = React.useContext(Context);
20 |
21 | // props data
22 | const { base64 = null, displayType, index } = props;
23 | const { image, imageBuffer = null, isOpened, warnClass = '' } = props;
24 |
25 | // image data
26 | const { id, altText, name, type } = image;
27 |
28 | // on functions
29 | const { onChange, onFocus, onOpen, onSelect, onRemove } = props;
30 |
31 | const canEdit = type === 'informative';
32 |
33 | return (
34 |
35 |
zoomTo([id], true)}
38 | onKeyDown={({ key }) => {
39 | if (utils.isEnterKey(key)) zoomTo([id], true);
40 | }}
41 | role="button"
42 | tabIndex="0"
43 | >
44 | {displayType === 'scanned' && (
45 |

50 | )}
51 |
52 | {displayType === 'manual' && (
53 |
60 | )}
61 |
62 |
scroll to
63 |
64 |
65 |
Alt text
66 |
67 | {canEdit === false &&
n/a
}
68 |
69 | {canEdit && (
70 |
78 | )}
79 |
80 |
88 |
89 |
{
94 | if (utils.isEnterKey(e.key)) onRemove();
95 | }}
96 | role="button"
97 | tabIndex="0"
98 | >
99 |
100 |
101 |
102 | );
103 | }
104 |
105 | AltTextRow.propTypes = {
106 | // required
107 | displayType: PropTypes.oneOf(['manual', 'scanned']).isRequired,
108 | image: PropTypes.shape({
109 | id: PropTypes.string.isRequired,
110 | altText: PropTypes.string.isRequired,
111 | name: PropTypes.string.isRequired,
112 | type: PropTypes.string.isRequired
113 | }).isRequired,
114 | index: PropTypes.number.isRequired,
115 | isOpened: PropTypes.bool.isRequired,
116 | onChange: PropTypes.func.isRequired,
117 | onFocus: PropTypes.func.isRequired,
118 | onOpen: PropTypes.func.isRequired,
119 | onSelect: PropTypes.func.isRequired,
120 | onRemove: PropTypes.func.isRequired,
121 |
122 | // optional
123 | base64: PropTypes.string,
124 | imageBuffer: PropTypes.instanceOf(Uint8Array),
125 | warnClass: PropTypes.string
126 | };
127 |
128 | export default React.memo(AltTextRow);
129 |
--------------------------------------------------------------------------------
/src/components/AltTextRow/styles.scss:
--------------------------------------------------------------------------------
1 | .alt-text-row {
2 | align-items: center;
3 | column-gap: var(--spacing-xs);
4 | display: flex;
5 | margin-bottom: var(--spacing-xs);
6 | }
7 |
8 | .container-image-preview {
9 | position: relative;
10 |
11 | .scroll-to {
12 | background-color: var(--figma-color-bg-inverse);
13 | border-bottom-left-radius: 5px;
14 | border-bottom-right-radius: 5px;
15 | bottom: 0;
16 | color: var(--figma-color-text-oninverse);
17 | font-size: 10px;
18 | left: 0;
19 | opacity: 0;
20 | padding-bottom: 2px;
21 | position: absolute;
22 | text-align: center;
23 | transition: all 200ms ease;
24 | width: 100%;
25 | }
26 |
27 | &:hover .scroll-to {
28 | opacity: 1;
29 | }
30 | }
31 |
32 | .image-preview {
33 | background-color: var(--figma-color-bg-disabled);
34 | border-radius: 6px;
35 | height: 48px;
36 | min-width: 48px;
37 | object-fit: contain;
38 | width: 48px;
39 | }
40 |
41 | .image-preview-blob {
42 | background-color: var(--figma-color-bg-disabled);
43 | background-repeat: no-repeat;
44 | background-size: cover;
45 | border-radius: 6px;
46 | height: 48px;
47 | min-width: 48px;
48 | width: 48px;
49 | }
50 |
51 | .input {
52 | flex-grow: 1;
53 | }
54 |
55 | .input-na {
56 | flex-grow: 1;
57 | padding-left: 8px;
58 | }
--------------------------------------------------------------------------------
/src/components/AnnotationStepPage.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { analytics } from '@/constants';
4 |
5 | // components
6 | import BannerTip from '@/components/BannerTip';
7 | import Footer from '@/components/Footer';
8 |
9 | // app state
10 | import Context from '@/context';
11 |
12 | function AnnotationStepPage({
13 | children,
14 | bannerTipProps,
15 | footerProps = {},
16 | title,
17 | routeName
18 | }) {
19 | const cnxt = React.useContext(Context);
20 | const { currentUser, sessionId, isProd } = cnxt;
21 |
22 | React.useEffect(() => {
23 | analytics.logEvent({
24 | pageTitle: encodeURIComponent(routeName),
25 | sessionId,
26 | currentUser,
27 | isProd
28 | });
29 | }, []);
30 |
31 | return (
32 |
33 |
34 |
35 |
36 | {title}
37 |
38 | {children}
39 |
40 |
41 |
42 | );
43 | }
44 |
45 | AnnotationStepPage.propTypes = {
46 | // required
47 | bannerTipProps: PropTypes.shape({
48 | pageType: PropTypes.oneOf(['web', 'native']).isRequired,
49 | routeName: PropTypes.string.isRequired,
50 | customFooter: PropTypes.element
51 | }).isRequired,
52 | children: PropTypes.element.isRequired,
53 | routeName: PropTypes.string.isRequired,
54 | title: PropTypes.string.isRequired,
55 |
56 | // optional
57 | footerProps: PropTypes.shape({
58 | primaryAction: PropTypes.object,
59 | secondaryAction: PropTypes.object
60 | })
61 | };
62 |
63 | export default AnnotationStepPage;
64 |
--------------------------------------------------------------------------------
/src/components/BannerAlert/index.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | // styles
5 | import './styles.scss';
6 |
7 | function BannerAlert({ icon = null, text }) {
8 | return (
9 |
10 | {icon &&
{icon}
}
11 |
12 |
{text}
13 |
14 | );
15 | }
16 |
17 | BannerAlert.propTypes = {
18 | // required
19 | text: PropTypes.string.isRequired,
20 |
21 | // optional
22 | icon: PropTypes.element
23 | };
24 |
25 | export default React.memo(BannerAlert);
26 |
--------------------------------------------------------------------------------
/src/components/BannerAlert/styles.scss:
--------------------------------------------------------------------------------
1 | .banner-alert-icon {
2 | margin-right: var(--spacing-sm);
3 | margin-top: 3px;
4 | }
--------------------------------------------------------------------------------
/src/components/BannerSuccess.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | // icons
5 | import { SvgCheck } from '@/icons';
6 |
7 | function BannerSuccess({ text }) {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
{text}
15 |
16 | );
17 | }
18 |
19 | BannerSuccess.propTypes = {
20 | // required
21 | text: PropTypes.string.isRequired
22 | };
23 |
24 | export default React.memo(BannerSuccess);
25 |
--------------------------------------------------------------------------------
/src/components/BannerTip.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | // components
5 | import BannerTipText from '@/components/BannerTipText';
6 |
7 | // data
8 | import tips from '@/data/tips.json';
9 |
10 | function BannerTip({ footer = null, pageType, routeName = 'Landmarks' }) {
11 | const tip = tips[pageType][routeName];
12 |
13 | return (
14 |
20 | );
21 | }
22 |
23 | BannerTip.propTypes = {
24 | footer: PropTypes.element,
25 | routeName: PropTypes.string,
26 | pageType: PropTypes.oneOf(['web', 'native']).isRequired
27 | };
28 |
29 | export default React.memo(BannerTip);
30 |
--------------------------------------------------------------------------------
/src/components/BannerTipText/index.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { utils } from '@/constants';
4 |
5 | // icons
6 | import { SvgChevronDown } from '@/icons';
7 |
8 | // app state
9 | import Context from '@/context';
10 |
11 | // styles
12 | import './styles.scss';
13 |
14 | function BannerTipText(props) {
15 | // main app state
16 | const { sendToFigma, tipExpanded, updateState } = React.useContext(Context);
17 |
18 | // props
19 | const { footer = null } = props;
20 | const { helpText = 'Learn more', helpUrl = null, text } = props;
21 |
22 | // local state
23 | const [animateClass, setAnimateClass] = React.useState('');
24 |
25 | // ui state
26 | const tabIndex = tipExpanded ? '0' : '-1';
27 | const isLink =
28 | helpUrl !== null
29 | ? ` ${helpText}`
30 | : '';
31 | const displayText = `${text}${isLink}`;
32 | const ariaLabel = tipExpanded ? 'collapse' : 'expand';
33 | const rotateClass = tipExpanded ? ' rotate-right-rev' : ' rotate-left-rev';
34 | const tipTextClass = tipExpanded ? '' : 'tip-text-collapsed';
35 |
36 | const onToggle = () => {
37 | updateState('tipExpanded', !tipExpanded);
38 |
39 | sendToFigma('set-tip-preference', {
40 | expanded: !tipExpanded
41 | });
42 | };
43 |
44 | // animate on mount
45 | React.useEffect(() => {
46 | const animateTimer = setTimeout(() => {
47 | setAnimateClass(' animated');
48 | }, 800);
49 |
50 | return () => {
51 | clearTimeout(animateTimer);
52 | };
53 | }, []);
54 |
55 | return (
56 |
57 |
58 |
{
63 | if (utils.isEnterKey(key)) onToggle();
64 | }}
65 | role="button"
66 | tabIndex="0"
67 | >
68 |
69 |
70 |
71 |
72 |
73 |
tip
74 |
75 |
79 |
80 |
81 | {footer && tipExpanded && footer}
82 |
83 | );
84 | }
85 |
86 | BannerTipText.propTypes = {
87 | // required
88 | text: PropTypes.string.isRequired,
89 |
90 | // optional
91 | footer: PropTypes.element,
92 | helpText: PropTypes.string,
93 | helpUrl: PropTypes.string
94 | };
95 |
96 | export default React.memo(BannerTipText);
97 |
--------------------------------------------------------------------------------
/src/components/BannerTipText/styles.scss:
--------------------------------------------------------------------------------
1 | .banner-tip {
2 | background-color: var(--background-warning);
3 | border-radius: 6px;
4 | color: var(--foreground-on-warning);
5 | font-size: 11px;
6 | line-height: 15px;
7 | padding: var(--spacing-xs) var(--spacing-md) var(--spacing-xs) var(--spacing-xs);
8 | position: relative;
9 | }
10 |
11 | .tip-label {
12 | font-size: 12px;
13 | font-weight: bold;
14 | margin-right: var(--spacing-xs);
15 | text-transform: uppercase;
16 | }
17 |
18 | .tip-text-collapsed {
19 | max-height: 16px;
20 | overflow: hidden;
21 | text-overflow: ellipsis;
22 | white-space: nowrap;
23 | }
24 |
25 | .tip-toggle {
26 | cursor: pointer;
27 | position: absolute!important;
28 | right: 8px;
29 | top: 8px;
30 |
31 | & .rotate-left-rev {
32 | transform: rotate(-180deg)
33 | }
34 |
35 | & .rotate-right-rev {
36 | transform: rotate(0deg)
37 | }
38 | }
39 |
40 | .tip-link {
41 | border-radius: 2px;
42 | color: var(--foreground-on-warning);
43 | display: inline-block;
44 | font-size: 12px;
45 | margin-left: 26px;
46 | margin-top: 8px;
47 | text-decoration: underline;
48 | }
--------------------------------------------------------------------------------
/src/components/Checkbox/index.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { utils } from '@/constants';
4 |
5 | // icons
6 | import { SvgCheck } from '@/icons';
7 |
8 | // styles
9 | import './styles.scss';
10 |
11 | function Checkbox(props) {
12 | const { checked = false, id, isDisabled = false, label = null } = props;
13 | const { onClick } = props;
14 |
15 | // checked and disabled state
16 | const checkedClass = checked ? ' checked' : '';
17 | const noEvents = isDisabled ? ' no-events' : '';
18 | const onSelect = isDisabled ? () => null : onClick;
19 |
20 | return (
21 | {
27 | if (utils.isEnterKey(key)) onSelect();
28 | }}
29 | role="radio"
30 | tabIndex="0"
31 | >
32 |
33 | {checked && }
34 |
35 |
36 | {label !== null && (
37 |
40 | )}
41 |
42 | );
43 | }
44 |
45 | Checkbox.propTypes = {
46 | // required
47 | id: PropTypes.string.isRequired,
48 | onClick: PropTypes.func.isRequired,
49 |
50 | // optional
51 | checked: PropTypes.bool,
52 | isDisabled: PropTypes.bool,
53 | label: PropTypes.string
54 | };
55 |
56 | export default React.memo(Checkbox);
57 |
--------------------------------------------------------------------------------
/src/components/Checkbox/styles.scss:
--------------------------------------------------------------------------------
1 | .container-checkbox {
2 | align-items: center;
3 | border-radius: 2px;
4 | cursor: pointer;
5 | display: flex;
6 | flex-shrink: 0;
7 | width: 100%;
8 |
9 | label {
10 | cursor: pointer;
11 | font-size: 13px;
12 | }
13 |
14 | &.no-events {
15 | cursor: default;
16 | pointer-events: none;
17 |
18 | label {
19 | cursor: default;
20 | pointer-events: none;
21 | }
22 | }
23 | }
24 |
25 | .checkbox {
26 | align-items: center;
27 | border: 1px solid var(--figma-color-icon);
28 | border-radius: 3px;
29 | display: flex;
30 | height: 18px;
31 | justify-content: center;
32 | padding-right: 1px;
33 | width: 18px;
34 |
35 | &.checked {
36 | background-color: var(--figma-color-text);
37 | border-color: var(--figma-color-text);
38 |
39 | svg {
40 | path {
41 | fill: var(--figma-color-bg);
42 | }
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/src/components/ColorBlindnessFilter.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | function ColorBlindnessFilter() {
4 | return (
5 |
89 | );
90 | }
91 |
92 | export default React.memo(ColorBlindnessFilter);
93 |
--------------------------------------------------------------------------------
/src/components/ContrastScreenshot/index.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { contrast, utils } from '@/constants';
4 |
5 | // icons
6 | import { SvgWarning } from '@/icons';
7 |
8 | // app state
9 | import Context from '@/context';
10 |
11 | // styles
12 | import './styles.scss';
13 |
14 | function ContrastScreenshot({ benchmark = 'aa', report }) {
15 | // main app state
16 | const { condensedUI, zoomTo } = React.useContext(Context);
17 |
18 | const { width, hotspots, pageBgColor } = report;
19 |
20 | const onClick = (nodeId) => zoomTo([nodeId], true);
21 |
22 | // condensed: 434
23 | // full width: 441
24 | const cWidth = condensedUI ? 434 : 441;
25 |
26 | const scaleResize = parseFloat(cWidth / width, 10);
27 |
28 | return (
29 |
37 | {report && (
38 |
39 |

40 |
41 | {hotspots.map(({ x, y, w, h, nodeId, aa, aaa }) => {
42 | const { note, contrastRatio, status } =
43 | benchmark === 'aa' ? aa : aaa;
44 | const statusClass = `status-${status}`;
45 | const statusUpper = utils.capitalize(status);
46 |
47 | return (
48 |
onClick(nodeId)}
52 | onKeyDown={({ key }) => {
53 | if (utils.isEnterKey(key)) onClick(nodeId);
54 | }}
55 | role="link"
56 | tabIndex="0"
57 | style={{
58 | left: Math.floor(x * scaleResize),
59 | top: Math.floor(y * scaleResize),
60 | width: Math.ceil(w * scaleResize),
61 | height: Math.ceil(h * scaleResize)
62 | }}
63 | >
64 |
65 | {status === 'fail' && }
66 | {`${statusUpper}: `}
67 | {note || contrast.formatContrastRatio(contrastRatio)}
68 |
69 |
70 |
71 | );
72 | })}
73 |
74 | )}
75 |
76 | );
77 | }
78 |
79 | ContrastScreenshot.propTypes = {
80 | // required
81 | report: PropTypes.object.isRequired,
82 |
83 | // optional
84 | benchmark: PropTypes.string
85 | };
86 |
87 | export default React.memo(ContrastScreenshot);
88 |
--------------------------------------------------------------------------------
/src/components/ContrastScreenshot/styles.scss:
--------------------------------------------------------------------------------
1 | .contrast-preview {
2 | border: 1px solid var(--figma-color-border);
3 | overflow: hidden;
4 | user-select: none;
5 | }
6 |
7 | .contrast-preview-content {
8 | display: flex;
9 | position: relative;
10 |
11 | & img {
12 | height: auto;
13 | width: 100%;
14 | }
15 | }
16 |
17 | .text-node-hotspot {
18 | cursor: pointer;
19 | display: flex;
20 | position: absolute;
21 |
22 | & span {
23 | border-radius: 4px;
24 | color: #fff;
25 | display: none;
26 | font-family: sans-serif;
27 | font-size: 10px;
28 | font-weight: bold;
29 | line-height: 16px;
30 | padding: 0 4px;
31 | pointer-events: none;
32 | position: absolute;
33 | white-space: nowrap;
34 | z-index: 10;
35 | }
36 |
37 | &:hover {
38 | outline: 1px solid transparent;
39 |
40 | & span {
41 | display: block;
42 | }
43 | }
44 |
45 | &:focus-visible {
46 | outline: 1px solid transparent;
47 |
48 | &.status-fail {
49 | outline-color: var(--border-attention);
50 | }
51 |
52 | &.status-mixed,
53 | &.status-unknown {
54 | outline-color: var(--border-warning);
55 | }
56 |
57 | &.status-pass {
58 | outline-color: var(--figma-color-bg-success);
59 | }
60 |
61 | & span {
62 | display: block;
63 | }
64 | }
65 |
66 | &.status-fail span {
67 | align-items: center;
68 | background-color: var(--background-attention);
69 | color: var(--foreground-on-color);
70 | display: flex;
71 |
72 | svg {
73 | margin-right: 4px;
74 | }
75 | }
76 |
77 | &.status-mixed span,
78 | &.status-unknown span {
79 | background-color: var(--background-warning);
80 | color: var(--foreground-on-warning);
81 | }
82 |
83 | &.status-pass span {
84 | background-color: var(--figma-color-bg-success);
85 | color: var(--figma-color-text-onsuccess);
86 | }
87 | }
88 |
89 | .text-node-hotspot-bg {
90 | height: 100%;
91 | opacity: 0.6;
92 | position: absolute;
93 | width: 100%;
94 | z-index: 5;
95 |
96 | .status-fail & {
97 | background-color: var(--background-attention);
98 | }
99 |
100 | .status-mixed &,
101 | .status-unknown & {
102 | background-color: var(--background-warning);
103 | }
104 |
105 | .status-pass & {
106 | background-color: var(--figma-color-bg-success);
107 | }
108 | }
--------------------------------------------------------------------------------
/src/components/Dropdown/index.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { utils } from '@/constants';
4 |
5 | // icons
6 | import { SvgCheckSm, SvgDownCarrot } from '@/icons';
7 |
8 | // styles
9 | import './styles.scss';
10 |
11 | function Dropdown(props) {
12 | const { align = 'left', data, disabledValues = [], index } = props;
13 | const { isOpened = false, onOpen, onSelect, type } = props;
14 |
15 | // ui state
16 | const toggledValue = isOpened ? null : index;
17 | const openedClass = isOpened ? ' opened' : '';
18 | const alignClass = align === 'right' ? ' align-right' : '';
19 |
20 | const didSelect = (selectedType) => {
21 | // set new selection
22 | onSelect(selectedType, index);
23 |
24 | // close dropdown
25 | onOpen(toggledValue);
26 | };
27 |
28 | return (
29 |
30 |
onOpen(toggledValue)}
33 | onKeyDown={({ key }) => {
34 | if (utils.isEnterKey(key)) onOpen(toggledValue);
35 | }}
36 | role="button"
37 | tabIndex="0"
38 | >
39 | {type}
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
87 |
88 |
onOpen(toggledValue)}
92 | onKeyDown={({ key }) => {
93 | if (utils.isEnterKey(key)) onOpen(toggledValue);
94 | }}
95 | role="button"
96 | tabIndex="-1"
97 | >
98 |
99 |
100 |
101 | );
102 | }
103 |
104 | Dropdown.propTypes = {
105 | // required
106 | data: PropTypes.arrayOf(
107 | PropTypes.shape({
108 | id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
109 | disabled: PropTypes.bool,
110 | label: PropTypes.string,
111 | value: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
112 | .isRequired
113 | })
114 | ).isRequired,
115 | index: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
116 | onOpen: PropTypes.func.isRequired,
117 | onSelect: PropTypes.func.isRequired,
118 | type: PropTypes.string.isRequired,
119 |
120 | // optional
121 | align: PropTypes.oneOf(['left', 'right']),
122 | disabledValues: PropTypes.arrayOf(PropTypes.string),
123 | isOpened: PropTypes.bool
124 | };
125 |
126 | export default React.memo(Dropdown);
127 |
--------------------------------------------------------------------------------
/src/components/Dropdown/styles.scss:
--------------------------------------------------------------------------------
1 | .container-dropdown {
2 | display: inline-block;
3 | overflow: hidden;
4 | position: relative;
5 |
6 | .dropdown {
7 | align-items: center;
8 | border-radius: 2px;
9 | cursor: pointer;
10 | display: inline-flex;
11 | padding: 2px 6px;
12 | position: relative;
13 | text-transform: capitalize;
14 | }
15 |
16 | .dropdown-options {
17 | background-color: var(--figma-color-bg-inverse);
18 | border: 1px solid var(--figma-color-border-strong);
19 | border-radius: 2px;
20 | box-shadow: 0 2px 6px rgb(0 0 0 / 15%);
21 | color: var(--figma-color-text-oninverse);
22 | filter: drop-shadow(0 4px 4px var(--figma-color-bg-inverse));
23 | margin: 0;
24 | opacity: 0;
25 | padding: 0;
26 | position: absolute;
27 | text-transform: capitalize;
28 | top: calc(100% + 0.4rem);
29 |
30 | // transition: all 300ms ease;
31 | width: 148px;
32 | z-index: 99;
33 |
34 | .figma-dark & {
35 | background-color: var(--figma-color-bg-hover);
36 | border-color: var(--figma-color-border);
37 | color: var(--figma-color-text);
38 | filter: drop-shadow(0 4px 4px var(--figma-color-bg));
39 | }
40 |
41 | &.align-right {
42 | right: 0;
43 | }
44 | }
45 |
46 | .dropdown-overlay-close {
47 | inset: 0;
48 | pointer-events: none;
49 | position: fixed;
50 | transform: translate(100%, 0);
51 | z-index: 9;
52 | }
53 |
54 | &.opened {
55 | overflow: visible;
56 |
57 | .dropdown-options {
58 | opacity: 1;
59 | padding: var(--spacing-xs) 0;
60 | }
61 |
62 | .dropdown {
63 | z-index: 99;
64 | }
65 |
66 | .dropdown-overlay-close {
67 | pointer-events: auto;
68 | transform: none;
69 | }
70 | }
71 |
72 | .dropdown-option {
73 | cursor: pointer;
74 | line-height: 24px;
75 | list-style: none;
76 | padding-left: var(--spacing-lg);
77 | width: 100%;
78 |
79 | &:hover {
80 | background-color: var(--background-accent);
81 | }
82 | }
83 |
84 | .dropdown-option-disabled {
85 | & .dropdown-option {
86 | cursor: default;
87 | opacity: 0.5;
88 |
89 | &:hover {
90 | background-color: transparent;
91 | }
92 | }
93 | }
94 |
95 | .dropdown-option-selected {
96 | left: 5px;
97 | position: absolute;
98 | top: 5px;
99 | }
100 | }
--------------------------------------------------------------------------------
/src/components/EmptyStepSelection.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | // components
5 | import Checkbox from '@/components/Checkbox';
6 |
7 | function EmptyStepSelection(props) {
8 | const { id, isDisabled = false, isSelected, onClick, text } = props;
9 |
10 | return (
11 |
12 |
19 |
20 |
21 |
22 | );
23 | }
24 |
25 | EmptyStepSelection.propTypes = {
26 | // required
27 | id: PropTypes.string.isRequired,
28 | isSelected: PropTypes.bool.isRequired,
29 | onClick: PropTypes.func.isRequired,
30 | text: PropTypes.string.isRequired,
31 |
32 | // optional
33 | isDisabled: PropTypes.bool
34 | };
35 |
36 | export default EmptyStepSelection;
37 |
--------------------------------------------------------------------------------
/src/components/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { analytics } from '@/constants';
4 |
5 | // app state
6 | import Context from '@/context';
7 |
8 | // catch any React component issues and log to GA
9 | class ErrorBoundary extends React.Component {
10 | componentDidCatch(error) {
11 | const { isProd, sessionId, currentUser } = this.context;
12 |
13 | analytics.logEvent({
14 | name: 'error_boundary_error',
15 | pageTitle: error,
16 | sessionId,
17 | currentUser,
18 | isProd
19 | });
20 | }
21 |
22 | render() {
23 | const { children } = this.props;
24 |
25 | return children;
26 | }
27 | }
28 |
29 | ErrorBoundary.contextType = Context;
30 |
31 | ErrorBoundary.propTypes = {
32 | children: PropTypes.node.isRequired
33 | };
34 |
35 | export default ErrorBoundary;
36 |
--------------------------------------------------------------------------------
/src/components/FooterActionButton.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Link } from 'react-router-dom';
4 |
5 | function FooterActionButton(props) {
6 | const { children, className = '', goToNextStep = false } = props;
7 | const { isDisabled = false, isLast, onClick = () => {}, next = null } = props;
8 |
9 | // show an interactive button
10 | if (goToNextStep && !isDisabled) {
11 | return (
12 |
19 | {children}
20 |
21 | );
22 | }
23 |
24 | // show a disabled button
25 | return (
26 |
34 | );
35 | }
36 |
37 | FooterActionButton.propTypes = {
38 | // required
39 | children: PropTypes.node.isRequired,
40 | isLast: PropTypes.bool.isRequired,
41 |
42 | // optional
43 | className: PropTypes.string,
44 | goToNextStep: PropTypes.bool,
45 | isDisabled: PropTypes.bool,
46 | onClick: PropTypes.func,
47 | next: PropTypes.string
48 | };
49 |
50 | export default React.memo(FooterActionButton);
51 |
--------------------------------------------------------------------------------
/src/components/HeadingStep/index.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | // styles
5 | import './styles.scss';
6 |
7 | function HeadingStep({ number = null, text }) {
8 | return (
9 |
10 | {number &&
{`Step ${number}`}
}
11 |
12 |
13 |
14 | );
15 | }
16 |
17 | HeadingStep.propTypes = {
18 | // required
19 | text: PropTypes.string.isRequired,
20 |
21 | // optional
22 | number: PropTypes.number
23 | };
24 |
25 | export default React.memo(HeadingStep);
26 |
--------------------------------------------------------------------------------
/src/components/HeadingStep/styles.scss:
--------------------------------------------------------------------------------
1 | .heading-step {
2 | display: flex;
3 | margin-bottom: var(--spacing-sm);
4 |
5 | p {
6 | display: flex;
7 | flex: 1;
8 | font-size: 12px;
9 | }
10 | }
11 |
12 | .circle-step {
13 | align-items: center;
14 | background-color: var(--figma-color-bg-inverse);
15 | border-radius: 4px;
16 | color: var(--figma-color-text-oninverse);
17 | display: flex;
18 | font-size: 10px;
19 | font-weight: bold;
20 | height: 18px;
21 | justify-content: center;
22 | margin-right: var(--spacing-xs);
23 | padding: 0 6px;
24 | text-transform: uppercase;
25 | }
--------------------------------------------------------------------------------
/src/components/LoadingSpinner/index.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | // icons
5 | import { SvgLoadingSpinner } from '@/icons';
6 |
7 | // styles
8 | import './styles.scss';
9 |
10 | function LoadingSpinner({ fill = '#18a0fb', size = 20 }) {
11 | return (
12 |
17 |
18 |
19 | );
20 | }
21 |
22 | LoadingSpinner.propTypes = {
23 | // optional
24 | fill: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
25 | size: PropTypes.number
26 | };
27 |
28 | export default React.memo(LoadingSpinner);
29 |
--------------------------------------------------------------------------------
/src/components/LoadingSpinner/styles.scss:
--------------------------------------------------------------------------------
1 | .loading-spinner {
2 | align-items: center;
3 | animation: spin 600ms linear infinite;
4 | display: flex;
5 | height: 30px;
6 | justify-content: center;
7 | width: 30px;
8 | }
--------------------------------------------------------------------------------
/src/components/NavLeft/styles.scss:
--------------------------------------------------------------------------------
1 | nav {
2 | border-right: 1px solid var(--figma-color-border);
3 | overflow: hidden;
4 | position: relative;
5 | transition: width 500ms ease-in-out;
6 | width: 224px;
7 |
8 | .collapsed & {
9 | // overflow: hidden;
10 | width: 48px;
11 | }
12 |
13 | ul {
14 | list-style: none;
15 | margin: 0;
16 | padding: 40px 0 0;
17 |
18 | .collapsed & {
19 | padding-top: 8px;
20 | }
21 | }
22 |
23 | a {
24 | align-items: center;
25 | display: flex;
26 | height: 40px;
27 | padding: 8px 16px;
28 | text-decoration: none;
29 | transition: background-color 400ms ease, padding 600ms ease-in-out;
30 |
31 | &.active {
32 | background-color: #c5e5fb;
33 |
34 | .figma-dark & {
35 | background-color: #4f5c64;
36 | }
37 | }
38 | }
39 | }
40 |
41 | .container-flow-type {
42 | align-items: center;
43 | display: flex;
44 | height: 32px;
45 | padding: 12px 16px 0;
46 | position: absolute;
47 | width: 100%;
48 |
49 | .collapsed & {
50 | opacity: 0;
51 | position: relative;
52 | }
53 | }
54 |
55 | .flow-text {
56 | font-size: 11px;
57 | font-weight: 600;
58 | margin-left: 15px;
59 | text-transform: capitalize;
60 | }
61 |
62 | .step-marker {
63 | align-items: center;
64 | border: 1px solid var(--figma-color-text);
65 | border-radius: 8px;
66 | display: flex;
67 | justify-content: center;
68 | margin-right: 16px;
69 | min-height: 16px;
70 | min-width: 16px;
71 |
72 | svg {
73 | display: none;
74 | }
75 |
76 | .active & .step-inner {
77 | background-color: var(--figma-color-text);
78 | border-radius: 2px;
79 | height: 4px;
80 | width: 4px;
81 | }
82 |
83 | .completed & {
84 | background-color: var(--figma-color-text);
85 |
86 | .step-inner {
87 | display: none;
88 | }
89 |
90 | svg {
91 | display: block;
92 | }
93 | }
94 | }
95 |
96 | .toggle-nav {
97 | align-items: center;
98 | border-radius: 2px;
99 | cursor: pointer;
100 | display: flex;
101 | justify-content: center;
102 | padding: 6px;
103 | position: absolute;
104 | right: 6px;
105 | top: 9px;
106 | transition: right 500ms ease-in-out;
107 | width: 26px;
108 |
109 | .collapsed & {
110 | right: 9px;
111 | }
112 |
113 | .view-condensed & {
114 | top: 4px;
115 | }
116 | }
117 |
118 | .step-label {
119 | white-space: nowrap;
120 |
121 | .collapsed & {
122 | white-space: nowrap;
123 | }
124 | }
125 |
126 | .container-progress {
127 | align-items: center;
128 | display: flex;
129 | flex-direction: row;
130 | justify-content: space-between;
131 | overflow: hidden;
132 | padding: 0 var(--spacing-sm) calc(var(--spacing-sm) * 1.25) var(--spacing-sm);
133 | white-space: nowrap;
134 | width: 100%;
135 |
136 | .collapsed & {
137 | justify-content: center;
138 | }
139 | }
140 |
141 | .progress-encourage {
142 | .collapsed & {
143 | display: none;
144 | }
145 | }
--------------------------------------------------------------------------------
/src/components/ProgressLine.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | // icons
5 | import { SvgEmojiCelebrate, SvgEmojiMonocle } from '@/icons';
6 |
7 | const progressDisplay = {
8 | 'work-to-do': {
9 | animation: 'pulse',
10 | icon: ,
11 | msg: 'There is some a11y work to do'
12 | },
13 | 'green-100s': {
14 | animation: 'tada',
15 | icon: ,
16 | msg: 'All a11y checks are good!'
17 | }
18 | };
19 |
20 | function ProgressLine({ progressType }) {
21 | const { animation, icon, msg } = progressDisplay[progressType];
22 |
23 | return (
24 |
25 |
26 |
27 |
34 |
35 | );
36 | }
37 |
38 | ProgressLine.propTypes = {
39 | // required
40 | progressType: PropTypes.oneOf(['work-to-do', 'green-100s']).isRequired
41 | };
42 |
43 | export default React.memo(ProgressLine);
44 |
--------------------------------------------------------------------------------
/src/components/ProgressPieChart/index.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | // icons
5 | import { SvgCheck } from '@/icons';
6 |
7 | // styles
8 | import './styles.scss';
9 |
10 | function ProgressPieChart({ progress }) {
11 | const percentCss = {
12 | background: `conic-gradient(var(--figma-color-text) 0, var(--figma-color-text) ${progress}%, transparent 0, transparent 100%)`
13 | };
14 |
15 | return (
16 |
17 |
18 |
19 |
20 | {progress === 100 && (
21 |
22 |
23 |
24 | )}
25 |
26 |
27 |
{`${progress}%`}
28 |
29 | );
30 | }
31 |
32 | ProgressPieChart.propTypes = {
33 | // required
34 | progress: PropTypes.number.isRequired
35 | };
36 |
37 | export default React.memo(ProgressPieChart);
38 |
--------------------------------------------------------------------------------
/src/components/ProgressPieChart/styles.scss:
--------------------------------------------------------------------------------
1 | .progress-chart {
2 | border: 1px solid var(--figma-color-text);
3 | border-radius: 50%;
4 | margin-right: var(--spacing-xs);
5 | min-height: 16px;
6 | position: relative;
7 | width: 16px;
8 |
9 | .collapsed & {
10 | display: none;
11 | }
12 | }
13 |
14 | .progress {
15 | font-size: 12px;
16 | line-height: 16px;
17 | }
18 |
19 | .progress-check {
20 | left: 1px;
21 | position: absolute;
22 | top: 1px;
23 |
24 | .collapsed & {
25 | display: none;
26 | }
27 | }
--------------------------------------------------------------------------------
/src/components/Toggle/index.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { utils } from '@/constants';
4 |
5 | // styles
6 | import './styles.scss';
7 |
8 | function SlidingToggle({ checked = false, label, onChange }) {
9 | return (
10 | onChange(!checked)}
15 | onKeyDown={({ key }) => {
16 | if (utils.isEnterKey(key)) onChange(!checked);
17 | }}
18 | role="switch"
19 | tabIndex="0"
20 | >
21 |
24 |
25 | {label &&
{label}}
26 |
27 | );
28 | }
29 |
30 | SlidingToggle.propTypes = {
31 | // required
32 | onChange: PropTypes.func.isRequired,
33 |
34 | // optional
35 | checked: PropTypes.bool,
36 | label: PropTypes.string
37 | };
38 |
39 | export default React.memo(SlidingToggle);
40 |
--------------------------------------------------------------------------------
/src/components/Toggle/styles.scss:
--------------------------------------------------------------------------------
1 | .toggle-wrapper {
2 | align-items: center;
3 | display: flex;
4 | gap: 16px;
5 | width: fit-content;
6 | }
7 |
8 | .toggle-label {
9 | cursor: pointer;
10 | font-size: 14px;
11 | user-select: none;
12 | }
13 |
14 | .toggle-container {
15 | background: var(--figma-color-bg-disabled-secondary);
16 | border-radius: 6px;
17 | cursor: pointer;
18 | height: 12px;
19 | position: relative;
20 | transition: background 400ms ease-in-out;
21 | width: 24px;
22 |
23 | .toggle-slider {
24 | background: var(--figma-color-bg);
25 | border-radius: 5px;
26 | height: 10px;
27 | left: 1px;
28 | position: absolute;
29 | top: 50%;
30 | transform: translateY(-50%);
31 | transition: left 400ms ease-in-out;
32 | width: 10px;
33 | }
34 |
35 | &.checked {
36 | background: var(--figma-color-bg-inverse);
37 |
38 | .toggle-slider {
39 | left: 13px;
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | // components
2 | export { default as Alert } from './Alert';
3 | export { default as AltTextRow } from './AltTextRow';
4 | export { default as AnnotationStepPage } from './AnnotationStepPage';
5 | export { default as BannerAlert } from './BannerAlert';
6 | export { default as BannerSuccess } from './BannerSuccess';
7 | export { default as BannerTip } from './BannerTip';
8 | export { default as Checkbox } from './Checkbox';
9 | export { default as ContrastScreenshot } from './ContrastScreenshot';
10 | export { default as Dropdown } from './Dropdown';
11 | export { default as EmptyStepSelection } from './EmptyStepSelection';
12 | export { default as Footer } from './Footer';
13 | export { default as HeadingStep } from './HeadingStep';
14 | export { default as LoadingSpinner } from './LoadingSpinner';
15 | export { default as NavLeft } from './NavLeft';
16 | export { default as Toggle } from './Toggle';
17 |
--------------------------------------------------------------------------------
/src/constants/analytics.js:
--------------------------------------------------------------------------------
1 | const baseURL = process.env.ANALYTICS_URL;
2 |
3 | export const logEvent = async (data) => {
4 | const { isProd = false } = data;
5 | const { name = 'page_view', pageTitle = 'Stepper' } = data;
6 | const { currentUser = 'ANON', sessionId = 1 } = data;
7 |
8 | if (baseURL?.length > 0 && isProd) {
9 | try {
10 | // specify network access
11 | // https://www.figma.com/plugin-docs/making-network-requests/#specify-network-access
12 | const fullURL = `${baseURL}/${sessionId}/${currentUser}/${name}/${pageTitle}`;
13 | const response = await fetch(fullURL, { mode: 'no-cors' });
14 |
15 | if (!response.ok) {
16 | throw new Error(
17 | `This is an HTTP error: The status is ${response.status}`
18 | );
19 | }
20 | } catch (err) {
21 | // fail silently
22 | }
23 | } else {
24 | // uncomment to debug analytics logging
25 | // console.log(
26 | // 'analytics event log call',
27 | // `${baseURL}/${sessionId}/${currentUser}/${name}/${pageTitle}`
28 | // );
29 | }
30 | };
31 |
32 | export default { logEvent };
33 |
--------------------------------------------------------------------------------
/src/constants/colors.js:
--------------------------------------------------------------------------------
1 | export default {
2 | // #000000 || rgb(0, 0, 0)
3 | black: { r: 0, g: 0, b: 0 },
4 |
5 | // #c226de || rgb(194, 38, 222)
6 | purple: { r: 194 / 255, g: 38 / 255, b: 222 / 255 },
7 |
8 | // #7c00f9 || rgb(124, 0, 249)
9 | purpleDark: { r: 124 / 255, g: 0, b: 249 / 255 },
10 |
11 | // #f531b3 || rgb(245, 49, 179)
12 | pink: { r: 245 / 255, g: 49 / 255, b: 179 / 255 },
13 |
14 | // #0400d4 || rgb(4, 0, 212)
15 | blue: { r: 4 / 255, g: 0, b: 212 / 255 },
16 |
17 | // #009391 || rgba(0, 147, 145)
18 | deepTeal: { r: 0, g: 147 / 255, b: 145 / 255 },
19 |
20 | // #cecece || rgb(206, 206, 206)
21 | grey: { r: 206 / 255, g: 206 / 255, b: 206 / 255 },
22 |
23 | // #8f8f8f || rgb(143, 143, 143)
24 | coolGrey: { r: 143 / 255, g: 143 / 255, b: 143 / 255 },
25 |
26 | // #707070 || rgb(112, 112, 112)
27 | mediumGrey: { r: 112 / 255, g: 112 / 255, b: 112 / 255 },
28 |
29 | // #ffffff || rgb(255, 255, 255)
30 | white: { r: 1, g: 1, b: 1 }
31 | };
32 |
--------------------------------------------------------------------------------
/src/constants/index.js:
--------------------------------------------------------------------------------
1 | import analytics from './analytics';
2 | import colors from './colors';
3 | import contrast from './contrast';
4 | import figmaLayer from './figma-layer';
5 | import utils from './utilities';
6 |
7 | export { analytics, colors, contrast, figmaLayer, utils };
8 |
--------------------------------------------------------------------------------
/src/context/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default React.createContext({
4 | // global ui
5 | alertMsg: null,
6 | condensedUI: false,
7 | isLoading: true,
8 | loadingMsg: 'Scanning for Accessibility layers in Figma document',
9 | leftNavVisible: true,
10 |
11 | // page changes
12 | hasDashboard: false,
13 | showDashboard: false,
14 | showPageChange: false,
15 | showSettings: false,
16 |
17 | // global accessibility data
18 | pages: [],
19 | page: null,
20 | pageSelected: null,
21 | pageType: null,
22 | steps: [],
23 | stepsNative: [],
24 | stepsCompleted: [],
25 | stepsData: {},
26 | tipExpanded: true,
27 |
28 | // landmarks
29 | landmarks: {},
30 |
31 | // headings
32 | headings: {},
33 | headingTemp: null,
34 |
35 | // focus orders
36 | focusOrders: {},
37 |
38 | // alt text
39 | noImages: false,
40 | imagesData: [],
41 | imagesScanned: [],
42 |
43 | // contrast
44 | contrastResults: null,
45 |
46 | // focus grouping
47 | groups: [],
48 |
49 | // complex gestures
50 | gestures: {},
51 |
52 | // touch targets
53 | touchTargets: {},
54 |
55 | // color blindness
56 | colorBlindnessView: false,
57 |
58 | // user data
59 | currentUser: null,
60 | newFeaturesIntro: [],
61 | sessionId: 0
62 | });
63 |
--------------------------------------------------------------------------------
/src/data/color-blindness-types.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | // color blindness svgs
4 | import ColorBlindness from '@/icons/color-blindness';
5 |
6 | // setup color blindness options
7 | export default {
8 | deuteranomaly: {
9 | id: '2',
10 | desc: 'Deuteranomaly—reduced sensitivity to green trouble distinguishing reds & greens',
11 | icon: ,
12 | population: '5.3%',
13 | value: 'Deuteranomaly'
14 | },
15 | deuteranopia: {
16 | id: '3',
17 | desc: "Deuteranopia—can't see greens at all",
18 | icon: ,
19 | population: '1.2%',
20 | value: 'Deuteranopia'
21 | },
22 | protanomaly: {
23 | id: '4',
24 | desc: 'Protonomaly—reduced sensitivity to red trouble distinguishing reds & greens',
25 | icon: ,
26 | population: '1.3%',
27 | value: 'Protanomaly'
28 | },
29 | protanopia: {
30 | id: '5',
31 | desc: "Protanopia—can't see reds at all",
32 | icon: ,
33 | population: '1.5%',
34 | value: 'Protanopia'
35 | },
36 | tritanopia: {
37 | id: '6',
38 | desc: "Tritanopia—can't distinguish blues and greens, purples and reds, and yellows and pinks",
39 | icon: ,
40 | population: '0.03%',
41 | value: 'Tritanopia'
42 | },
43 | achromatomaly: {
44 | id: '7',
45 | desc: 'Achromatomaly—sees the absence of most colors',
46 | icon: ,
47 | population: '0.09%',
48 | value: 'Achromatomaly'
49 | },
50 | achromatopsia: {
51 | id: '8',
52 | desc: 'Achromatopsia—full color blindness, can only see shades',
53 | icon: ,
54 | population: '0.05%',
55 | value: 'Achromatopsia'
56 | },
57 | normal: {
58 | id: '1',
59 | desc: 'Trichromatic—can distinguish all the primary colours',
60 | icon: ,
61 | population: '68%',
62 | value: 'Tritanomaly'
63 | }
64 | };
65 |
--------------------------------------------------------------------------------
/src/data/dropdown-heading-types.json:
--------------------------------------------------------------------------------
1 | [
2 | { "id": "1", "value": "h1" },
3 | { "id": "2", "value": "h2" },
4 | { "id": "3", "value": "h3" },
5 | { "id": "4", "value": "h4" },
6 | { "id": "5", "value": "h5" },
7 | { "id": "6", "value": "h6" }
8 | ]
9 |
--------------------------------------------------------------------------------
/src/data/dropdown-image-types.json:
--------------------------------------------------------------------------------
1 | [
2 | { "id": "1", "value": "informative" },
3 | { "id": "2", "value": "decorative" }
4 | ]
5 |
--------------------------------------------------------------------------------
/src/data/focus-order-types.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | // focus order icons
4 | import FocusOrderIcons from '@/icons/focus-order';
5 |
6 | // setup focus order button options
7 | export default {
8 | tabs: {
9 | icon: ,
10 | id: 'tabs',
11 | label: 'tab stops'
12 | },
13 | arrows: {
14 | icon: ,
15 | id: 'arrows',
16 | label: 'arrow keys'
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/src/data/gesture-types.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | // svg gestures
4 | import SvgGestures from '@/icons/complex-gestures';
5 |
6 | // setup gesture options
7 | export default {
8 | swipe: {
9 | id: '1',
10 | label: 'swipe/slide/drag',
11 | icon:
12 | },
13 | 'drag-and-drop': {
14 | id: '2',
15 | icon: ,
16 | label: 'drag & drop'
17 | },
18 | 'pinch-and-zoom': {
19 | id: '3',
20 | label: 'pinch & zoom',
21 | icon:
22 | },
23 | rotate: {
24 | id: '4',
25 | label: 'rotate',
26 | icon:
27 | },
28 | 'multi-finger': {
29 | id: '5',
30 | label: 'multi-finger tap',
31 | icon:
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/src/data/heading-types-native.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | // svg headings
4 | import SvgHeadings from '@/icons/headings';
5 |
6 | // setup native heading options
7 | export default {
8 | 2: {
9 | icon: ,
10 | id: '2',
11 | value: 'h2',
12 | label: ''
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/src/data/heading-types.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | // svg headings
4 | import SvgHeadings from '@/icons/headings';
5 |
6 | // setup heading options
7 | export default {
8 | 1: {
9 | icon: ,
10 | id: '1',
11 | value: 'h1',
12 | label: 'level 1'
13 | },
14 | 2: {
15 | icon: ,
16 | id: '2',
17 | value: 'h2',
18 | label: 'level 2'
19 | },
20 | 3: {
21 | icon: ,
22 | id: '3',
23 | value: 'h3',
24 | label: 'level 3'
25 | },
26 | 4: {
27 | icon: ,
28 | id: '4',
29 | value: 'h4',
30 | label: 'level 4'
31 | },
32 | 5: {
33 | icon: ,
34 | id: '5',
35 | value: 'h5',
36 | label: 'level 5'
37 | },
38 | 6: {
39 | icon: ,
40 | id: '6',
41 | value: 'h6',
42 | label: 'level 6'
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/src/data/landmark-types.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | // svg landmarks
4 | import SvgLandmarks from '@/icons/landmarks';
5 |
6 | // setup landmark options
7 | export default {
8 | header: {
9 | icon: ,
10 | label: ''
11 | },
12 | search: {
13 | icon: ,
14 | label: 'search'
15 | },
16 | nav: {
17 | icon: ,
18 | label: '