Skip to content

Commit

Permalink
[CLNP-3692] Apply postcss-rtlcss plugin for RTL support (#1134)
Browse files Browse the repository at this point in the history
Applied [postcss-rtlcss
plugin](https://github.com/elchininet/postcss-rtlcss) for the better RTL
support without tedious manual CSS style modifications.

- [x] Added plugin configuration to storybook/main.ts - Allows testing
of RTL support during development.
- [x] Added plugin configuration to rollup.config.js - Ensures build
artifacts support RTL languages.
- [x] Created useHTMLTextDirection custom hook - Applies the dir
attribute to modal and dropdown portal components for correct RTL
rendering.

postcss-rtlcss automates the conversion of LTR CSS to RTL, ensuring
consistency and reducing manual errors.
It automates CSS transformation by converting LTR styles to RTL,
ensuring proper alignment and display in RTL mode.

You can play around in here https://elchininet.github.io/postcss-rtlcss/
to see what kind of modifications could be made by this plugin.
  • Loading branch information
AhyoungRyu committed Jun 24, 2024
1 parent 7307b8d commit eb56719
Show file tree
Hide file tree
Showing 15 changed files with 119 additions and 27 deletions.
9 changes: 9 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ export default {
},
viteFinal: (config) => {
return mergeConfig(config, {
css: {
postcss: {
plugins: [
require('postcss-rtlcss')({
mode: 'override',
}),
],
},
},
plugins: [svgr({ include: '**/*.svg' })],
});
},
Expand Down
6 changes: 6 additions & 0 deletions apps/testing/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import vitePluginSvgr from 'vite-plugin-svgr';
import postcssRtl from "postcss-rtlcss";

// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), vitePluginSvgr({ include: '**/*.svg' })],
css: {
postcss: {
plugins: [postcssRtl({ mode: 'override' })],
},
},
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
"jsdom": "^20.0.0",
"plop": "^2.5.3",
"postcss": "^8.3.5",
"postcss-rtlcss": "^5.3.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rollup": "^4.9.2",
Expand Down
3 changes: 2 additions & 1 deletion rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import scss from "rollup-plugin-scss";
import postcss from "rollup-plugin-postcss";
import replace from "@rollup/plugin-replace";
import autoprefixer from "autoprefixer";
import postcssRtl from "postcss-rtlcss";
import copy from "rollup-plugin-copy";
import nodePolyfills from "rollup-plugin-polyfill-node";
import {visualizer} from "rollup-plugin-visualizer";
Expand Down Expand Up @@ -57,7 +58,7 @@ export default {
const result = scss.renderSync({ file: id });
resolvecss({ code: result.css.toString() });
}),
plugins: [autoprefixer],
plugins: [autoprefixer, postcssRtl({ mode: 'override' })],
sourceMap: false,
extract: "dist/index.css",
extensions: [".sass", ".scss", ".css"],
Expand Down
5 changes: 3 additions & 2 deletions src/modules/App/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { MobileLayout } from './MobileLayout';
import useSendbirdStateContext from '../../hooks/useSendbirdStateContext';
import { SendableMessageType } from '../../utils';
import { getCaseResolvedReplyType } from '../../lib/utils/resolvedReplyType';
import useApplyTextDirection from './hooks/useApplyTextDirection';

export const AppLayout = (props: AppLayoutProps) => {
const {
Expand All @@ -33,6 +34,8 @@ export const AppLayout = (props: AppLayoutProps) => {
const [startingPoint, setStartingPoint] = useState<number | null>(null);
const { isMobile } = useMediaQueryContext();

useApplyTextDirection(htmlTextDirection);

/**
* Below configs can be set via Dashboard UIKit config setting but as a lower priority than App props.
* So need to be have fallback value \w global configs even though each prop values are undefined
Expand Down Expand Up @@ -62,7 +65,6 @@ export const AppLayout = (props: AppLayoutProps) => {
threadTargetMessage={threadTargetMessage}
setThreadTargetMessage={setThreadTargetMessage}
enableLegacyChannelModules={enableLegacyChannelModules}
htmlTextDirection={htmlTextDirection}
/>
)
: (
Expand All @@ -89,7 +91,6 @@ export const AppLayout = (props: AppLayoutProps) => {
startingPoint={startingPoint}
setStartingPoint={setStartingPoint}
enableLegacyChannelModules={enableLegacyChannelModules}
htmlTextDirection={htmlTextDirection}
/>
)
}
Expand Down
4 changes: 2 additions & 2 deletions src/modules/App/DesktopLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import MessageSearchPannel from '../MessageSearch';
import Thread from '../Thread';
import { SendableMessageType } from '../../utils';
import { classnames } from '../../utils/utils';
import { APP_LAYOUT_ROOT } from './const';

export const DesktopLayout: React.FC<DesktopLayoutProps> = (props: DesktopLayoutProps) => {
const {
Expand All @@ -39,7 +40,6 @@ export const DesktopLayout: React.FC<DesktopLayoutProps> = (props: DesktopLayout
threadTargetMessage,
setThreadTargetMessage,
enableLegacyChannelModules,
htmlTextDirection,
} = props;

const updateFocusedChannel = (channel: GroupChannelClass) => {
Expand Down Expand Up @@ -110,7 +110,7 @@ export const DesktopLayout: React.FC<DesktopLayoutProps> = (props: DesktopLayout
};

return (
<div className="sendbird-app__wrap" dir={htmlTextDirection}>
<div className="sendbird-app__wrap" id={APP_LAYOUT_ROOT}>
<div className="sendbird-app__channellist-wrap">
{enableLegacyChannelModules ? <ChannelList {...channelListProps} /> : <GroupChannelList {...channelListProps} />}
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/modules/App/MobileLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import useSendbirdStateContext from '../../hooks/useSendbirdStateContext';
import uuidv4 from '../../utils/uuid';
import { ALL, useVoicePlayerContext } from '../../hooks/VoicePlayer';
import { SendableMessageType } from '../../utils';
import { APP_LAYOUT_ROOT } from './const';

enum PANELS {
CHANNEL_LIST = 'CHANNEL_LIST',
Expand Down Expand Up @@ -44,7 +45,6 @@ export const MobileLayout: React.FC<MobileLayoutProps> = (props: MobileLayoutPro
highlightedMessage,
setHighlightedMessage,
enableLegacyChannelModules,
htmlTextDirection,
} = props;
const [panel, setPanel] = useState(PANELS.CHANNEL_LIST);

Expand Down Expand Up @@ -166,7 +166,7 @@ export const MobileLayout: React.FC<MobileLayoutProps> = (props: MobileLayoutPro
};

return (
<div className="sb_mobile" dir={htmlTextDirection}>
<div className="sb_mobile" id={APP_LAYOUT_ROOT}>
{panel === PANELS.CHANNEL_LIST && (
<div className="sb_mobile__panelwrap">
{enableLegacyChannelModules ? <ChannelList {...channelListProps} /> : <GroupChannelList {...channelListProps} />}
Expand Down
1 change: 1 addition & 0 deletions src/modules/App/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const APP_LAYOUT_ROOT = 'sendbird-app__layout';
35 changes: 35 additions & 0 deletions src/modules/App/hooks/useApplyTextDirection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useEffect } from 'react';
import { MODAL_ROOT } from '../../../hooks/useModal';
import { EMOJI_MENU_ROOT_ID, MENU_ROOT_ID } from '../../../ui/ContextMenu';
import { HTMLTextDirection } from '../../../types';
import { APP_LAYOUT_ROOT } from '../const';

const ELEMENT_IDS = [
MODAL_ROOT,
EMOJI_MENU_ROOT_ID,
MENU_ROOT_ID,
APP_LAYOUT_ROOT,
];

/**
* This hook sets the direction (dir) attribute for specified elements.
*
* @param {HTMLTextDirection} direction - The direction to set ('ltr' or 'rtl').
*
* Note:
* This is necessary because elements such as modal, emoji reaction list, and dropdown
* are at the same level as the Sendbird app root element. They need to have the 'dir'
* attribute set explicitly to ensure proper directionality based on the app's language setting.
*/
const useApplyTextDirection = (direction: HTMLTextDirection) => {
useEffect(() => {
ELEMENT_IDS.forEach((id) => {
const element = document.getElementById(id);
if (element) {
element.dir = direction;
}
});
}, [direction]);
};

export default useApplyTextDirection;
4 changes: 2 additions & 2 deletions src/ui/ContextMenu/MenuItems.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { ReactElement } from 'react';
import { createPortal } from 'react-dom';
import { classnames } from '../../utils/utils';
import { MENU_OBSERVING_CLASS_NAME } from '.';
import { MENU_OBSERVING_CLASS_NAME, MENU_ROOT_ID } from '.';

interface MenuItemsProps {
id?: string;
Expand Down Expand Up @@ -105,7 +105,7 @@ export default class MenuItems extends React.Component<MenuItemsProps, MenuItems
};

render(): ReactElement {
const portalElement = document.getElementById('sendbird-dropdown-portal');
const portalElement = document.getElementById(MENU_ROOT_ID);
if (!portalElement)
return <></>;

Expand Down
1 change: 0 additions & 1 deletion src/ui/ContextMenu/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
z-index: 99999;
position: absolute;
top: 100%;
left: 0;
min-width: 140px;
margin: 0px;
padding: 8px 0px;
Expand Down
1 change: 0 additions & 1 deletion src/ui/ContextMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ export const MenuRoot = (): ReactElement => (
<div id={MENU_ROOT_ID} className={MENU_ROOT_ID} />
);

// For the test environment
export const EMOJI_MENU_ROOT_ID = 'sendbird-emoji-list-portal';
export const EmojiReactionListRoot = (): ReactElement => <div id={EMOJI_MENU_ROOT_ID} />;

Expand Down
3 changes: 3 additions & 0 deletions src/ui/FileViewer/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ $file-viewer-img-max-width: calc(100% - #{$file-viewer-slide-buttons-side-length
top: calc(50% - 16px);
}

// Fliping the arrow icons for RTL is not necessary
/*rtl:begin:ignore*/
.sendbird-file-viewer-arrow--left {
left: 14px;
}
Expand All @@ -135,3 +137,4 @@ $file-viewer-img-max-width: calc(100% - #{$file-viewer-slide-buttons-side-length
right: 14px;
transform: rotate(180deg);
}
/*rtl:end:ignore*/
41 changes: 26 additions & 15 deletions src/ui/Toggle/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@
position: relative;
display: inline-flex;
align-items: center;

box-sizing: border-box;
cursor: pointer;
}

.sendbird-input-toggle-button--checked {
@include themed() {
background-color: t(primary-3);
border: 1px solid t(primary-3);
}
}

.sendbird-input-toggle-button--unchecked {
@include themed() {
background-color: t(bg-3);
border: 1px solid t(bg-3);
}
}

.sendbird-input-toggle-button--disabled {
cursor: not-allowed;
@include themed() {
Expand All @@ -43,49 +45,58 @@
}

/* Manage animation and position by status */
@keyframes sendbirdMoveToRight {
@keyframes sendbirdMoveToEnd {
0% {
right: 60%;
inset-inline-end: 60%;
}
100% {
right: 10%;
inset-inline-end: 10%;
}
}
@keyframes sendbirdMoveToLeft {

@keyframes sendbirdMoveToStart {
0% {
right: 10%;
inset-inline-end: 10%;
}
100% {
right: 60%;
inset-inline-end: 60%;
}
}

// normal - animation
.sendbird-input-toggle-button--turned-on .sendbird-input-toggle-button__inner-dot {
animation-name: sendbirdMoveToRight;
animation-name: sendbirdMoveToEnd;
}

.sendbird-input-toggle-button--turned-off .sendbird-input-toggle-button__inner-dot {
animation-name: sendbirdMoveToLeft;
animation-name: sendbirdMoveToStart;
}

// normal - position
.sendbird-input-toggle-button--unchecked .sendbird-input-toggle-button__inner-dot {
right: 60%;
inset-inline-end: 60%;
}

.sendbird-input-toggle-button--checked .sendbird-input-toggle-button__inner-dot {
right: 10%;
inset-inline-end: 10%;
}

.sendbird-input-toggle-button--reversed {
// reverse - animation
.sendbird-input-toggle-button--turned-on .sendbird-input-toggle-button__inner-dot {
animation-name: sendbirdMoveToLeft;
animation-name: sendbirdMoveToStart;
}

.sendbird-input-toggle-button--turned-off .sendbird-input-toggle-button__inner-dot {
animation-name: sendbirdMoveToRight;
animation-name: sendbirdMoveToEnd;
}

// reverse - position
&.sendbird-input-toggle-button--unchecked .sendbird-input-toggle-button__inner-dot {
right: 10%;
inset-inline-end: 10%;
}

&.sendbird-input-toggle-button--checked .sendbird-input-toggle-button__inner-dot {
right: 60%;
inset-inline-end: 60%;
}
}
28 changes: 27 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2710,6 +2710,7 @@ __metadata:
jsdom: ^20.0.0
plop: ^2.5.3
postcss: ^8.3.5
postcss-rtlcss: ^5.3.0
react: ^18.2.0
react-dom: ^18.2.0
rollup: ^4.9.2
Expand Down Expand Up @@ -12966,6 +12967,17 @@ __metadata:
languageName: node
linkType: hard

"postcss-rtlcss@npm:^5.3.0":
version: 5.3.0
resolution: "postcss-rtlcss@npm:5.3.0"
dependencies:
rtlcss: 4.1.1
peerDependencies:
postcss: ^8.4.21
checksum: bd9bd09dc3b45b65db83dc8a0189701d1039b712916484bcb6afb7465a8343eafb0c09464c4d9079f016847e699a5724454e75855fb21fe72aa1b2b415f888ac
languageName: node
linkType: hard

"postcss-safe-parser@npm:^4.0.2":
version: 4.0.2
resolution: "postcss-safe-parser@npm:4.0.2"
Expand Down Expand Up @@ -13063,7 +13075,7 @@ __metadata:
languageName: node
linkType: hard

"postcss@npm:^8.3.5, postcss@npm:^8.4.38":
"postcss@npm:^8.3.5, postcss@npm:^8.4.21, postcss@npm:^8.4.38":
version: 8.4.38
resolution: "postcss@npm:8.4.38"
dependencies:
Expand Down Expand Up @@ -14043,6 +14055,20 @@ __metadata:
languageName: node
linkType: hard

"rtlcss@npm:4.1.1":
version: 4.1.1
resolution: "rtlcss@npm:4.1.1"
dependencies:
escalade: ^3.1.1
picocolors: ^1.0.0
postcss: ^8.4.21
strip-json-comments: ^3.1.1
bin:
rtlcss: bin/rtlcss.js
checksum: dcf37d76265b5c84d610488afa68a2506d008f95feac968b35ccae9aa49e7019ae0336a80363303f8f8bbf60df3ecdeb60413548b049114a24748319b68aefde
languageName: node
linkType: hard

"run-async@npm:^2.4.0":
version: 2.4.1
resolution: "run-async@npm:2.4.1"
Expand Down

0 comments on commit eb56719

Please sign in to comment.