diff --git a/packages/pxweb2-ui/src/lib/components/Select/Select.spec.tsx b/packages/pxweb2-ui/src/lib/components/Select/Select.spec.tsx index 86dda649e..8b323c113 100644 --- a/packages/pxweb2-ui/src/lib/components/Select/Select.spec.tsx +++ b/packages/pxweb2-ui/src/lib/components/Select/Select.spec.tsx @@ -2,7 +2,8 @@ import { render, fireEvent, screen } from '@testing-library/react'; import { describe, it, expect, beforeEach, vi } from 'vitest'; import '@testing-library/jest-dom/vitest'; -import Select, { SelectOption } from './Select'; +import Select from './Select'; +import { SelectOption } from './SelectOptionType'; import { mockHTMLDialogElement } from '../../util/test-utils'; describe('Select', () => { diff --git a/packages/pxweb2-ui/src/lib/components/Select/Select.stories.tsx b/packages/pxweb2-ui/src/lib/components/Select/Select.stories.tsx index 1b5baebbe..7e8427a7e 100644 --- a/packages/pxweb2-ui/src/lib/components/Select/Select.stories.tsx +++ b/packages/pxweb2-ui/src/lib/components/Select/Select.stories.tsx @@ -1,5 +1,6 @@ import type { Meta, StoryFn } from '@storybook/react'; -import { Select, SelectOption } from './Select'; +import { Select } from './Select'; +import { SelectOption } from './SelectOptionType'; const meta: Meta = { component: Select, @@ -31,6 +32,13 @@ function selectedOptionChanged(selectedItem: SelectOption | undefined) { ? console.log('Selected option: ' + selectedItem.label) : console.log('No option selected'); } +function addModal(name: string, closeFunction: () => void) { + console.log('Add modal: ' + name); + closeFunction(); +} +function closeModal() { + console.log('Close modal'); +} export const Default = { args: { @@ -41,6 +49,8 @@ export const Default = { options: options, placeholder: placeholder, onChange: selectedOptionChanged, + addModal: addModal, + removeModal: closeModal, }, }; @@ -131,3 +141,50 @@ export const SelectedOption: StoryFn = () => { ); }; + +export const RTLLanguage: StoryFn = () => { + const rtlOptions: SelectOption[] = [ + { label: 'الخيار 1', value: 'opt1' }, + { label: 'الخيار 2', value: 'opt2' }, + { label: 'الخيار 3 هو خيار بنص طويل جدًا', value: 'opt3' }, + { label: 'الخيار 4', value: 'opt4' }, + { label: 'الخيار 5', value: 'opt5' }, + ]; + + const rtlPlaceholder = 'اختر خيارًا'; + + return ( +
+

RTL Language Support

+ +

Arabic interface:

+
+ +
+
+ ); +}; diff --git a/packages/pxweb2-ui/src/lib/components/Select/Select.tsx b/packages/pxweb2-ui/src/lib/components/Select/Select.tsx index 50b77747e..15ca3a28b 100644 --- a/packages/pxweb2-ui/src/lib/components/Select/Select.tsx +++ b/packages/pxweb2-ui/src/lib/components/Select/Select.tsx @@ -2,20 +2,18 @@ import cl from 'clsx'; import { useState, useEffect, useRef } from 'react'; import classes from './Select.module.scss'; +import { SelectOption } from './SelectOptionType'; import Label from '../Typography/Label/Label'; import BodyShort from '../Typography/BodyShort/BodyShort'; import { Icon } from '../Icon/Icon'; import Modal from '../Modal/Modal'; import Radio from '../Radio/Radio'; - -export type SelectOption = { - label: string; - value: string; -}; +import { getIconDirection } from '../../util/util'; export type SelectProps = { variant?: 'default' | 'inVariableBox'; label: string; + languageDirection?: 'ltr' | 'rtl'; modalHeading?: string; modalCancelLabel?: string; modalConfirmLabel?: string; @@ -38,6 +36,7 @@ function openOptions(options: SelectOption[]) { export function Select({ variant = 'default', label, + languageDirection = 'ltr', modalHeading = '', modalCancelLabel = '', modalConfirmLabel = '', @@ -70,6 +69,7 @@ export function Select({ {variant === 'inVariableBox' && ( & { + languageDirection: 'ltr' | 'rtl'; addModal: (id: string, onClose: () => void) => void; removeModal: (name: string) => void; }; function VariableBoxSelect({ label, + languageDirection, modalHeading, modalCancelLabel, modalConfirmLabel, @@ -253,6 +255,13 @@ function VariableBoxSelect({ } }, [removeModal, isModalOpen, addModal]); + // handle rtl for the icon + const chevronIcon = getIconDirection( + languageDirection, + 'ChevronRight', + 'ChevronLeft', + ); + return ( <>
- +
{isModalOpen && ( diff --git a/packages/pxweb2-ui/src/lib/components/Select/SelectOptionType.ts b/packages/pxweb2-ui/src/lib/components/Select/SelectOptionType.ts new file mode 100644 index 000000000..83486a007 --- /dev/null +++ b/packages/pxweb2-ui/src/lib/components/Select/SelectOptionType.ts @@ -0,0 +1,4 @@ +export type SelectOption = { + label: string; + value: string; +}; diff --git a/packages/pxweb2-ui/src/lib/components/VariableBox/VariableBox.tsx b/packages/pxweb2-ui/src/lib/components/VariableBox/VariableBox.tsx index 69e32ad0d..62a34851c 100644 --- a/packages/pxweb2-ui/src/lib/components/VariableBox/VariableBox.tsx +++ b/packages/pxweb2-ui/src/lib/components/VariableBox/VariableBox.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import cl from 'clsx'; import classes from './VariableBox.module.scss'; -import { SelectOption } from '../Select/Select'; +import { SelectOption } from '../Select/SelectOptionType'; import { VariableBoxHeader } from './VariableBoxHeader/VariableBoxHeader'; import { VariableBoxContent } from './VariableBoxContent/VariableBoxContent'; import { Variable } from '../../shared-types/variable'; @@ -18,6 +18,7 @@ export type VariableBoxPropsBase = Omit; export type VariableBoxProps = VariableBoxPropsBase & { tableId: string; + languageDirection: 'ltr' | 'rtl'; initialIsOpen?: boolean; onChangeCodeList: ( selectedItem: SelectOption | undefined, @@ -39,6 +40,7 @@ export function VariableBox({ initialIsOpen, tableId, label, + languageDirection, mandatory = false, type, values, @@ -97,6 +99,7 @@ export function VariableBox({ varId={id} type={type} label={capitalizedVariableName} + languageDirection={languageDirection} values={values} codeLists={codeLists} selectedValues={selectedValues} diff --git a/packages/pxweb2-ui/src/lib/components/VariableBox/VariableBoxContent/VariableBoxContent.spec.tsx b/packages/pxweb2-ui/src/lib/components/VariableBox/VariableBoxContent/VariableBoxContent.spec.tsx index 5b52ca35b..4ca915611 100644 --- a/packages/pxweb2-ui/src/lib/components/VariableBox/VariableBoxContent/VariableBoxContent.spec.tsx +++ b/packages/pxweb2-ui/src/lib/components/VariableBox/VariableBoxContent/VariableBoxContent.spec.tsx @@ -8,6 +8,7 @@ describe('VariableBoxContent', () => { const { baseElement } = render( { diff --git a/packages/pxweb2-ui/src/lib/components/VariableBox/VariableBoxContent/VariableBoxContent.tsx b/packages/pxweb2-ui/src/lib/components/VariableBox/VariableBoxContent/VariableBoxContent.tsx index 813eb7281..09f729844 100644 --- a/packages/pxweb2-ui/src/lib/components/VariableBox/VariableBoxContent/VariableBoxContent.tsx +++ b/packages/pxweb2-ui/src/lib/components/VariableBox/VariableBoxContent/VariableBoxContent.tsx @@ -7,7 +7,8 @@ import { Virtuoso, VirtuosoHandle } from 'react-virtuoso'; import classes from './VariableBoxContent.module.scss'; import { Checkbox, MixedCheckbox } from '../../Checkbox/Checkbox'; import Search from '../../Search/Search'; -import { Select, SelectOption } from '../../Select/Select'; +import { Select } from '../../Select/Select'; +import { SelectOption } from '../../Select/SelectOptionType'; import { VariableBoxProps, SelectedVBValues } from '../VariableBox'; import { VartypeEnum } from '../../../shared-types/vartypeEnum'; import { Value } from '../../../shared-types/value'; @@ -34,6 +35,7 @@ type VariableBoxContentProps = VariableBoxPropsToContent & { selectedValues: SelectedVBValues[]; totalValues: number; totalChosenValues: number; + languageDirection: 'ltr' | 'rtl'; onChangeCodeList: ( selectedItem: SelectOption | undefined, varId: string, @@ -51,6 +53,7 @@ type VariableBoxContentProps = VariableBoxPropsToContent & { export function VariableBoxContent({ varId, label, + languageDirection, type, values, codeLists, @@ -537,6 +540,7 @@ export function VariableBoxContent({ label={t( 'presentation_page.sidemenu.selection.variablebox.content.select.label', )} + languageDirection={languageDirection} modalHeading={label} modalCancelLabel={t( 'presentation_page.sidemenu.selection.variablebox.content.select.modal.cancel_button', diff --git a/packages/pxweb2-ui/src/lib/components/VariableBox/utils.spec.ts b/packages/pxweb2-ui/src/lib/components/VariableBox/utils.spec.ts index 3b1c47ce3..abea5a762 100644 --- a/packages/pxweb2-ui/src/lib/components/VariableBox/utils.spec.ts +++ b/packages/pxweb2-ui/src/lib/components/VariableBox/utils.spec.ts @@ -1,7 +1,7 @@ import { describe, it, expect } from 'vitest'; import { CodeList } from '../../shared-types/codelist'; -import { SelectOption } from '../Select/Select'; +import { SelectOption } from '../Select/SelectOptionType'; import { sortSelectOptionsGroupingsLast, mapAndSortCodeLists } from './utils'; const vsOptions: SelectOption[] = [ diff --git a/packages/pxweb2-ui/src/lib/components/VariableBox/utils.ts b/packages/pxweb2-ui/src/lib/components/VariableBox/utils.ts index 46a1f7abb..f4916ce3b 100644 --- a/packages/pxweb2-ui/src/lib/components/VariableBox/utils.ts +++ b/packages/pxweb2-ui/src/lib/components/VariableBox/utils.ts @@ -1,4 +1,4 @@ -import { SelectOption } from '../Select/Select'; +import { SelectOption } from '../Select/SelectOptionType'; import { CodeList } from '../../shared-types/codelist'; import { mapCodeListsToSelectOptions } from '../../util/util'; diff --git a/packages/pxweb2-ui/src/lib/components/VariableList/VariableList.tsx b/packages/pxweb2-ui/src/lib/components/VariableList/VariableList.tsx index 96f8a627a..201f55e54 100644 --- a/packages/pxweb2-ui/src/lib/components/VariableList/VariableList.tsx +++ b/packages/pxweb2-ui/src/lib/components/VariableList/VariableList.tsx @@ -3,12 +3,13 @@ import cl from 'clsx'; import styles from './VariableList.module.scss'; import { SelectedVBValues, VariableBox } from '../VariableBox/VariableBox'; import { PxTableMetadata } from '../../shared-types/pxTableMetadata'; -import { SelectOption } from '../Select/Select'; +import { SelectOption } from '../Select/SelectOptionType'; import { Value } from '../../shared-types/value'; export type VariableListProps = { pxTableMetadata: PxTableMetadata | null; isLoadingMetadata: boolean; + languageDirection: 'ltr' | 'rtl'; hasLoadedDefaultSelection: boolean; isChangingCodeList: boolean; selectedVBValues: SelectedVBValues[]; @@ -31,6 +32,7 @@ export type VariableListProps = { export function VariableList({ pxTableMetadata, isLoadingMetadata, + languageDirection, hasLoadedDefaultSelection, isChangingCodeList = false, selectedVBValues, @@ -59,6 +61,7 @@ export function VariableList({ initialIsOpen={index === 0} tableId={pxTableMetadata.id} label={variable.label} + languageDirection={languageDirection} mandatory={variable.mandatory} type={variable.type} values={variable.values} diff --git a/packages/pxweb2-ui/src/lib/shared-types/variable.ts b/packages/pxweb2-ui/src/lib/shared-types/variable.ts index 2cf51cc9d..573c22ab2 100644 --- a/packages/pxweb2-ui/src/lib/shared-types/variable.ts +++ b/packages/pxweb2-ui/src/lib/shared-types/variable.ts @@ -1,7 +1,6 @@ import { CodeList } from './codelist'; import { Note } from './note'; import { Value } from './value'; -import { ValueDisplayType } from './valueDisplayType'; import { VartypeEnum } from './vartypeEnum'; export type Variable = { @@ -12,5 +11,4 @@ export type Variable = { values: Value[]; codeLists?: CodeList[]; notes?: Note[]; - valueDisplayType: ValueDisplayType; }; diff --git a/packages/pxweb2-ui/src/lib/util/util.spec.ts b/packages/pxweb2-ui/src/lib/util/util.spec.ts index 29ec83ce6..24035a4ef 100644 --- a/packages/pxweb2-ui/src/lib/util/util.spec.ts +++ b/packages/pxweb2-ui/src/lib/util/util.spec.ts @@ -6,7 +6,7 @@ import { mapCodeListsToSelectOptions, } from './util'; import { CodeList } from '../shared-types/codelist'; -import { SelectOption } from '../components/Select/Select'; +import { SelectOption } from '../components/Select/SelectOptionType'; describe('getCSSVariable', () => { beforeEach(() => { diff --git a/packages/pxweb2-ui/src/lib/util/util.ts b/packages/pxweb2-ui/src/lib/util/util.ts index ba3ff8bc0..f9b88208b 100644 --- a/packages/pxweb2-ui/src/lib/util/util.ts +++ b/packages/pxweb2-ui/src/lib/util/util.ts @@ -1,5 +1,6 @@ import { CodeList } from '../shared-types/codelist'; -import { SelectOption } from '../components/Select/Select'; +import { SelectOption } from '../components/Select/SelectOptionType'; +import { IconProps } from '../components/Icon/Icon'; export const getCSSVariable = (variable: string): string => { const rootStyle = getComputedStyle(document.documentElement); @@ -23,3 +24,21 @@ export const mapCodeListsToSelectOptions = ( value: code.id, })); }; + +/** + * Returns the icon direction based on the RTL setting. + * + * @param isRtl - A boolean indicating whether the layout is RTL (right-to-left). + * @param iconLeft - The icon to use for left-to-right layout. + * @param iconRight - The icon to use for right-to-left layout. + * @returns The icon direction as a string. + */ +export function getIconDirection( + langDir: 'ltr' | 'rtl', + iconForLtl: IconProps['iconName'], + iconForRtl: IconProps['iconName'], +): IconProps['iconName'] { + const isRtl = langDir === 'rtl'; + + return isRtl ? iconForRtl : iconForLtl; +} diff --git a/packages/pxweb2/src/app/components/NavigationDrawer/NavigationDrawer.tsx b/packages/pxweb2/src/app/components/NavigationDrawer/NavigationDrawer.tsx index 734295c13..eaad1d7a1 100644 --- a/packages/pxweb2/src/app/components/NavigationDrawer/NavigationDrawer.tsx +++ b/packages/pxweb2/src/app/components/NavigationDrawer/NavigationDrawer.tsx @@ -1,8 +1,9 @@ import React, { forwardRef } from 'react'; import cl from 'clsx'; -import styles from './NavigationDrawer.module.scss'; -import { Heading, Icon, Label } from '@pxweb2/pxweb2-ui'; import { useTranslation } from 'react-i18next'; + +import styles from './NavigationDrawer.module.scss'; +import { Heading, Icon, getIconDirection, Label } from '@pxweb2/pxweb2-ui'; import i18next from 'i18next'; import useAccessibility from '../../context/useAccessibility'; import useApp from '../../context/useApp'; @@ -36,8 +37,12 @@ export const NavigationDrawer = forwardRef< }; }, [addModal, removeModal, onClose, view]); - // Handle RTL languages - const hideIcon = i18next.dir() === 'rtl' ? 'ChevronRight' : 'ChevronLeft'; + // Handle RTL languages for the icon + const hideIcon = getIconDirection( + i18next.dir(), + 'ChevronLeft', + 'ChevronRight', + ); function handleKeyDown(event: React.KeyboardEvent) { if (event.key === 'Enter' || event.key === ' ') { diff --git a/packages/pxweb2/src/app/components/Selection/Selection.tsx b/packages/pxweb2/src/app/components/Selection/Selection.tsx index f4e33911a..66dbc1e07 100644 --- a/packages/pxweb2/src/app/components/Selection/Selection.tsx +++ b/packages/pxweb2/src/app/components/Selection/Selection.tsx @@ -13,6 +13,7 @@ import { mapCodeListToSelectOption, PxTable, ValueDisplayType, + Variable, } from '@pxweb2/pxweb2-ui'; import NavigationDrawer from '../../components/NavigationDrawer/NavigationDrawer'; import useVariables from '../../context/useVariables'; @@ -252,6 +253,10 @@ function removeAllValuesOfVariable( return newValues; } +interface VariableWithDisplayType extends Variable { + valueDisplayType: ValueDisplayType; +} + type propsType = { selectedNavigationView: string; selectedTabId: string; @@ -376,7 +381,7 @@ export function Selection({ const prevSelectedValues = structuredClone(selectedVBValues); const currentVariableMetadata = pxTableMetaToRender?.variables.find( (variable) => variable.id === varId, - ); + ) as VariableWithDisplayType; const currentSelectedVariable = prevSelectedValues.find( (variable) => variable.id === varId, ); @@ -550,6 +555,7 @@ export function Selection({ const drawerFilter = ( ({ i18n: { // eslint-disable-next-line @typescript-eslint/no-empty-function changeLanguage: () => new Promise(() => {}), + dir: () => 'ltr', }, }), initReactI18next: {