diff --git a/package.json b/package.json index 2e039a4ed7..00f849e067 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,8 @@ "@codemirror/view": "^6.34.2", "enzyme>cheerio": "1.0.0-rc.10", "ts-node": "10.9.2", - "react-big-calendar@1>clsx": "2.1.0" + "react-big-calendar@1>clsx": "2.1.0", + "mendix": "^10.16" }, "patchedDependencies": { "mobx@6.12.3": "patches/mobx@6.12.3.patch", diff --git a/packages/modules/data-widgets/src/themesource/datawidgets/web/_datagrid-dropdown-filter.scss b/packages/modules/data-widgets/src/themesource/datawidgets/web/_datagrid-dropdown-filter.scss index b77663d6cc..affc9a7cd0 100644 --- a/packages/modules/data-widgets/src/themesource/datawidgets/web/_datagrid-dropdown-filter.scss +++ b/packages/modules/data-widgets/src/themesource/datawidgets/web/_datagrid-dropdown-filter.scss @@ -1,6 +1,6 @@ @mixin scroll-shadow { background: - /* Shadow Cover TOP */ + /* Shadow Cover TOP */ linear-gradient(white 30%, rgba(255, 255, 255, 0)) center top, /* Shadow Cover BOTTOM */ linear-gradient(rgba(255, 255, 255, 0), white 70%) center bottom, /* Shadow TOP */ linear-gradient(0deg, rgba(255, 255, 255, 0.6), rgba(197, 197, 197, 0.6)) center top, @@ -138,7 +138,6 @@ $root: ".widget-dropdown-filter"; &-clear { @include btn-with-cross; align-items: center; - align-self: center; display: flex; flex-shrink: 0; justify-self: end; @@ -150,6 +149,11 @@ $root: ".widget-dropdown-filter"; &:has(+ #{$root}-toggle) { border-inline-end: 1px solid var(--gray, #787d87); } + + &:focus-visible { + outline-offset: -2px; + outline: var(--brand-primary, $brand-primary) solid 1px; + } } &-state-icon { @@ -262,9 +266,13 @@ $root: ".widget-dropdown-filter"; justify-content: center; line-height: 1.334; padding: var(--wdf-tag-padding); + margin: var(--spacing-smallest, 2px); &:focus-visible { outline: var(--brand-primary, #264ae5) auto 1px; } + &:focus { + background-color: var(--color-primary-light, $color-primary-light); + } } #{$root}-input { @@ -273,6 +281,14 @@ $root: ".widget-dropdown-filter"; width: initial; } + &:not(:focus-within):not([data-empty]) { + #{$root}-input { + opacity: 0; + flex-shrink: 1; + min-width: 1px; + } + } + #{$root}-clear { border-color: transparent; } diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/CHANGELOG.md b/packages/pluggableWidgets/datagrid-dropdown-filter-web/CHANGELOG.md index f3a4b1743f..28c7355e63 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/CHANGELOG.md +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/CHANGELOG.md @@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Fixed + +- We enhanced dropdown widget customization by separating texts for empty selection, empty option caption, and input placeholder. This change allows for more granular control over the widget's text configurations. + +- We improved dropdown widget usability with enhanced keyboard navigation, visual feedback, and interaction behavior. + +### Breaking changes + +- Text configurations for empty option, empty selection, and input placeholder need to be reviewed and reconfigured. + ## [2.10.1] - 2025-04-16 ### Fixed diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/package.json b/packages/pluggableWidgets/datagrid-dropdown-filter-web/package.json index 055788f2fe..726b5f811c 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/package.json +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/package.json @@ -1,7 +1,7 @@ { "name": "@mendix/datagrid-dropdown-filter-web", "widgetName": "DatagridDropdownFilter", - "version": "2.10.1", + "version": "2.27.0", "description": "", "copyright": "© Mendix Technology BV 2025. All rights reserved.", "license": "Apache-2.0", diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorConfig.ts b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorConfig.ts index cd2dbe6373..5194e4211a 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorConfig.ts +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorConfig.ts @@ -20,6 +20,8 @@ export function getProperties(values: DatagridDropdownFilterPreviewProps, defaul if (values.filterable) { hidePropertyIn(defaultProperties, values, "clearable"); hidePropertyIn(defaultProperties, values, "emptyOptionCaption"); + } else { + hidePropertyIn(defaultProperties, values, "filterInputPlaceholderCaption"); } if (!showSelectedItemsStyle) { @@ -54,7 +56,7 @@ export const getPreview = (values: DatagridDropdownFilterPreviewProps, isDarkMod text({ fontColor: palette.text.secondary, italic: true - })(values.emptyOptionCaption || " ") + })(values.emptySelectionCaption || " ") ], grow: 1 } as ContainerProps, diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorPreview.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorPreview.tsx index 8aff061f29..7b3ebeeac0 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorPreview.tsx +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorPreview.tsx @@ -1,19 +1,21 @@ import { enableStaticRendering } from "mobx-react-lite"; -enableStaticRendering(true); - import { createElement, ReactElement } from "react"; import { DatagridDropdownFilterPreviewProps } from "../typings/DatagridDropdownFilterProps"; import { parseStyle } from "@mendix/widget-plugin-platform/preview/parse-style"; -import { Select_inner } from "@mendix/widget-plugin-filtering/controls/select/Select"; +import { Select } from "@mendix/widget-plugin-filtering/controls/select/Select"; + +enableStaticRendering(true); function Preview(props: DatagridDropdownFilterPreviewProps): ReactElement { return ( - ({ items: [] })} @@ -25,11 +27,7 @@ const noop = (): void => {}; function getPreviewValue(props: DatagridDropdownFilterPreviewProps): string { let value = props.defaultValue; - if (!props.filterable) { - value ||= props.emptyOptionCaption || "Select"; - } else { - value ||= "Search"; - } + value ||= props.emptySelectionCaption || (props.filterable ? "Search" : "Select"); return value; } diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.tsx index 5435f96422..845b0060c2 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.tsx +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.tsx @@ -7,7 +7,7 @@ import { RefFilterContainer } from "./components/RefFilterContainer"; function Container(props: DatagridDropdownFilterContainerProps & Select_FilterAPIv2): React.ReactElement { const commonProps = { - ariaLabel: props.ariaLabel?.value, + ariaLabel: props.ariaLabel?.value ?? "", className: props.class, tabIndex: props.tabIndex, styles: props.style, @@ -16,7 +16,9 @@ function Container(props: DatagridDropdownFilterContainerProps & Select_FilterAP parentChannelName: props.parentChannelName, name: props.name, multiselect: props.multiSelect, - emptyCaption: props.emptyOptionCaption?.value, + emptyOptionCaption: props.emptyOptionCaption?.value ?? "", + emptySelectionCaption: props.emptySelectionCaption?.value ?? "", + placeholder: props.filterInputPlaceholderCaption?.value ?? "", defaultValue: props.defaultValue?.value, filterable: props.filterable, selectionMethod: props.selectionMethod, diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.xml b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.xml index 5e32a4a329..11afe3ce8e 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.xml +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.xml @@ -45,6 +45,10 @@ Empty option caption + + None + Niets + Clearable @@ -83,13 +87,32 @@ - + Input caption Assistive technology will read this upon reaching the input element. + + + Empty selection caption + This text is shown if no options are selected. For example 'Select color' or 'No options are selected'. + + Select + Selecteer + + + + + Filter input placeholder + This text is shown as placeholder for filterable filters. For example 'Type to search'. + + Search + Zoeken + + + diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/RefFilterContainer.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/RefFilterContainer.tsx index 5b332e833e..a259a73540 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/RefFilterContainer.tsx +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/RefFilterContainer.tsx @@ -15,10 +15,12 @@ import { SelectedItemsStyleEnum, SelectionMethodEnum } from "../../typings/Datag import { useOnScrollBottom } from "@mendix/widget-plugin-hooks/useOnScrollBottom"; export interface RefFilterContainerProps { - ariaLabel?: string; + ariaLabel: string; className?: string; defaultValue?: string; - emptyCaption?: string; + emptyOptionCaption: string; + emptySelectionCaption: string; + placeholder: string; filterStore: RefFilterStore; multiselect: boolean; name: string; @@ -66,6 +68,7 @@ const SelectWidget = observer(function SelectWidget(props: RefFilterContainerPro showCheckboxes={ctrl1.multiselect} className={props.className} style={props.styles} + ariaLabel={props.ariaLabel} /> ); }); @@ -80,6 +83,8 @@ const ComboboxWidget = observer(function ComboboxWidget(props: RefFilterContaine ); }); @@ -81,6 +84,8 @@ const ComboboxWidget = observer(function ComboboxWidget(props: StaticFilterConta { /> ); - expect(screen.getByRole("combobox")).toHaveAccessibleName("enum_value_1"); + expect(document.querySelector(".widget-dropdown-filter-toggle")).toHaveTextContent("enum_value_1"); }); it("don't sync defaultValue with state when defaultValue changes from undefined to string", async () => { @@ -132,7 +135,7 @@ describe("Dropdown Filter", () => { ); await waitFor(() => { - expect(screen.getByRole("combobox")).toHaveAccessibleName("Select"); + expect(document.querySelector(".widget-dropdown-filter-toggle")).toHaveTextContent("Select"); }); // “Real” context causes widgets to re-renders multiple times, replicate this in mocked context. @@ -156,7 +159,7 @@ describe("Dropdown Filter", () => { ); await waitFor(() => { - expect(screen.getByRole("combobox")).toHaveAccessibleName("Select"); + expect(document.querySelector(".widget-dropdown-filter-toggle")).toHaveTextContent("Select"); }); }); @@ -172,7 +175,7 @@ describe("Dropdown Filter", () => { /> ); - expect(screen.getByRole("combobox")).toHaveAccessibleName("xyz"); + expect(screen.getByText("xyz", { selector: ".widget-dropdown-filter-toggle" })).toBeInTheDocument(); // “Real” context causes widgets to re-renders multiple times, replicate this in mocked context. rerender( @@ -195,7 +198,9 @@ describe("Dropdown Filter", () => { ); await waitFor(() => { - expect(screen.getByRole("combobox")).toHaveAccessibleName("xyz"); + expect( + screen.getByText("xyz", { selector: ".widget-dropdown-filter-toggle" }) + ).toBeInTheDocument(); }); }); }); @@ -471,8 +476,8 @@ describe("Dropdown Filter", () => { ); - expect(fragment1().querySelector("button")?.getAttribute("aria-controls")).not.toBe( - fragment2().querySelector("button")?.getAttribute("aria-controls") + expect(fragment1().querySelector("div[role='combobox']")?.getAttribute("aria-controls")).not.toBe( + fragment2().querySelector("div[role='combobox']")?.getAttribute("aria-controls") ); }); diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/__snapshots__/DataGridDropdownFilter.spec.tsx.snap b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/__snapshots__/DataGridDropdownFilter.spec.tsx.snap index 28606ed0ee..696f9cc221 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/__snapshots__/DataGridDropdownFilter.spec.tsx.snap +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/__snapshots__/DataGridDropdownFilter.spec.tsx.snap @@ -3,20 +3,20 @@ exports[`Dropdown Filter with single instance with single attribute DOM structure renders correctly 1`] = ` - +