Skip to content
80 changes: 80 additions & 0 deletions src/bundle/Asset/RtlEntrypointLookup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Bundle\AdminUi\Asset;

use Ibexa\Contracts\AdminUi\Rtl\RtlModeResolverInterface;
use Symfony\WebpackEncoreBundle\Asset\EntrypointLookup;
use Symfony\WebpackEncoreBundle\Asset\EntrypointLookupInterface;
use Symfony\WebpackEncoreBundle\Asset\IntegrityDataProviderInterface;

final readonly class RtlEntrypointLookup implements EntrypointLookupInterface, IntegrityDataProviderInterface
{
public function __construct(
private EntrypointLookupInterface $inner,
private RtlModeResolverInterface $rtlModeResolver,
) {
}

/**
* @return array<string>
*/
public function getJavaScriptFiles(string $entryName): array
{
return $this->inner->getJavaScriptFiles($entryName);
}

/**
* @return array<string>
*/
public function getCssFiles(string $entryName): array
{
if (!$this->rtlModeResolver->isRtl()) {
return $this->inner->getCssFiles($entryName);
}

if ($this->entryExists($entryName . '-rtl')) {
$entryName .= '-rtl';
}

$files = $this->inner->getCssFiles($entryName);

$overrideEntryName = $entryName . '-override-rtl-css';
Comment thread
konradoboza marked this conversation as resolved.
Outdated
if ($this->entryExists($overrideEntryName)) {
$files = array_merge($files, $this->inner->getCssFiles($overrideEntryName));
}

return $files;
}

private function entryExists(string $entryName): bool
{
if ($this->inner instanceof EntrypointLookup) {
return $this->inner->entryExists($entryName);
}

return false;
}

/**
* @return array<string, string>
*/
public function getIntegrityData(): array
{
if ($this->inner instanceof IntegrityDataProviderInterface) {
return $this->inner->getIntegrityData();
}

return [];
}

public function reset(): void
{
$this->inner->reset();
}
}
3 changes: 3 additions & 0 deletions src/bundle/DependencyInjection/IbexaAdminUiExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ final class IbexaAdminUiExtension extends Extension implements PrependExtensionI
'ibexa.config.setup.js' => [
'ibexa.config.setup.js' => [],
],
'ibexa.rtl.config.js' => [
'ibexa.rtl.config.js' => [],
],
];

/**
Expand Down
2 changes: 2 additions & 0 deletions src/bundle/Resources/config/default_parameters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,5 @@ parameters:
ibexa.dam_widget.image.download_limit: 25

ibexa.dam_widget.folder.content_type_identifier: folder

ibexa.admin_ui.rtl_languages: ['ar', 'he', 'fa', 'ur', 'yi', 'iw', 'kd', 'ps']
Comment thread
konradoboza marked this conversation as resolved.
2 changes: 1 addition & 1 deletion src/bundle/Resources/config/services/components.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ services:
$template: '@@ibexadesign/ui/layout_content_after.html.twig'
tags:
- { name: ibexa.twig.component, group: 'admin-ui-layout-content-after' }

Ibexa\AdminUi\Component\EventSubscriber\RenderEventSubscriber:
tags:
- { name: kernel.event_subscriber }
6 changes: 6 additions & 0 deletions src/bundle/Resources/config/services/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ services:

Ibexa\Contracts\AdminUi\ContentType\ContentTypeFieldsByExpressionServiceInterface:
'@Ibexa\AdminUi\ContentType\ContentTypeFieldsByExpressionService'

Ibexa\AdminUi\Rtl\RtlModeResolver:
arguments:
$rtlLanguages: '%ibexa.admin_ui.rtl_languages%'

Ibexa\Contracts\AdminUi\Rtl\RtlModeResolverInterface: '@Ibexa\AdminUi\Rtl\RtlModeResolver'
5 changes: 5 additions & 0 deletions src/bundle/Resources/config/services/ui_config/common.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ services:
tags:
- { name: ibexa.admin_ui.config.provider, key: 'damWidget' }

Ibexa\Bundle\AdminUi\Asset\RtlEntrypointLookup:
decorates: 'webpack_encore.entrypoint_lookup[ibexa]'
arguments:
$inner: '@.inner'

# Resolvers
Ibexa\AdminUi\REST\Generator\ApplicationConfigRestGeneratorRegistry:
arguments:
Expand Down
9 changes: 9 additions & 0 deletions src/bundle/Resources/config/services/ui_config/rtl.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
_defaults:
autowire: true
autoconfigure: true
public: false

Ibexa\AdminUi\UI\Config\Provider\IsRtl:
tags:
- { name: ibexa.admin_ui.config.provider, key: 'isRtl' }
3 changes: 2 additions & 1 deletion src/bundle/Resources/encore/ibexa.css.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ module.exports = (Encore) => {
.addEntry('ibexa-admin-ui-security-base-css', [
path.resolve(__dirname, '../public/scss/ibexa-bootstrap.scss'),
path.resolve(__dirname, '../public/scss/ibexa.scss'),
]);
])
.addEntry('ibexa-admin-ui-layout-css-override-rtl-css', [path.resolve(__dirname, '../public/scss/ibexa-override-rtl.scss')]);
};
4 changes: 4 additions & 0 deletions src/bundle/Resources/encore/ibexa.rtl.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
excludedFiles: ['ibexa-admin-ui-layout-css-override-rtl-css.css', 'ibexa-admin-ui-layout-css-override-rtl-css.js'],
excludedEntrypoints: ['ibexa-admin-ui-layout-css-override-rtl-css'],
};
7 changes: 5 additions & 2 deletions src/bundle/Resources/public/js/scripts/admin.input.text.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isRtl } from '@ibexa-admin-ui-helpers/system.helper';

(function (global, doc) {
const INPUT_PADDING = 12;
const EXTRA_SPACING = 6;
Expand Down Expand Up @@ -48,18 +50,19 @@
const inputType = textWrapper.classList.contains('ibexa-input-text-wrapper--multiline') ? 'textarea' : 'input';
const input = textWrapper.querySelector(inputType);
const { width: actionsWidth } = inputActionsContainer.getBoundingClientRect();
const paddingProperty = isRtl() ? 'paddingLeft' : 'paddingRight';

if (!input) {
return;
}

if (input.type === 'number') {
input.style.paddingRight = input.value ? `${actionsWidth + EXTRA_SPACING}px` : `${INPUT_PADDING}px`;
input.style[paddingProperty] = input.value ? `${actionsWidth + EXTRA_SPACING}px` : `${INPUT_PADDING}px`;

return;
}

input.style.paddingRight = `${actionsWidth + INPUT_PADDING}px`;
input.style[paddingProperty] = `${actionsWidth + INPUT_PADDING}px`;
};
const recalculateStyling = () => {
const inputActionsContainers = doc.querySelectorAll('.ibexa-input-text-wrapper__actions');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ const isPrintPressed = (event) => {
const isSelectAllPressed = (event) => {
return isShortcutWithLetter(event, 'a');
};
const isRtl = () => {
return document.documentElement.dir === 'rtl';
};

export {
isWindows,
Expand All @@ -74,4 +77,5 @@ export {
isPrintPressed,
isSelectAllPressed,
isShortcutWithLetter,
isRtl,
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ const getBrowserTimezone = () => {
return Intl.DateTimeFormat().resolvedOptions().timeZone;
};

export { convertDateToTimezone, formatFullDateTime, formatShortDateTime, getBrowserTimezone };
export { formatDate, convertDateToTimezone, formatFullDateTime, formatShortDateTime, getBrowserTimezone };
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isRtl } from "@ibexa-admin-ui-helpers/system.helper";

(function (global, doc) {
const ACTION_BTN_VERTICAL_SPACING = 4.3;
const isIframe = global.self !== global.top;
Expand Down Expand Up @@ -48,7 +50,8 @@
}

container.style.position = 'fixed';
container.style.right = '2rem';
container.style.right = isRtl() ? 'auto' : '2rem';
container.style.left = isRtl() ? '2rem' : 'auto';
container.style.zIndex = buttonConfig.zIndex || 1040;

const bottomPosition = `${index * ACTION_BTN_VERTICAL_SPACING + maxExtraPadding}rem`;
Expand Down
11 changes: 8 additions & 3 deletions src/bundle/Resources/public/js/scripts/sidebar/main.menu.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isRtl } from "@ibexa-admin-ui-helpers/system.helper";

(function (global, doc, ibexa) {
const SECOND_LEVEL_COLLAPSED_WIDTH = 48;
const SECOND_LEVEL_EXPANDED_WIDTH = 220;
Expand Down Expand Up @@ -164,8 +166,9 @@
doc.removeEventListener('mouseup', removeResizeListeners, false);
};
const triggerSecondLevelChangeWidth = ({ clientX }) => {
const resizeValue = secondMenuLevelCurrentWidth + (clientX - resizeStartPositionX);
const newMenuWidth = resizeValue > SECOND_LEVEL_MANUAL_RESIZE_MIN_WIDTH ? resizeValue : SECOND_LEVEL_COLLAPSED_WIDTH;
const resizeValue = isRtl() ? resizeStartPositionX - clientX : clientX - resizeStartPositionX;
const calculatedMenuWidth = secondMenuLevelCurrentWidth + resizeValue;
const newMenuWidth = calculatedMenuWidth > SECOND_LEVEL_MANUAL_RESIZE_MIN_WIDTH ? calculatedMenuWidth : SECOND_LEVEL_COLLAPSED_WIDTH;

ibexa.helpers.cookies.setBackOfficeCookie('ibexa-aui_menu-secondary-width', newMenuWidth);
setWidthOfSecondLevelMenu();
Expand Down Expand Up @@ -218,7 +221,9 @@
position: () => {
const popupLeftOffset = 5;
const targetTopPosition = selectorItem.offsetTop;
const targetLeftPosition = selectorItem.offsetLeft + selectorItem.offsetWidth + popupLeftOffset;
const targetLeftPosition = isRtl()
? selectorItem.offsetLeft - firstLevelPopupMenu.offsetWidth - popupLeftOffset
: selectorItem.offsetLeft + selectorItem.offsetWidth + popupLeftOffset;

firstLevelPopupMenu.style.top = `${targetTopPosition}px`;
firstLevelPopupMenu.style.left = `${targetLeftPosition}px`;
Expand Down
70 changes: 70 additions & 0 deletions src/bundle/Resources/public/scss/ibexa-override-rtl.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
@use '@ibexa-admin-ui/src/bundle/Resources/public/scss/custom.scss' as *;
@use '@ibexa-admin-ui/src/bundle/Resources/public/scss/functions/calculate.rem' as *;

[dir='rtl'] {
.ibexa-theme {
.ibexa-tabs.ibexa-tabs {
&:not(.ibexa-tabs--switcher) {
.ibexa-tabs {
&__tab-corner {
transform: rotateY(180deg);
}
}
}
}

.ibexa-header-user-menu {
justify-content: left;
}

.ibexa-main-menu {
&__navbar {
&--second-level {
&.ibexa-main-menu__navbar--collapsed {
.ibexa-main-menu {
&__toggler {
.ibexa-icon {
transform: rotateY(0deg);
}
}
}
}
}
}

&__toggler {
margin-left: calculateRem(6px);

.ibexa-icon {
transform: rotateY(180deg);
}
}
}

.ibexa-tooltip {
&--navigation {
&.bs-tooltip-start,
&[data-popper-placement='left'] {
.ibexa-tooltip__arrow {
&::before {
border-left-color: $ibexa-color-complementary-primary-400;
}
}
}
}
}

.ibexa-back-to-top {
&__icon.ibexa-icon {
transform: rotate(90deg);
}
}

.flatpickr-months {
.flatpickr-next-month,
.flatpickr-prev-month {
transform: rotateY(180deg);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{# !! TEST PURPOSE - REMOVE BEFORE MERGE !! #}
<!DOCTYPE html>
<html>
<html {{ ibexa_admin_ui_config.isRtl ? 'dir="rtl"' : '' }}>

Check warning on line 3 in src/bundle/Resources/views/themes/admin/account/base.html.twig

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add "lang" and/or "xml:lang" attributes to this "<html>" element

See more on https://sonarcloud.io/project/issues?id=ibexa_admin-ui&issues=AZ1JLez79q9xW7xtnTJj&open=AZ1JLez79q9xW7xtnTJj&pullRequest=1866
<head>
<meta charset="UTF-8" />
<title>Ibexa DXP</title>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="{{ app.request.locale }}">
<html lang="{{ app.request.locale }}" {{ ibexa_admin_ui_config.isRtl ? 'dir="rtl"' : '' }}>
<head>
<meta charset="UTF-8" />
<meta name="CSRF-Token" content="{{ csrf_token(ibexa_get_rest_csrf_token_intention()) }}" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { isRtl } from '@ibexa-admin-ui-helpers/system.helper';

import List from '../list/list.component';
import Header from '../header/header';
Expand Down Expand Up @@ -100,7 +101,7 @@ export default class ContentTree extends Component {

this.setState(
(state) => ({
resizedContainerWidth: state.containerWidth + (currentPositionX - state.resizeStartPositionX),
resizedContainerWidth: state.containerWidth + (isRtl() ? state.resizeStartPositionX - currentPositionX : currentPositionX - state.resizeStartPositionX),
}),
() => {
document.body.dispatchEvent(new CustomEvent('ibexa-content-resized'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import FinderLeaf from './finder.leaf';
import Icon from '../../../common/icon/icon';
import Spinner from '../../../common/spinner/spinner';
import { getIconPath } from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/icon.helper.js';
import { isRtl } from '@ibexa-admin-ui-helpers/system.helper';

import { createCssClassNames } from '../../../common/helpers/css.class.names';
import { useFindLocationsByParentLocationIdFetch } from '../../hooks/useFindLocationsByParentLocationIdFetch';
Expand Down Expand Up @@ -53,7 +54,8 @@ const FinderBranch = ({ locationData, itemsPerPage = 50 }) => {
dispatchLoadedLocationsAction({ type: 'UPDATE_LOCATIONS', data: { ...locationData, collapsed: false } });
};
const changeBranchWidth = ({ clientX }) => {
let newBranchWidth = branchCurrentWidth + (clientX - resizeStartPositionX);
const resizeValue = isRtl() ? resizeStartPositionX - clientX : clientX - resizeStartPositionX;
let newBranchWidth = branchCurrentWidth + resizeValue;

if (newBranchWidth < 50) {
dispatchLoadedLocationsAction({ type: 'UPDATE_LOCATIONS', data: { ...locationData, collapsed: true } });
Expand Down
14 changes: 14 additions & 0 deletions src/contracts/Rtl/RtlModeResolverInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Contracts\AdminUi\Rtl;

interface RtlModeResolverInterface
{
public function isRtl(): bool;
}
Loading
Loading