Skip to content

[WC-2488] Checkbox Radio Selection widget #1757

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
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require("@mendix/prettier-config-web-widgets");
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

- Initial release of Checkbox Radio Selection widget
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Checkbox Radio Selection

A widget for displaying radio button lists (single selection) and checkbox lists (multiple selection) based on different data sources.

## Features

- **Single Selection**: Radio button list for exclusive selection
- **Multiple Selection**: Checkbox list for multiple selection
- **Data Sources**: Support for context (association), database, and static data
- **Custom Content**: Ability to add custom content for options
- **Accessibility**: Full accessibility support with ARIA labels and keyboard navigation

## Configuration

The widget supports various data source types:

- **Context**: Use associations from your entity
- **Database**: Query database for selectable objects
- **Static**: Define static values directly in the widget

## Usage

1. Add the Checkbox Radio Selection widget to your page
2. Configure the data source (Context, Database, or Static)
3. Set up caption and value attributes
4. Configure selection method (single or multiple)
5. Customize styling and accessibility options

For detailed configuration options, please refer to the widget properties in Studio Pro.

## Browser Support

- Modern browsers supporting ES6+
- Internet Explorer 11+ (with polyfills)

## Development

This widget is built using:

- React 18+
- TypeScript
- Mendix Pluggable Widgets API
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
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.describe("selection-controls-web", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/p/selectioncontrols");
await page.waitForLoadState("networkidle");
});

test.describe("data source types", () => {
test("renders selection controls using association", async ({ page }) => {
const selectionControls = page.locator(".mx-name-selectionControls1");
await expect(selectionControls).toBeVisible({ timeout: 10000 });
await expect(selectionControls).toHaveScreenshot(`selectionControlsAssociation.png`);
});

test("renders selection controls using enum", async ({ page }) => {
const selectionControls = page.locator(".mx-name-selectionControls2");
await expect(selectionControls).toBeVisible({ timeout: 10000 });
await expect(selectionControls).toHaveScreenshot(`selectionControlsEnum.png`);
});

test("renders selection controls using boolean", async ({ page }) => {
const selectionControls = page.locator(".mx-name-selectionControls3");
await expect(selectionControls).toBeVisible({ timeout: 10000 });
await expect(selectionControls).toHaveScreenshot(`selectionControlsBoolean.png`);
});

test("renders selection controls using static values", async ({ page }) => {
const selectionControls = page.locator(".mx-name-selectionControls4");
await expect(selectionControls).toBeVisible({ timeout: 10000 });
await expect(selectionControls).toHaveScreenshot(`selectionControlsStatic.png`);
});

test("renders selection controls using database", async ({ page }) => {
const selectionControls = page.locator(".mx-name-selectionControls5");
await expect(selectionControls).toBeVisible({ timeout: 10000 });
await expect(selectionControls).toHaveScreenshot(`selectionControlsDatabase.png`);
});

test.describe("selection behavior", () => {
test("handles radio button selection", async ({ page }) => {
const selectionControls = page.locator(".mx-name-selectionControls1");
await expect(selectionControls).toBeVisible({ timeout: 10000 });

const radioOption = selectionControls.locator('input[type="radio"]').first();
await radioOption.click();
await expect(radioOption).toBeChecked();
});

test("handles checkbox selection", async ({ page }) => {
const selectionControls = page.locator(".mx-name-selectionControls6"); // multi selection
await expect(selectionControls).toBeVisible({ timeout: 10000 });

const checkboxOption = selectionControls.locator('input[type="checkbox"]').first();
await checkboxOption.click();
await expect(checkboxOption).toBeChecked();
});
});
});
});
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;
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"name": "@mendix/checkbox-radio-selection-web",
"widgetName": "CheckboxRadioSelection",
"version": "1.0.0",
"description": "Configurable radio buttons and check box widget",
"copyright": "© Mendix Technology BV 2025. All rights reserved.",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/mendix/web-widgets.git"
},
"config": {
"developmentPort": 3000,
"mendixHost": "http://localhost:8080"
},
"mxpackage": {
"name": "CheckboxRadioSelection",
"type": "widget",
"mpkName": "com.mendix.widget.web.CheckboxRadioSelection.mpk"
},
"packagePath": "com.mendix.widget.web",
"marketplace": {
"minimumMXVersion": "10.7.0",
"appNumber": 219304,
"appName": "Checkbox Radio Selection",
"reactReady": true
},
"testProject": {
"githubUrl": "https://github.com/mendix/testProjects",
"branchName": "checkbox-radio-selection-web"
},
"scripts": {
"prebuild": "rui-create-translation",
"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": "run-e2e ci",
"e2edev": "run-e2e dev --with-preps",
"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": "pluggable-widgets-tools test:unit:web:enzyme-free",
"update-changelog": "rui-update-changelog-widget",
"verify": "rui-verify-package-format"
},
"dependencies": {
"classnames": "^2.3.2"
},
"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-component-kit": "workspace:*",
"@mendix/widget-plugin-grid": "workspace:*",
"@mendix/widget-plugin-hooks": "workspace:*",
"@mendix/widget-plugin-platform": "workspace:*",
"@mendix/widget-plugin-test-utils": "workspace:*",
"cross-env": "^7.0.3"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require("@mendix/run-e2e/playwright.config.cjs");
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const { join } = require("path");
const { cp, mkdir, rm } = require("shelljs");

const sourcePath = process.cwd();
const outDir = join(sourcePath, "/dist/tmp/widgets/");

module.exports = args => {
const result = args.configDefaultConfig;

const localesDir = join(outDir, "locales/");
mkdir("-p", localesDir);

const translationFiles = join(sourcePath, "dist/locales/**/*");
// copy everything under dist/locales to dist/tmp/widgets/locales for the widget mpk
cp("-r", translationFiles, localesDir);
// remove root level *.json locales files (duplicate with language specific files (e.g. en-US/*.json))
rm("-f", join(outDir, "locales/*.json"), localesDir);

return result;
};
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading