mirror of
https://github.com/facebook/docusaurus.git
synced 2025-08-03 16:59:06 +02:00
feat(v2): add support for RTL direction (#4140)
* feat(v2): add support for RTL * Move to i18n config * Move direction to localeConfigs * Create RTL styles for Docusaurus styles (via new hook) * update infima to alpha 19 * fix minor type error * Fix build * Cleanup * polish RTL support * polish RTL support * revert english rtl dir * minor RTL fix * minor doc updates Co-authored-by: slorber <lorber.sebastien@gmail.com>
This commit is contained in:
parent
2fb642d9ee
commit
0ac34b75c5
15 changed files with 126 additions and 29 deletions
|
@ -52,7 +52,7 @@ declare module '@generated/i18n' {
|
|||
defaultLocale: string;
|
||||
locales: [string, ...string[]];
|
||||
currentLocale: string;
|
||||
localeConfigs: Record<string, {label: string}>;
|
||||
localeConfigs: Record<string, {label: string; direction: string}>;
|
||||
};
|
||||
export default i18n;
|
||||
}
|
||||
|
|
|
@ -34,15 +34,17 @@
|
|||
"@types/react-toggle": "^4.0.2",
|
||||
"clsx": "^1.1.1",
|
||||
"copy-text-to-clipboard": "^2.2.0",
|
||||
"infima": "0.2.0-alpha.18",
|
||||
"infima": "0.2.0-alpha.19",
|
||||
"joi": "^17.2.1",
|
||||
"lodash": "^4.17.19",
|
||||
"parse-numeric-range": "^1.2.0",
|
||||
"postcss": "^7.0.2",
|
||||
"prism-react-renderer": "^1.1.1",
|
||||
"prismjs": "^1.23.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-toggle": "^4.1.1"
|
||||
"react-toggle": "^4.1.1",
|
||||
"rtlcss": "^2.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "2.0.0-alpha.70"
|
||||
|
|
|
@ -9,6 +9,8 @@ import {Plugin} from '@docusaurus/types';
|
|||
import {getTranslationFiles, translateThemeConfig} from './translations';
|
||||
import path from 'path';
|
||||
import Module from 'module';
|
||||
import postcss from 'postcss';
|
||||
import rtlcss from 'rtlcss';
|
||||
|
||||
const createRequire = Module.createRequire || Module.createRequireFromPath;
|
||||
const requireFromDocusaurusCore = createRequire(
|
||||
|
@ -59,15 +61,23 @@ const noFlashColorMode = ({defaultMode, respectPrefersColorScheme}) => {
|
|||
})();`;
|
||||
};
|
||||
|
||||
function getInfimaCSSFile(direction) {
|
||||
return `infima/dist/css/default/default${
|
||||
direction === 'rtl' ? '-rtl' : ''
|
||||
}.css`;
|
||||
}
|
||||
|
||||
export default function docusaurusThemeClassic(
|
||||
context,
|
||||
options,
|
||||
): Plugin<null, unknown> {
|
||||
const {
|
||||
siteConfig: {themeConfig},
|
||||
i18n: {currentLocale, localeConfigs},
|
||||
} = context;
|
||||
const {colorMode, prism: {additionalLanguages = []} = {}} = themeConfig || {};
|
||||
const {customCss} = options || {};
|
||||
const {direction} = localeConfigs[currentLocale];
|
||||
|
||||
return {
|
||||
name: 'docusaurus-theme-classic',
|
||||
|
@ -95,7 +105,7 @@ export default function docusaurusThemeClassic(
|
|||
|
||||
getClientModules() {
|
||||
const modules = [
|
||||
require.resolve('infima/dist/css/default/default.css'),
|
||||
require.resolve(getInfimaCSSFile(direction)),
|
||||
path.resolve(__dirname, './prism-include-languages'),
|
||||
];
|
||||
|
||||
|
@ -135,6 +145,37 @@ export default function docusaurusThemeClassic(
|
|||
};
|
||||
},
|
||||
|
||||
configurePostCss(postCssOptions) {
|
||||
if (direction === 'rtl') {
|
||||
postCssOptions.plugins.push(
|
||||
postcss.plugin('RtlCssPlugin', () => {
|
||||
function isInfimaCSSFile(file) {
|
||||
return (
|
||||
file.endsWith(getInfimaCSSFile(direction)) ||
|
||||
// special case for our own monorepo using symlinks!
|
||||
file.endsWith(
|
||||
'infima/packages/core/dist/css/default/default-rtl.css',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return function (root: any) {
|
||||
const file = root?.source.input.file;
|
||||
|
||||
// Skip Infima as we are using the its RTL version.
|
||||
if (isInfimaCSSFile(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
rtlcss.process(root);
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return postCssOptions;
|
||||
},
|
||||
|
||||
injectHtmlTags() {
|
||||
return {
|
||||
preBodyTags: [
|
||||
|
|
|
@ -99,7 +99,10 @@ function DocPageContent({
|
|||
role="button"
|
||||
onKeyDown={toggleSidebar}
|
||||
onClick={toggleSidebar}>
|
||||
<IconArrow aria-label="Expand sidebar" />
|
||||
<IconArrow
|
||||
aria-label="Expand sidebar"
|
||||
className={styles.expandSidebarButtonIcon}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -44,6 +44,13 @@
|
|||
background-color: var(--ifm-color-emphasis-200);
|
||||
}
|
||||
|
||||
.expandSidebarButtonIcon {
|
||||
transform: rotate(0);
|
||||
}
|
||||
html[dir='rtl'] .expandSidebarButtonIcon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .collapsedDocSidebar:hover,
|
||||
html[data-theme='dark'] .collapsedDocSidebar:focus {
|
||||
background-color: var(--collapse-button-bg-color-dark);
|
||||
|
|
|
@ -78,6 +78,9 @@
|
|||
transform: rotate(180deg);
|
||||
margin-top: 4px;
|
||||
}
|
||||
html[dir='rtl'] .collapseSidebarButtonIcon {
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .collapseSidebarButton {
|
||||
background-color: var(--collapse-button-bg-color-dark);
|
||||
|
|
|
@ -83,7 +83,7 @@ function CanonicalUrlHeaders({permalink}: {permalink?: string}) {
|
|||
export default function LayoutHead(props: Props): JSX.Element {
|
||||
const {
|
||||
siteConfig,
|
||||
i18n: {currentLocale},
|
||||
i18n: {currentLocale, localeConfigs},
|
||||
} = useDocusaurusContext();
|
||||
const {
|
||||
favicon,
|
||||
|
@ -98,11 +98,12 @@ export default function LayoutHead(props: Props): JSX.Element {
|
|||
// See https://github.com/facebook/docusaurus/issues/3317#issuecomment-754661855
|
||||
// const htmlLang = currentLocale.split('-')[0];
|
||||
const htmlLang = currentLocale; // should we allow the user to override htmlLang with localeConfig?
|
||||
const htmlDir = localeConfigs[currentLocale].direction;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<html lang={htmlLang} />
|
||||
<html lang={htmlLang} dir={htmlDir} />
|
||||
{metaTitle && <title>{metaTitle}</title>}
|
||||
{metaTitle && <meta property="og:title" content={metaTitle} />}
|
||||
{favicon && <link rel="shortcut icon" href={faviconUrl} />}
|
||||
|
|
1
packages/docusaurus-types/src/index.d.ts
vendored
1
packages/docusaurus-types/src/index.d.ts
vendored
|
@ -94,6 +94,7 @@ export type TranslationFiles = TranslationFile[];
|
|||
|
||||
export type I18nLocaleConfig = {
|
||||
label: string;
|
||||
direction: string;
|
||||
};
|
||||
|
||||
export type I18nConfig = {
|
||||
|
|
|
@ -93,7 +93,7 @@ describe('loadI18n', () => {
|
|||
locales: ['en', 'fr', 'de'],
|
||||
currentLocale: 'de',
|
||||
localeConfigs: {
|
||||
fr: {label: 'Français'},
|
||||
fr: {label: 'Français', direction: 'ltr'},
|
||||
en: defaultLocaleConfig('en'),
|
||||
de: defaultLocaleConfig('de'),
|
||||
},
|
||||
|
|
|
@ -71,6 +71,7 @@ const PresetSchema = Joi.alternatives().try(
|
|||
|
||||
const LocaleConfigSchema = Joi.object({
|
||||
label: Joi.string(),
|
||||
direction: Joi.string().equal('ltr', 'rtl').default('ltr'),
|
||||
});
|
||||
|
||||
const I18N_CONFIG_SCHEMA = Joi.object<I18nConfig>({
|
||||
|
|
|
@ -11,6 +11,7 @@ import {normalizeUrl} from '@docusaurus/utils';
|
|||
export function defaultLocaleConfig(locale: string): I18nLocaleConfig {
|
||||
return {
|
||||
label: locale,
|
||||
direction: 'ltr',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import merge from 'webpack-merge';
|
|||
import webpack, {
|
||||
Configuration,
|
||||
Loader,
|
||||
NewLoader,
|
||||
Plugin,
|
||||
RuleSetRule,
|
||||
Stats,
|
||||
|
@ -182,21 +181,27 @@ export function applyConfigurePostCss(
|
|||
): Configuration {
|
||||
type LocalPostCSSLoader = Loader & {options: {postcssOptions: any}};
|
||||
|
||||
// TODO not ideal heuristic but good enough for our usecase?
|
||||
function isPostCssLoader(loader: Loader): loader is LocalPostCSSLoader {
|
||||
// TODO not ideal heuristic but good enough for our usecase?
|
||||
return !!(loader as any)?.options?.postcssOptions;
|
||||
}
|
||||
|
||||
// Does not handle all edge cases, but good enough for now
|
||||
config.module?.rules.map((rule) => {
|
||||
for (const loader of rule.use as NewLoader[]) {
|
||||
if (isPostCssLoader(loader)) {
|
||||
loader.options.postcssOptions = configurePostCss(
|
||||
loader.options.postcssOptions,
|
||||
);
|
||||
}
|
||||
function overridePostCssOptions(entry) {
|
||||
if (isPostCssLoader(entry)) {
|
||||
entry.options.postcssOptions = configurePostCss(
|
||||
entry.options.postcssOptions,
|
||||
);
|
||||
} else if (Array.isArray(entry.oneOf)) {
|
||||
entry.oneOf.forEach(overridePostCssOptions);
|
||||
} else if (Array.isArray(entry.use)) {
|
||||
entry.use
|
||||
.filter((u) => typeof u === 'object')
|
||||
.forEach(overridePostCssOptions);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
config.module?.rules.forEach(overridePostCssOptions);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue