-
Notifications
You must be signed in to change notification settings - Fork 63
[WC-2946] Initial setup of skiplink widget #1764
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
HedwigAR
wants to merge
27
commits into
main
Choose a base branch
from
wc-2946-new-skiplink-widget
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 23 commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
d71607f
feat: initial setup of skiplink widget
HedwigJDoets 93c74e8
feat(skiplink-web): adding configuration
HedwigJDoets fa2060f
chore: improve config files, ran linting
HedwigJDoets 6815e12
fix: small improvements
HedwigJDoets 52c763c
fix: rewrite tests for current version of the widget
HedwigJDoets 74d766d
fix: add changes in pnpm lock file
HedwigJDoets bef09bd
chore: update readme
HedwigJDoets 95005b6
fix: fix lint issues
HedwigJDoets ef5e035
chore: add testproject info to package json
HedwigJDoets 7ec99cd
fix: lint error on new testproject
HedwigJDoets fd33e2a
chore: add changelog file
HedwigJDoets 8230160
fix: fix changelog
HedwigJDoets 574c551
fix: add whitespace to changelog
HedwigJDoets 74ebca9
fix: add added header to changelog
HedwigJDoets 0ee3595
fix: remove react import
HedwigJDoets 5958650
chore: add specific mendix version to e2e, add not required to xml
HedwigJDoets 01492c8
test: fix Docker mxbuild for ARM arch and skip the atlas theme copy
leonardomendix 73b48d0
feat: add e2e test
HedwigJDoets de74789
fix: e2e tests
HedwigJDoets d476c9b
fix: add screenshot for screenshot test
HedwigJDoets 29de627
fix: rename snaphot
HedwigJDoets d72d9d3
fix: remove screenshot
HedwigJDoets 9f1fd25
fix: readd screenshot
HedwigJDoets a9450a1
fix: refactor based on review comments
HedwigJDoets bb34e80
fix: apply lint fixes
HedwigJDoets dafc52d
fix: rename css class, fix click event type
HedwigJDoets ba4ff10
fix: implement review comments
HedwigJDoets File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| /tests/TestProjects/**/.classpath | ||
| /tests/TestProjects/**/.project | ||
| /tests/TestProjects/**/javascriptsource | ||
| /tests/TestProjects/**/javasource | ||
| /tests/TestProjects/**/resources | ||
| /tests/TestProjects/**/userlib | ||
|
|
||
| /tests/TestProjects/Mendix8/theme/styles/native | ||
| /tests/TestProjects/Mendix8/theme/styles/web/sass | ||
| /tests/TestProjects/Mendix8/theme/*.* | ||
| !/tests/TestProjects/Mendix8/theme/components.json | ||
| !/tests/TestProjects/Mendix8/theme/favicon.ico | ||
| !/tests/TestProjects/Mendix8/theme/LICENSE | ||
| !/tests/TestProjects/Mendix8/theme/settings.json |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| module.exports = require("@mendix/prettier-config-web-widgets"); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| # Changelog | ||
|
|
||
| All notable changes to this widget will be documented in this file. | ||
|
|
||
| The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||
|
|
||
| ## [Unreleased] | ||
|
|
||
| ### Added | ||
|
|
||
| - Created skiplink widget. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # Skip Link | ||
|
|
||
| Adds a skip navigation link for keyboard accessibility. The link is hidden until focused and allows users to jump directly to the main content. | ||
|
|
||
| ## Usage | ||
|
|
||
| 1. Add the Skip Link widget anywhere on your page, preferrably at the top or in a layout. | ||
| 2. Configure the **Link Text** and **Main Content ID** properties. | ||
| 3. Ensure your main content element has the specified ID, or there's a main tag on the page. | ||
|
|
||
| The widget automatically inserts the skip link as the first child of the `#root` element. | ||
|
|
||
| ## Properties | ||
|
|
||
| - **Link Text**: Text displayed for the skip link (default: "Skip to main content"). | ||
| - **Main Content ID**: ID of the main content element to focus (optional). | ||
|
|
||
| If the target element is not found, the widget will focus the first `<main>` element instead. | ||
|
|
||
| ## Accessibility | ||
|
|
||
| The skip link is positioned absolutely at the top-left of the page, hidden by default with `transform: translateY(-120%)`, and becomes visible when focused via keyboard navigation. |
77 changes: 77 additions & 0 deletions
77
packages/pluggableWidgets/skiplink-web/e2e/SkipLink.spec.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| import { test, expect } from "@playwright/test"; | ||
|
|
||
| test.afterEach("Cleanup session", async ({ page }) => { | ||
| // Because the test isolation that will open a new session for every test executed, and that exceeds Mendix's license limit of 5 sessions, so we need to force logout after each test. | ||
| await page.evaluate(() => window.mx.session.logout()); | ||
| }); | ||
|
|
||
| test.beforeEach(async ({ page }) => { | ||
| await page.goto("/"); | ||
| await page.waitForLoadState("networkidle"); | ||
| }); | ||
|
|
||
| test.describe("SkipLink:", function () { | ||
| test("skip link is present in DOM but initially hidden", async ({ page }) => { | ||
| // Skip link should be in the DOM but not visible | ||
| const skipLink = page.locator(".skip-link").first(); | ||
| await expect(skipLink).toBeAttached(); | ||
|
|
||
| // Check initial styling (hidden) | ||
| const transform = await skipLink.evaluate(el => getComputedStyle(el).transform); | ||
| expect(transform).toContain("matrix(1, 0, 0, 1, 0, -48)"); | ||
| }); | ||
|
|
||
| test("skip link becomes visible when focused via keyboard", async ({ page }) => { | ||
| // Tab to focus the skip link (should be first focusable element) | ||
| const skipLink = page.locator(".skip-link").first(); | ||
| await page.keyboard.press("Tab"); | ||
|
|
||
| await expect(skipLink).toBeFocused(); | ||
| await page.waitForTimeout(1000); | ||
| // Check that it becomes visible when focused | ||
| const transform = await skipLink.evaluate(el => getComputedStyle(el).transform); | ||
| expect(transform).toContain("matrix(1, 0, 0, 1, 0, 0)") | ||
| }); | ||
|
|
||
| test("skip link navigates to main content when activated", async ({ page }) => { | ||
| // Tab to focus the skip link | ||
| await page.keyboard.press("Tab"); | ||
|
|
||
| const skipLink = page.locator(".skip-link").first(); | ||
| await expect(skipLink).toBeFocused(); | ||
|
|
||
| // Activate the skip link | ||
| await page.keyboard.press("Enter"); | ||
|
|
||
| // Check that main content is now focused | ||
| const mainContent = page.locator("main"); | ||
| await expect(mainContent).toBeFocused(); | ||
| }); | ||
|
|
||
| test("skip link has correct attributes and text", async ({ page }) => { | ||
| const skipLink = page.locator(".skip-link").first(); | ||
|
|
||
| // Check default text | ||
| await expect(skipLink).toHaveText("Skip to main content"); | ||
|
|
||
| // Check href attribute | ||
| await expect(skipLink).toHaveAttribute("href", "#"); | ||
|
|
||
| // Check tabindex | ||
| await expect(skipLink).toHaveAttribute("tabindex", "0"); | ||
|
|
||
| // Check CSS class | ||
| await expect(skipLink).toHaveClass("skip-link"); | ||
| }); | ||
|
|
||
| test("visual comparison", async ({ page }) => { | ||
| // Tab to make skip link visible for screenshot | ||
| await page.keyboard.press("Tab"); | ||
|
|
||
| const skipLink = page.locator(".skip-link").first(); | ||
| await expect(skipLink).toBeFocused(); | ||
|
|
||
| // Visual comparison of focused skip link | ||
| await expect(skipLink).toHaveScreenshot("skiplink-focused.png"); | ||
| }); | ||
| }); |
Binary file added
BIN
+2.51 KB
...skiplink-web/e2e/SkipLink.spec.js-snapshots/skiplink-focused-chromium-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| import config from "@mendix/eslint-config-web-widgets/widget-ts.mjs"; | ||
|
|
||
| export default config; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| module.exports = { | ||
| ...require("@mendix/pluggable-widgets-tools/test-config/jest.enzyme-free.config.js") | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| { | ||
| "name": "@mendix/skiplink-web", | ||
| "widgetName": "SkipLink", | ||
| "version": "1.0.0", | ||
| "description": "Adds a skip link to the top of the page for accessibility.", | ||
| "copyright": "© Mendix Technology BV 2025. All rights reserved.", | ||
| "license": "Apache-2.0", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/mendix/web-widgets.git" | ||
| }, | ||
| "config": {}, | ||
| "mxpackage": { | ||
| "name": "SkipLink", | ||
| "type": "widget", | ||
| "mpkName": "com.mendix.widget.web.SkipLink.mpk" | ||
| }, | ||
| "packagePath": "com.mendix.widget.web", | ||
| "marketplace": { | ||
| "minimumMXVersion": "11.1.0", | ||
| "appNumber": 119999, | ||
| "appName": "SkipLink", | ||
| "reactReady": true | ||
| }, | ||
| "testProject": { | ||
| "githubUrl": "https://github.com/mendix/testProjects", | ||
| "branchName": "skiplink-web" | ||
| }, | ||
| "scripts": { | ||
| "build": "pluggable-widgets-tools build:web", | ||
| "create-gh-release": "rui-create-gh-release", | ||
| "create-translation": "rui-create-translation", | ||
| "dev": "pluggable-widgets-tools start:web", | ||
| "e2e": "MENDIX_VERSION=11.1.0.75979 run-e2e ci --no-update-project", | ||
| "e2edev": "MENDIX_VERSION=11.1.0.75979 run-e2e dev --with-preps --no-update-project", | ||
| "format": "prettier --ignore-path ./node_modules/@mendix/prettier-config-web-widgets/global-prettierignore --write .", | ||
| "lint": "eslint src/ package.json", | ||
| "publish-marketplace": "rui-publish-marketplace", | ||
| "release": "pluggable-widgets-tools release:web", | ||
| "start": "pluggable-widgets-tools start:server", | ||
| "test": "jest --projects jest.config.js", | ||
| "update-changelog": "rui-update-changelog-widget", | ||
| "verify": "rui-verify-package-format" | ||
| }, | ||
| "dependencies": { | ||
| "@floating-ui/react": "^0.26.27", | ||
| "@mendix/widget-plugin-component-kit": "workspace:*", | ||
| "classnames": "^2.5.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@mendix/automation-utils": "workspace:*", | ||
| "@mendix/eslint-config-web-widgets": "workspace:*", | ||
| "@mendix/pluggable-widgets-tools": "*", | ||
| "@mendix/prettier-config-web-widgets": "workspace:*", | ||
| "@mendix/run-e2e": "workspace:*", | ||
| "@mendix/widget-plugin-hooks": "workspace:*", | ||
| "@mendix/widget-plugin-platform": "workspace:*", | ||
| "@mendix/widget-plugin-test-utils": "workspace:*" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| module.exports = require("@mendix/run-e2e/playwright.config.cjs"); |
74 changes: 74 additions & 0 deletions
74
packages/pluggableWidgets/skiplink-web/src/SkipLink.editorConfig.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| import { Problem, Properties } from "@mendix/pluggable-widgets-tools"; | ||
| import { | ||
| ContainerProps, | ||
| RowLayoutProps, | ||
| structurePreviewPalette, | ||
| StructurePreviewProps, | ||
| TextProps | ||
| } from "@mendix/widget-plugin-platform/preview/structure-preview-api"; | ||
|
|
||
| export function getProperties(defaultValues: Properties): Properties { | ||
| // No conditional properties for skiplink, but function provided for consistency | ||
| return defaultValues; | ||
| } | ||
|
|
||
| export function check(values: any): Problem[] { | ||
| const errors: Problem[] = []; | ||
| if (!values.linkText) { | ||
| errors.push({ | ||
| property: "linkText", | ||
| message: "Link text is required" | ||
| }); | ||
| } | ||
| return errors; | ||
| } | ||
|
|
||
| export function getPreview(values: any, isDarkMode: boolean): StructurePreviewProps | null { | ||
| const palette = structurePreviewPalette[isDarkMode ? "dark" : "light"]; | ||
| const titleHeader: RowLayoutProps = { | ||
| type: "RowLayout", | ||
| columnSize: "grow", | ||
| backgroundColor: palette.background.topbarStandard, | ||
| borders: true, | ||
| borderWidth: 1, | ||
| children: [ | ||
| { | ||
| type: "Container", | ||
| padding: 4, | ||
| children: [ | ||
| { | ||
| type: "Text", | ||
| content: "SkipLink", | ||
| fontColor: palette.text.secondary | ||
| } as TextProps | ||
| ] | ||
| } | ||
| ] | ||
| }; | ||
| const linkContent: RowLayoutProps = { | ||
| type: "RowLayout", | ||
| columnSize: "grow", | ||
| borders: true, | ||
| padding: 0, | ||
| children: [ | ||
| { | ||
| type: "Container", | ||
| padding: 6, | ||
| children: [ | ||
| { | ||
| type: "Text", | ||
| content: values.linkText || "Skip to main content", | ||
| fontSize: 14, | ||
| fontColor: palette.text.primary, | ||
| bold: true | ||
| } as TextProps | ||
| ] | ||
| } | ||
| ] | ||
| }; | ||
| return { | ||
| type: "Container", | ||
| borders: true, | ||
| children: [titleHeader, linkContent] | ||
| } as ContainerProps; | ||
| } |
54 changes: 54 additions & 0 deletions
54
packages/pluggableWidgets/skiplink-web/src/SkipLink.editorPreview.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import { ReactElement } from "react"; | ||
| import { SkipLinkPreviewProps } from "../typings/SkipLinkProps"; | ||
|
|
||
| export const preview = (props: SkipLinkPreviewProps): ReactElement => { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want user to style this live in preview?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure, this is the first editorPreview I've made. So if there's improvements I'm all ears. |
||
| if (props.renderMode === "xray") { | ||
| return ( | ||
| <div style={{ position: "relative", height: 40 }}> | ||
| <a | ||
| href={`#${props.mainContentId}`} | ||
| style={{ | ||
| position: "absolute", | ||
| top: 0, | ||
| left: 0, | ||
| background: "#fff", | ||
| color: "#0078d4", | ||
| padding: "8px 16px", | ||
| zIndex: 1000, | ||
| textDecoration: "none", | ||
| border: "2px solid #0078d4", | ||
| borderRadius: 4, | ||
| fontWeight: "bold" | ||
| }} | ||
| > | ||
| {props.linkText} | ||
| </a> | ||
| </div> | ||
| ); | ||
| } else { | ||
| return ( | ||
| <a | ||
| href={`#${props.mainContentId}`} | ||
| style={{ | ||
| position: "absolute", | ||
| top: 0, | ||
| left: 0, | ||
| background: "#fff", | ||
| color: "#0078d4", | ||
| padding: "8px 16px", | ||
| zIndex: 1000, | ||
| textDecoration: "none", | ||
| border: "2px solid #0078d4", | ||
| borderRadius: 4, | ||
| fontWeight: "bold" | ||
| }} | ||
| > | ||
| {props.linkText} | ||
| </a> | ||
| ); | ||
| } | ||
| }; | ||
|
|
||
| export function getPreviewCss(): string { | ||
| return require("./ui/SkipLink.scss"); | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.