From: Mark Otto Date: Sat, 15 Nov 2025 03:08:01 +0000 (-0800) Subject: Remove RTLCSS dependency, update docs and CSS to use native RTL (#41858) X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=14c1a4e0a1a50aca708030a5f981fc9baa4dd62f;p=thirdparty%2Fbootstrap.git Remove RTLCSS dependency, update docs and CSS to use native RTL (#41858) * Remove RTLCSS dependency, update docs and CSS to use native RTL * Rewrite more RTL stuff, was too verbose --- diff --git a/README.md b/README.md index e3618e643e..32e088f9e4 100644 --- a/README.md +++ b/README.md @@ -87,34 +87,18 @@ Within the download you’ll find the following directories and files, logically │ ├── bootstrap-grid.css.map │ ├── bootstrap-grid.min.css │ ├── bootstrap-grid.min.css.map - │ ├── bootstrap-grid.rtl.css - │ ├── bootstrap-grid.rtl.css.map - │ ├── bootstrap-grid.rtl.min.css - │ ├── bootstrap-grid.rtl.min.css.map │ ├── bootstrap-reboot.css │ ├── bootstrap-reboot.css.map │ ├── bootstrap-reboot.min.css │ ├── bootstrap-reboot.min.css.map - │ ├── bootstrap-reboot.rtl.css - │ ├── bootstrap-reboot.rtl.css.map - │ ├── bootstrap-reboot.rtl.min.css - │ ├── bootstrap-reboot.rtl.min.css.map │ ├── bootstrap-utilities.css │ ├── bootstrap-utilities.css.map │ ├── bootstrap-utilities.min.css │ ├── bootstrap-utilities.min.css.map - │ ├── bootstrap-utilities.rtl.css - │ ├── bootstrap-utilities.rtl.css.map - │ ├── bootstrap-utilities.rtl.min.css - │ ├── bootstrap-utilities.rtl.min.css.map │ ├── bootstrap.css │ ├── bootstrap.css.map │ ├── bootstrap.min.css - │ ├── bootstrap.min.css.map - │ ├── bootstrap.rtl.css - │ ├── bootstrap.rtl.css.map - │ ├── bootstrap.rtl.min.css - │ └── bootstrap.rtl.min.css.map + │ └── bootstrap.min.css.map └── js/ ├── bootstrap.bundle.js ├── bootstrap.bundle.js.map @@ -131,7 +115,7 @@ Within the download you’ll find the following directories and files, logically ``` -We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [Source maps](https://web.dev/articles/source-maps) (`bootstrap.*.map`) are available for use with certain browsers’ developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/docs/v2/). +We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [Source maps](https://web.dev/articles/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/docs/v2/). All CSS files work for both LTR and RTL layouts thanks to logical properties—simply set `dir="rtl"` on your HTML element. ## Bugs and feature requests diff --git a/build/generate-sri.mjs b/build/generate-sri.mjs index 5622843f34..1daa44c8c1 100644 --- a/build/generate-sri.mjs +++ b/build/generate-sri.mjs @@ -29,10 +29,6 @@ const files = [ file: 'dist/css/bootstrap.min.css', configPropertyName: 'css_hash' }, - { - file: 'dist/css/bootstrap.rtl.min.css', - configPropertyName: 'css_rtl_hash' - }, { file: 'dist/js/bootstrap.min.js', configPropertyName: 'js_hash' diff --git a/build/generate-utilities-json.mjs b/build/generate-utilities-json.mjs index bea11f991f..3c5e93331a 100644 --- a/build/generate-utilities-json.mjs +++ b/build/generate-utilities-json.mjs @@ -58,24 +58,20 @@ try { writeFileSync(outputPath, JSON.stringify(parsed, null, 2)) console.log(`✓ Wrote metadata to ${outputPath}`) - // Clean up temporary CSS files (including RTL variants that may have been generated) + // Clean up temporary CSS files try { unlinkSync(cssPath) } catch { // File may not exist } - // Also clean up any RTL variants that postcss may have created - const rtlFiles = [ - 'dist/css/utilities-metadata.tmp.rtl.css', - 'dist/css/utilities-metadata.tmp.rtl.css.map', - 'dist/css/utilities-metadata.tmp.rtl.min.css', - 'dist/css/utilities-metadata.tmp.rtl.min.css.map', + // Also clean up any other temporary variants that may have been created + const tempFiles = [ 'dist/css/utilities-metadata.tmp.min.css', 'dist/css/utilities-metadata.tmp.min.css.map' ] - for (const file of rtlFiles) { + for (const file of tempFiles) { try { unlinkSync(path.join(rootDir, file)) } catch { diff --git a/build/postcss.config.mjs b/build/postcss.config.mjs index 7717cfc3f1..5085844e6e 100644 --- a/build/postcss.config.mjs +++ b/build/postcss.config.mjs @@ -10,8 +10,7 @@ export default context => { plugins: { autoprefixer: { cascade: false - }, - rtlcss: context.env === 'RTL' + } } } } diff --git a/config.yml b/config.yml index 24d49eae57..de3b6767a7 100644 --- a/config.yml +++ b/config.yml @@ -36,8 +36,6 @@ cdn: # See https://www.srihash.org for info on how to generate the hashes css: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" css_hash: "sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" - css_rtl: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.rtl.min.css" - css_rtl_hash: "sha384-CfCrinSRH2IR6a4e6fy2q6ioOX7O6Mtm1L9vRvFZ1trBncWmMePhzvafv7oIcWiW" js: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.min.js" js_hash: "sha384-G/EV+4j2dNv+tEPo3++6LCgdCROaejBqfUeNjuKAiuXbjrxilcCdDz6ZAVfHWe1Y" js_bundle: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" diff --git a/package.json b/package.json index b72d26cf10..019aa97c41 100644 --- a/package.json +++ b/package.json @@ -41,20 +41,17 @@ "scripts": { "start": "npm-run-all --parallel watch docs-serve", "bundlewatch": "bundlewatch --config .bundlewatch.config.json", - "css": "npm-run-all css-compile css-prefix css-rtl css-minify css-docs", + "css": "npm-run-all css-compile css-prefix css-minify css-docs", "css-compile": "sass --style expanded --source-map --embed-sources --no-error-css scss/bootstrap.scss:dist/css/bootstrap.css scss/bootstrap-grid.scss:dist/css/bootstrap-grid.css scss/bootstrap-reboot.scss:dist/css/bootstrap-reboot.css scss/bootstrap-utilities.scss:dist/css/bootstrap-utilities.css", "css-docs": "node build/generate-utilities-json.mjs", - "css-rtl": "cross-env NODE_ENV=RTL postcss --config build/postcss.config.mjs --dir \"dist/css\" --ext \".rtl.css\" \"dist/css/*.css\" \"!dist/css/*.min.css\" \"!dist/css/*.rtl.css\" \"!dist/css/*.tmp.css\"", "css-lint": "npm-run-all --aggregate-output --continue-on-error --parallel css-lint-*", "css-lint-stylelint": "stylelint \"**/*.{css,scss}\" --cache --cache-location .cache/.stylelintcache", "css-lint-vars": "fusv scss/ site/src/scss/", "css-minify": "npm-run-all --aggregate-output --parallel css-minify-*", - "css-minify-main": "cleancss -O1 --format breakWith=lf --with-rebase --source-map --source-map-inline-sources --output dist/css/ --batch --batch-suffix \".min\" \"dist/css/*.css\" \"!dist/css/*.min.css\" \"!dist/css/*rtl*.css\" \"!dist/css/*.tmp.css\"", - "css-minify-rtl": "cleancss -O1 --format breakWith=lf --with-rebase --source-map --source-map-inline-sources --output dist/css/ --batch --batch-suffix \".min\" \"dist/css/*rtl.css\" \"!dist/css/*.min.css\"", + "css-minify-main": "cleancss -O1 --format breakWith=lf --with-rebase --source-map --source-map-inline-sources --output dist/css/ --batch --batch-suffix \".min\" \"dist/css/*.css\" \"!dist/css/*.min.css\" \"!dist/css/*.tmp.css\"", "css-prefix": "npm-run-all --aggregate-output --parallel css-prefix-*", - "css-prefix-main": "postcss --config build/postcss.config.mjs --replace \"dist/css/*.css\" \"!dist/css/*.rtl*.css\" \"!dist/css/*.min.css\" \"!dist/css/*.tmp.css\"", + "css-prefix-main": "postcss --config build/postcss.config.mjs --replace \"dist/css/*.css\" \"!dist/css/*.min.css\" \"!dist/css/*.tmp.css\"", "css-prefix-examples": "postcss --config build/postcss.config.mjs --replace \"site/src/assets/examples/**/*.css\"", - "css-prefix-examples-rtl": "cross-env-shell NODE_ENV=RTL postcss --config build/postcss.config.mjs --dir \"site/src/assets/examples/\" --ext \".rtl.css\" --base \"site/src/assets/examples/\" \"site/src/assets/examples/{blog,carousel,dashboard,cheatsheet}/*.css\" \"!site/src/assets/examples/{blog,carousel,dashboard,cheatsheet}/*.rtl.css\"", "css-test": "jasmine --config=scss/tests/jasmine.js", "js": "npm-run-all js-compile js-minify", "js-compile": "npm-run-all --aggregate-output --parallel js-compile-*", @@ -96,7 +93,6 @@ "netlify": "npm-run-all dist release-sri astro-build", "watch": "npm-run-all --parallel watch-*", "watch-css-main": "nodemon --watch scss/ --ext scss --exec \"npm-run-all css-lint css-compile css-prefix\"", - "watch-css-dist": "nodemon --watch dist/css/ --ext css --ignore \"dist/css/*.rtl.*\" --exec \"npm run css-rtl\"", "watch-css-docs": "nodemon --watch site/src/scss/ --ext scss --exec \"npm run css-lint\"", "watch-css-test": "nodemon --watch scss/ --ext scss,js --exec \"npm run css-test\"", "watch-js-main": "nodemon --watch js/src/ --ext js --exec \"npm-run-all js-lint js-compile\"", @@ -173,7 +169,6 @@ "remark-html": "^16.0.1", "rollup": "^4.53.2", "rollup-plugin-istanbul": "^5.0.0", - "rtlcss": "^4.3.0", "sass": "^1.90.0", "sass-true": "^9.0.0", "shelljs": "^0.10.0", diff --git a/scss/_breadcrumb.scss b/scss/_breadcrumb.scss index f7d1929fba..a296e90f77 100644 --- a/scss/_breadcrumb.scss +++ b/scss/_breadcrumb.scss @@ -52,10 +52,14 @@ $breadcrumb-border-radius: null !default; float: inline-start; // Suppress inline spacings and underlining of the separator padding-inline-end: var(--#{$prefix}breadcrumb-item-padding-x); color: var(--#{$prefix}breadcrumb-divider-color); - content: var(--#{$prefix}breadcrumb-divider, escape-svg($breadcrumb-divider)) #{"/* rtl:"} var(--#{$prefix}breadcrumb-divider, escape-svg($breadcrumb-divider-flipped)) #{"*/"}; + content: var(--#{$prefix}breadcrumb-divider, escape-svg($breadcrumb-divider)); } } + [dir="rtl"] &:not(:first-child)::before { + content: var(--#{$prefix}breadcrumb-divider, escape-svg($breadcrumb-divider-flipped)); + } + &.active { color: var(--#{$prefix}breadcrumb-item-active-color); } diff --git a/scss/_carousel.scss b/scss/_carousel.scss index ebdbe820ab..58897ba899 100644 --- a/scss/_carousel.scss +++ b/scss/_carousel.scss @@ -179,10 +179,19 @@ $carousel-control-icon-filter-dark: invert(1) grayscale(100) !default; } .carousel-control-prev-icon { - background-image: escape-svg($carousel-control-prev-icon-bg) #{"/*rtl:" + escape-svg($carousel-control-next-icon-bg) + "*/"}; + background-image: escape-svg($carousel-control-prev-icon-bg); } + + [dir="rtl"] .carousel-control-prev-icon { + background-image: escape-svg($carousel-control-next-icon-bg); + } + .carousel-control-next-icon { - background-image: escape-svg($carousel-control-next-icon-bg) #{"/*rtl:" + escape-svg($carousel-control-prev-icon-bg) + "*/"}; + background-image: escape-svg($carousel-control-next-icon-bg); + } + + [dir="rtl"] .carousel-control-next-icon { + background-image: escape-svg($carousel-control-prev-icon-bg); } // Optional indicator pips/controls diff --git a/scss/_popover.scss b/scss/_popover.scss index a0dbcb9036..e53b4c6f9f 100644 --- a/scss/_popover.scss +++ b/scss/_popover.scss @@ -107,7 +107,6 @@ $popover-arrow-height: .5rem !default; } } - /* rtl:begin:ignore */ .bs-popover-end { > .popover-arrow { left: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width)); @@ -131,8 +130,6 @@ $popover-arrow-height: .5rem !default; } } - /* rtl:end:ignore */ - .bs-popover-bottom { > .popover-arrow { top: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width)); @@ -166,7 +163,6 @@ $popover-arrow-height: .5rem !default; } } - /* rtl:begin:ignore */ .bs-popover-start { > .popover-arrow { right: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width)); @@ -190,8 +186,6 @@ $popover-arrow-height: .5rem !default; } } - /* rtl:end:ignore */ - .bs-popover-auto { &[data-popper-placement^="top"] { @extend .bs-popover-top; diff --git a/scss/_spinners.scss b/scss/_spinners.scss index b246e74788..aa3b96db6f 100644 --- a/scss/_spinners.scss +++ b/scss/_spinners.scss @@ -32,7 +32,7 @@ $spinner-border-width-sm: .2em !default; // scss-docs-start spinner-border-keyframes @keyframes spinner-border { - to { transform: rotate(360deg) #{"/* rtl:ignore */"}; } + to { transform: rotate(360deg); } } // scss-docs-end spinner-border-keyframes diff --git a/scss/_tooltip.scss b/scss/_tooltip.scss index 2f8788f35e..d08e84dbba 100644 --- a/scss/_tooltip.scss +++ b/scss/_tooltip.scss @@ -86,7 +86,6 @@ $form-feedback-tooltip-border-radius: $tooltip-border-radius !default; } } - /* rtl:begin:ignore */ .bs-tooltip-end .tooltip-arrow { left: calc(-1 * var(--#{$prefix}tooltip-arrow-height)); width: var(--#{$prefix}tooltip-arrow-height); @@ -99,8 +98,6 @@ $form-feedback-tooltip-border-radius: $tooltip-border-radius !default; } } - /* rtl:end:ignore */ - .bs-tooltip-bottom .tooltip-arrow { top: calc(-1 * var(--#{$prefix}tooltip-arrow-height)); @@ -111,7 +108,6 @@ $form-feedback-tooltip-border-radius: $tooltip-border-radius !default; } } - /* rtl:begin:ignore */ .bs-tooltip-start .tooltip-arrow { right: calc(-1 * var(--#{$prefix}tooltip-arrow-height)); width: var(--#{$prefix}tooltip-arrow-height); @@ -124,8 +120,6 @@ $form-feedback-tooltip-border-radius: $tooltip-border-radius !default; } } - /* rtl:end:ignore */ - .bs-tooltip-auto { &[data-popper-placement^="top"] { @extend .bs-tooltip-top; diff --git a/scss/content/_reboot.scss b/scss/content/_reboot.scss index 14e9ecd2f8..e112e3b9e3 100644 --- a/scss/content/_reboot.scss +++ b/scss/content/_reboot.scss @@ -562,19 +562,15 @@ } } - // 1. A few input types should stay LTR + // A few input types should stay LTR regardless of document direction // See https://rtlstyling.com/posts/rtl-styling#form-inputs - // 2. RTL only output - // See https://rtlcss.com/learn/usage-guide/control-directives/#raw - /* rtl:raw: [type="tel"], [type="url"], [type="email"], [type="number"] { direction: ltr; } - */ // Remove the inner padding in Chrome and Safari on macOS. diff --git a/site/src/assets/examples/blog-rtl/index.astro b/site/src/assets/examples/blog-rtl/index.astro index 69f1a9b99c..8553c0f2c9 100644 --- a/site/src/assets/examples/blog-rtl/index.astro +++ b/site/src/assets/examples/blog-rtl/index.astro @@ -1,7 +1,7 @@ --- export const title = 'قالب المدونة' export const direction = 'rtl' -export const extra_css = ['https://fonts.googleapis.com/css?family=Amiri:wght@400;700&display=swap', '../blog/blog.rtl.css'] +export const extra_css = ['https://fonts.googleapis.com/css?family=Amiri:wght@400;700&display=swap', '../blog/blog.css'] import Placeholder from "@shortcodes/Placeholder.astro" --- diff --git a/site/src/assets/examples/blog/blog.css b/site/src/assets/examples/blog/blog.css index 86eedaf1de..e12f4e14bd 100644 --- a/site/src/assets/examples/blog/blog.css +++ b/site/src/assets/examples/blog/blog.css @@ -1,16 +1,29 @@ /* stylelint-disable @stylistic/selector-list-comma-newline-after */ .blog-header-logo { - font-family: "Playfair Display", Georgia, "Times New Roman", serif/*rtl:Amiri, Georgia, "Times New Roman", serif*/; + font-family: "Playfair Display", Georgia, "Times New Roman", serif; font-size: 2.25rem; } +[dir="rtl"] .blog-header-logo { + font-family: Amiri, Georgia, "Times New Roman", serif; +} + .blog-header-logo:hover { text-decoration: none; } h1, h2, h3, h4, h5, h6 { - font-family: "Playfair Display", Georgia, "Times New Roman", serif/*rtl:Amiri, Georgia, "Times New Roman", serif*/; + font-family: "Playfair Display", Georgia, "Times New Roman", serif; +} + +[dir="rtl"] h1, +[dir="rtl"] h2, +[dir="rtl"] h3, +[dir="rtl"] h4, +[dir="rtl"] h5, +[dir="rtl"] h6 { + font-family: Amiri, Georgia, "Times New Roman", serif; } .flex-auto { diff --git a/site/src/assets/examples/blog/blog.rtl.css b/site/src/assets/examples/blog/blog.rtl.css deleted file mode 100644 index bd0c9d1f05..0000000000 --- a/site/src/assets/examples/blog/blog.rtl.css +++ /dev/null @@ -1,39 +0,0 @@ -/* stylelint-disable @stylistic/selector-list-comma-newline-after */ - -.blog-header-logo { - font-family: Amiri, Georgia, "Times New Roman", serif; - font-size: 2.25rem; -} - -.blog-header-logo:hover { - text-decoration: none; -} - -h1, h2, h3, h4, h5, h6 { - font-family: Amiri, Georgia, "Times New Roman", serif; -} - -.flex-auto { - flex: 0 0 auto; -} - -.h-250 { height: 250px; } -@media (min-width: 768px) { - .h-md-250 { height: 250px; } -} - -/* Pagination */ -.blog-pagination { - margin-bottom: 4rem; -} - -/* - * Blog posts - */ -.blog-post { - margin-bottom: 4rem; -} -.blog-post-meta { - margin-bottom: 1.25rem; - color: #727272; -} diff --git a/site/src/assets/examples/carousel-rtl/index.astro b/site/src/assets/examples/carousel-rtl/index.astro index fced29dd01..6a981e4942 100644 --- a/site/src/assets/examples/carousel-rtl/index.astro +++ b/site/src/assets/examples/carousel-rtl/index.astro @@ -1,7 +1,7 @@ --- export const title = 'قالب شرائح العرض' export const direction = 'rtl' -export const extra_css = ['../carousel/carousel.rtl.css'] +export const extra_css = ['../carousel/carousel.css'] import Placeholder from "@shortcodes/Placeholder.astro" --- diff --git a/site/src/assets/examples/carousel/carousel.rtl.css b/site/src/assets/examples/carousel/carousel.rtl.css deleted file mode 100644 index 9ff275d4ea..0000000000 --- a/site/src/assets/examples/carousel/carousel.rtl.css +++ /dev/null @@ -1,74 +0,0 @@ -/* GLOBAL STYLES --------------------------------------------------- */ -/* Padding below the footer and lighter body text */ - -body { - padding-top: 3rem; - padding-bottom: 3rem; - color: rgb(var(--bs-tertiary-color-rgb)); -} - - -/* CUSTOMIZE THE CAROUSEL --------------------------------------------------- */ - -/* Carousel base class */ -.carousel { - margin-bottom: 4rem; -} -/* Since positioning the image, we need to help out the caption */ -.carousel-caption { - bottom: 3rem; - z-index: 10; -} - -/* Declare heights because of positioning of img element */ -.carousel-item { - height: 32rem; -} - - -/* MARKETING CONTENT --------------------------------------------------- */ - -/* Center align the text within the three columns below the carousel */ -.marketing .col-lg-4 { - margin-bottom: 1.5rem; - text-align: center; -} -.marketing .col-lg-4 p { - margin-right: .75rem; - margin-left: .75rem; -} - - -/* Featurettes -------------------------- */ - -.featurette-divider { - margin: 5rem 0; /* Space out the Bootstrap
more */ -} - -/* Thin out the marketing headings */ - -/* RESPONSIVE CSS --------------------------------------------------- */ - -@media (min-width: 40em) { - /* Bump up size of carousel content */ - .carousel-caption p { - margin-bottom: 1.25rem; - font-size: 1.25rem; - line-height: 1.4; - } - - .featurette-heading { - font-size: 50px; - } -} - -@media (min-width: 62em) { - .featurette-heading { - margin-top: 7rem; - } -} diff --git a/site/src/assets/examples/cheatsheet-rtl/index.astro b/site/src/assets/examples/cheatsheet-rtl/index.astro index ce954a0a66..66a2ce8ef7 100644 --- a/site/src/assets/examples/cheatsheet-rtl/index.astro +++ b/site/src/assets/examples/cheatsheet-rtl/index.astro @@ -4,7 +4,7 @@ import { getVersionedDocsPath } from '@libs/path' import Example from '@shortcodes/Example.astro' export const title = 'ورقة الغش' -export const extra_css = ['../cheatsheet/cheatsheet.rtl.css'] +export const extra_css = ['../cheatsheet/cheatsheet.css'] export const extra_js = [{src: '../cheatsheet/cheatsheet.js'}] export const body_class = 'bg-body-tertiary' export const direction = 'rtl' diff --git a/site/src/assets/examples/cheatsheet/cheatsheet.rtl.css b/site/src/assets/examples/cheatsheet/cheatsheet.rtl.css deleted file mode 100644 index 416e39fcaf..0000000000 --- a/site/src/assets/examples/cheatsheet/cheatsheet.rtl.css +++ /dev/null @@ -1,156 +0,0 @@ -body { - scroll-behavior: smooth; -} - -/** - * Bootstrap "Journal code" icon - * @link https://icons.getbootstrap.com/icons/journal-code/ - */ -.bd-heading a::before { - display: inline-block; - width: 1em; - height: 1em; - margin-left: .25rem; - content: ""; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23999' viewBox='0 0 16 16'%3E%3Cpath d='M4 1h8a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2h1a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1H2a2 2 0 0 1 2-2z'/%3E%3Cpath d='M2 5v-.5a.5.5 0 0 1 1 0V5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H2zm0 3v-.5a.5.5 0 0 1 1 0V8h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H2zm0 3v-.5a.5.5 0 0 1 1 0v.5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H2z'/%3E%3Cpath fill-rule='evenodd' d='M8.646 5.646a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1 0 .708l-2 2a.5.5 0 0 1-.708-.708L10.293 8 8.646 6.354a.5.5 0 0 1 0-.708zm-1.292 0a.5.5 0 0 0-.708 0l-2 2a.5.5 0 0 0 0 .708l2 2a.5.5 0 0 0 .708-.708L5.707 8l1.647-1.646a.5.5 0 0 0 0-.708z'/%3E%3C/svg%3E"); - background-size: 1em; -} - -/* stylelint-disable-next-line selector-max-universal */ -.bd-heading + div > * + * { - margin-top: 3rem; -} - -/* Table of contents */ -.bd-aside a { - padding: .1875rem .5rem; - margin-top: .125rem; - margin-right: .3125rem; - color: var(--bs-body-color); -} - -.bd-aside a:hover, -.bd-aside a:focus { - color: var(--bs-body-color); - background-color: rgba(121, 82, 179, .1); -} - -.bd-aside .active { - font-weight: 600; - color: var(--bs-body-color); -} - -.bd-aside .btn { - padding: .25rem .5rem; - font-weight: 600; - color: var(--bs-body-color); -} - -.bd-aside .btn:hover, -.bd-aside .btn:focus { - color: var(--bs-body-color); - background-color: rgba(121, 82, 179, .1); -} - -.bd-aside .btn:focus { - box-shadow: 0 0 0 1px rgba(121, 82, 179, .7); -} - -.bd-aside .btn::before { - width: 1.25em; - line-height: 0; - content: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23ccc' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/%3e%3c/svg%3e"); - transition: transform .35s ease; - transform: rotate(180deg) translateX(-2px); - transform-origin: .5em 50%; -} - -.bd-aside .btn[aria-expanded="true"]::before { - transform: rotate(90deg); -} - - -/* Examples */ -.scrollspy-example { - height: 200px; -} - -[id="modal"] .bd-example .btn, -[id="buttons"] .bd-example .btn, -[id="tooltips"] .bd-example .btn, -[id="popovers"] .bd-example .btn, -[id="dropdowns"] .bd-example .btn-group, -[id="dropdowns"] .bd-example .dropdown, -[id="dropdowns"] .bd-example .dropup, -[id="dropdowns"] .bd-example .dropend, -[id="dropdowns"] .bd-example .dropstart { - margin: 0 0 1rem 1rem; -} - -/* Layout */ -@media (min-width: 1200px) { - body { - display: grid; - grid-template-rows: auto; - grid-template-columns: 1fr 4fr 1fr; - gap: 1rem; - } - - .bd-header { - position: fixed; - top: 0; - right: 0; - left: 0; - z-index: 1030; - grid-column: 1 / span 3; - } - - .bd-aside, - .bd-cheatsheet { - padding-top: 4rem; - } - - /** - * 1. Too bad only Firefox supports subgrids ATM - */ - .bd-cheatsheet, - .bd-cheatsheet section, - .bd-cheatsheet article { - display: inherit; /* 1 */ - grid-template-rows: auto; - grid-template-columns: 1fr 4fr; - grid-column: 1 / span 2; - gap: inherit; /* 1 */ - } - - .bd-aside { - grid-area: 1 / 3; - scroll-margin-top: 4rem; - } - - .bd-cheatsheet section, - .bd-cheatsheet section > h2 { - top: 2rem; - scroll-margin-top: 2rem; - } - - .bd-cheatsheet section > h2::before { - position: absolute; - top: 0; - right: 0; - bottom: -2rem; - left: 0; - z-index: -1; - content: ""; - } - - .bd-cheatsheet article, - .bd-cheatsheet .bd-heading { - top: 8rem; - scroll-margin-top: 8rem; - } - - .bd-cheatsheet .bd-heading { - z-index: 1; - } -} diff --git a/site/src/assets/examples/dashboard-rtl/index.astro b/site/src/assets/examples/dashboard-rtl/index.astro index c5758e95c3..5557e7b4d7 100644 --- a/site/src/assets/examples/dashboard-rtl/index.astro +++ b/site/src/assets/examples/dashboard-rtl/index.astro @@ -1,7 +1,7 @@ --- export const title = 'قالب لوحة القيادة' export const direction = 'rtl' -export const extra_css = ['../dashboard/dashboard.rtl.css'] +export const extra_css = ['../dashboard/dashboard.css'] export const extra_js = [ { src: 'https://cdn.jsdelivr.net/npm/chart.js@4.3.2/dist/chart.umd.js', integrity: 'sha384-eI7PSr3L1XLISH8JdDII5YN/njoSsxfbrkCTnJrzXt+ENP5MOVBxD+l6sEG4zoLp'}, { src: 'dashboard.js'} diff --git a/site/src/assets/examples/dashboard/dashboard.rtl.css b/site/src/assets/examples/dashboard/dashboard.rtl.css deleted file mode 100644 index 5c8a7e2571..0000000000 --- a/site/src/assets/examples/dashboard/dashboard.rtl.css +++ /dev/null @@ -1,48 +0,0 @@ -.bi { - display: inline-block; - width: 1rem; - height: 1rem; -} - -/* - * Sidebar - */ - -@media (min-width: 768px) { - .sidebar .offcanvas-lg { - position: -webkit-sticky; - position: sticky; - top: 48px; - } - .navbar-search { - display: block; - } -} - -.sidebar .nav-link { - font-size: .875rem; - font-weight: 500; -} - -.sidebar .nav-link.active { - color: #2470dc; -} - -.sidebar-heading { - font-size: .75rem; -} - -/* - * Navbar - */ - -.navbar-brand { - padding-top: .75rem; - padding-bottom: .75rem; - background-color: rgba(0, 0, 0, .25); - box-shadow: inset 1px 0 0 rgba(0, 0, 0, .25); -} - -.navbar .form-control { - padding: .75rem 1rem; -} diff --git a/site/src/content/docs/getting-started/contents.mdx b/site/src/content/docs/getting-started/contents.mdx index 6377d7af47..1ad5555e1d 100644 --- a/site/src/content/docs/getting-started/contents.mdx +++ b/site/src/content/docs/getting-started/contents.mdx @@ -19,34 +19,18 @@ bootstrap/ │ ├── bootstrap-grid.css.map │ ├── bootstrap-grid.min.css │ ├── bootstrap-grid.min.css.map -│ ├── bootstrap-grid.rtl.css -│ ├── bootstrap-grid.rtl.css.map -│ ├── bootstrap-grid.rtl.min.css -│ ├── bootstrap-grid.rtl.min.css.map │ ├── bootstrap-reboot.css │ ├── bootstrap-reboot.css.map │ ├── bootstrap-reboot.min.css │ ├── bootstrap-reboot.min.css.map -│ ├── bootstrap-reboot.rtl.css -│ ├── bootstrap-reboot.rtl.css.map -│ ├── bootstrap-reboot.rtl.min.css -│ ├── bootstrap-reboot.rtl.min.css.map │ ├── bootstrap-utilities.css │ ├── bootstrap-utilities.css.map │ ├── bootstrap-utilities.min.css │ ├── bootstrap-utilities.min.css.map -│ ├── bootstrap-utilities.rtl.css -│ ├── bootstrap-utilities.rtl.css.map -│ ├── bootstrap-utilities.rtl.min.css -│ ├── bootstrap-utilities.rtl.min.css.map │ ├── bootstrap.css │ ├── bootstrap.css.map │ ├── bootstrap.min.css -│ ├── bootstrap.min.css.map -│ ├── bootstrap.rtl.css -│ ├── bootstrap.rtl.css.map -│ ├── bootstrap.rtl.min.css -│ └── bootstrap.rtl.min.css.map +│ └── bootstrap.min.css.map └── js/ ├── bootstrap.bundle.js ├── bootstrap.bundle.js.map @@ -62,7 +46,7 @@ bootstrap/ └── bootstrap.min.js.map ``` -This is the most basic form of Bootstrap: compiled files for quick drop-in usage in nearly any web project. We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [Source maps](https://web.dev/articles/source-maps) (`bootstrap.*.map`) are available for use with certain browsers’ developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/docs/v2/). +This is the most basic form of Bootstrap: compiled files for quick drop-in usage in nearly any web project. We provide compiled CSS and JS (`bootstrap.*`), as well as compiled and minified CSS and JS (`bootstrap.min.*`). [Source maps](https://web.dev/articles/source-maps) (`bootstrap.*.map`) are available for use with certain browsers' developer tools. Bundled JS files (`bootstrap.bundle.js` and minified `bootstrap.bundle.min.js`) include [Popper](https://popper.js.org/docs/v2/). All CSS files work for both LTR and RTL layouts thanks to logical properties—simply set `dir="rtl"` on your HTML element. ### CSS files @@ -71,10 +55,10 @@ Bootstrap includes a handful of options for including some or all of our compile | CSS files | Layout | Content | Components | Utilities | | --- | --- | --- | --- | --- | -| `bootstrap.css`
`bootstrap.min.css`
`bootstrap.rtl.css`
`bootstrap.rtl.min.css` | Included | Included | Included | Included | -| `bootstrap-grid.css`
`bootstrap-grid.rtl.css`
`bootstrap-grid.min.css`
`bootstrap-grid.rtl.min.css` | [Only grid system]([[docsref:/layout/grid]]) | — | — | [Only flex utilities]([[docsref:/utilities/flex]]) | -| `bootstrap-utilities.css`
`bootstrap-utilities.rtl.css`
`bootstrap-utilities.min.css`
`bootstrap-utilities.rtl.min.css` | — | — | — | Included | -| `bootstrap-reboot.css`
`bootstrap-reboot.rtl.css`
`bootstrap-reboot.min.css`
`bootstrap-reboot.rtl.min.css` | — | [Only Reboot]([[docsref:/content/reboot]]) | — | — | +| `bootstrap.css`
`bootstrap.min.css` | Included | Included | Included | Included | +| `bootstrap-grid.css`
`bootstrap-grid.min.css` | [Only grid system]([[docsref:/layout/grid]]) | — | — | [Only flex utilities]([[docsref:/utilities/flex]]) | +| `bootstrap-utilities.css`
`bootstrap-utilities.min.css` | — | — | — | Included | +| `bootstrap-reboot.css`
`bootstrap-reboot.min.css` | — | [Only Reboot]([[docsref:/content/reboot]]) | — | — |
### JS files diff --git a/site/src/content/docs/getting-started/rtl.mdx b/site/src/content/docs/getting-started/rtl.mdx index 8c3ce87453..5b72674def 100644 --- a/site/src/content/docs/getting-started/rtl.mdx +++ b/site/src/content/docs/getting-started/rtl.mdx @@ -4,32 +4,72 @@ description: Learn how to enable support for right-to-left text in Bootstrap acr toc: true --- -## Get familiar + + **ProTip:** Get familiar with Bootstrap first with our [getting started guide]([[docsref:/getting-started/introduction]]) before working with RTL. + -We recommend getting familiar with Bootstrap first by reading through our [Getting Started Introduction page]([[docsref:/getting-started/introduction]]). Once you’ve run through it, continue reading here for how to enable RTL. +## How it works -You may also want to read up on [the RTLCSS project](https://rtlcss.com/), as it powers our approach to RTL. +Bootstrap uses **CSS logical properties** throughout the framework to make RTL support native and automatic. Set `dir="rtl"` and add a `lang` attribute, like `lang="ar"`, on your HTML element and the browser will handle the directional layout changes for you. No separate stylesheets are needed. - -**Bootstrap’s RTL feature is still experimental** and will evolve based on user feedback. Spotted something or have an improvement to suggest? [Open an issue]([[config:repo]]/issues/new/choose), we’d love to get your insights. - +Logical properties are CSS properties that adapt to the writing mode and direction of content. Instead of using physical directions like `left` and `right`, they use logical concepts like `start` and `end`: -## Required HTML +- `margin-left` → `margin-inline-start` +- `margin-right` → `margin-inline-end` +- `padding-left` → `padding-inline-start` +- `border-right` → `border-inline-end` -There are two strict requirements for enabling RTL in Bootstrap-powered pages. +When you set `dir="rtl"`, these logical properties automatically flip to match the right-to-left layout without requiring any additional CSS transformations. Only HTML changes are required. -1. Set `dir="rtl"` on the `` element. -2. Add an appropriate `lang` attribute, like `lang="ar"`, on the `` element. +### Required HTML -From there, you’ll need to include an RTL version of our CSS. For example, here’s the stylesheet for our compiled and minified CSS with RTL enabled: +One line of changes to your HTML is all it takes to enable RTL. Bootstrap will then automatically adapt to the RTL direction using logical properties. -```html - +```diff +- ++ ``` +### Live demo + +Toggle between LTR and RTL on this page to see Bootstrap's logical properties in action: + +
+ +
+ +
+ +
+
+ + + ### Starter template -You can see the above requirements reflected in this modified RTL starter template. +Here's a modified RTL starter template: ```html @@ -40,7 +80,7 @@ You can see the above requirements reflected in this modified RTL starter templa - + مرحبًا بالعالم! @@ -65,122 +105,37 @@ You can see the above requirements reflected in this modified RTL starter templa Get started with one of our several [RTL examples]([[docsref:/examples/#rtl]]). -## Approach - -Our approach to building RTL support into Bootstrap comes with two important decisions that impact how we write and use our CSS: - -1. **First, we decided to build it with the [RTLCSS](https://rtlcss.com/) project.** This gives us some powerful features for managing changes and overrides when moving from LTR to RTL. It also allows us to build two versions of Bootstrap from one codebase. - -2. **Second, we’ve renamed a handful of directional classes to adopt a logical properties approach.** Most of you have already interacted with logical properties thanks to our flex utilities—they replace direction properties like `left` and `right` in favor `start` and `end`. That makes the class names and values appropriate for LTR and RTL without any overhead. - - For example, instead of `.ml-3` for `margin-left`, use `.ms-3`. - -Working with RTL, through our source Sass or compiled CSS, shouldn’t be much different from our default LTR though. - -## Customize from source +## Switching directions -When it comes to [customization]([[docsref:/customize/sass]]), the preferred way is to take advantage of variables, maps, and mixins. This approach works the same for RTL, even if it’s post-processed from the compiled files, thanks to [how RTLCSS works](https://rtlcss.com/learn/getting-started/why-rtlcss/). - -### Custom RTL values - -Using [RTLCSS value directives](https://rtlcss.com/learn/usage-guide/value-directives/), you can make a variable output a different value for RTL. For example, to decrease the weight for `$font-weight-bold` throughout the codebase, you may use the `/*rtl: {value}*/` syntax: - -```scss -$font-weight-bold: 700 #{/* rtl:600 */} !default; -``` - -Which would output to the following for our default CSS and RTL CSS: - -```css -/* bootstrap.css */ -dt { - font-weight: 700 /* rtl:600 */; -} - -/* bootstrap.rtl.css */ -dt { - font-weight: 600; -} -``` - -### Alternative font stack - -In the case you’re using a custom font, be aware that not all fonts support the non-Latin alphabet. To switch from Pan-European to Arabic family, you may need to use `/*rtl:insert: {value}*/` in your font stack to modify the names of font families. - -For example, to switch from `Helvetica Neue` font for LTR to `Helvetica Neue Arabic` for RTL, your Sass code could look like this: - -```scss -$font-family-sans-serif: - Helvetica Neue #{"/* rtl:insert:Arabic */"}, - // Cross-platform generic font family (default user interface font) - system-ui, - // Safari for macOS and iOS (San Francisco) - -apple-system, - // Chrome < 56 for macOS (San Francisco) - BlinkMacSystemFont, - // Windows - "Segoe UI", - // Android - Roboto, - // Basic web fallback - Arial, - // Linux - "Noto Sans", - // Sans serif fallback - sans-serif, - // Emoji fonts - "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !default; -``` +**Need both LTR and RTL on the same page?** Wrap your content in containers with different `dir` attributes: -### LTR and RTL at the same time - -Need both LTR and RTL on the same page? Thanks to [RTLCSS String Maps](https://rtlcss.com/learn/usage-guide/string-map/), this is pretty straightforward. Wrap your `@import`s with a class, and set a custom rename rule for RTLCSS: - -```scss -/* rtl:begin:options: { - "autoRename": true, - "stringMap":[ { - "name": "ltr-rtl", - "priority": 100, - "search": ["ltr"], - "replace": ["rtl"], - "options": { - "scope": "*", - "ignoreCase": false - } - } ] -} */ -.ltr { - @import "../node_modules/bootstrap/scss/bootstrap"; -} -/*rtl:end:options*/ +```html + + + + + + + LTR and RTL + + + +
+

This is left-to-right text

+

Content flows from left to right.

+
+ + +
+

هذا نص من اليمين إلى اليسار

+

المحتوى يتدفق من اليمين إلى اليسار.

+
+ + ``` -After running Sass then RTLCSS, each selector in your CSS files will be prepended by `.ltr`, and `.rtl` for RTL files. Now you’re able to use both files on the same page, and simply use `.ltr` or `.rtl` on your components wrappers to use one or the other direction. - - -**Edge cases and known limitations** to consider when working with a combined LTR and RTL implementation: - -1. When switching `.ltr` and `.rtl`, make sure you add `dir` and `lang` attributes accordingly. -2. Loading both files can be a real performance bottleneck: consider some [optimization]([[docsref:/customize/optimize]]), and maybe try to [load one of those files asynchronously](https://www.filamentgroup.com/lab/load-css-simpler/). -3. Nesting styles this way will prevent our `form-validation-state()` mixin from working as intended, thus require you tweak it a bit by yourself. [See #31223](https://github.com/twbs/bootstrap/issues/31223). - - -Do you want to automate this process and address several edge cases involving both directions within a single stylesheet? Then, consider using [PostCSS RTLCSS](https://github.com/elchininet/postcss-rtlcss) as a [PostCSS](https://github.com/postcss/postcss) plugin to process your source files. PostCSS RTLCSS uses [RTLCSS](https://rtlcss.com) behind the scenes to manage the direction flipping process, but it separates the flipped declarations into rules with a different prefix for LTR and RTL, something that allows you to have both directions within the same stylesheet file. By doing this, you can switch between LTR and RTL orientations by simply changing the `dir` of the page (or even by modifying a specific class if you configure the plugin accordingly). - - -**Important things to take into account** when using PostCSS RTLCSS to build a combined LTR and RTL implementation: - -1. It is recommended that you add the `dir` attribute to the `html` element. This way, the entire page will be affected when you change the direction. Also, make sure you add the `lang` attribute accordingly. -2. Having a single bundle with both directions will increase the size of the final stylesheet (on average, by 20%-30%): consider some [optimization]([[docsref:/customize/optimize]]). -3. Take into account that PostCSS RTLCSS is not compatible with `/* rtl:remove */` directives because it doesn’t remove any CSS rule. You should replace your `/* rtl:remove */`, `/* rtl:begin:remove */` and `/* rtl:end:remove */` directives with `/* rtl:freeze */`, `/* rtl:begin:freeze */` and `/* rtl:end:freeze */` directives respectively. These directives will prefix the targeted rules or declarations with the current direction but will not create an RTL counterpart (same result as the `remove` ones in RTLCSS). - - -## The breadcrumb case - -The [breadcrumb separator]([[docsref:/components/breadcrumb]]#dividers) is the only case requiring its own brand-new variable— namely `$breadcrumb-divider-flipped` —defaulting to `$breadcrumb-divider`. - ## Additional resources -- [RTLCSS](https://rtlcss.com/) +- [MDN: CSS Logical Properties and Values](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties) +- [CSS Logical Properties - web.dev](https://web.dev/learn/css/logical-properties/) - [RTL Styling 101](https://rtlstyling.com/posts/rtl-styling) diff --git a/site/src/libs/bootstrap.ts b/site/src/libs/bootstrap.ts index fb12463798..39af96e756 100644 --- a/site/src/libs/bootstrap.ts +++ b/site/src/libs/bootstrap.ts @@ -5,10 +5,6 @@ import { getVersionedDocsPath } from '@libs/path' export function getVersionedBsCssProps(direction: 'rtl' | undefined) { let bsCssLinkHref = '/dist/css/bootstrap' - if (direction === 'rtl') { - bsCssLinkHref = `${bsCssLinkHref}.rtl` - } - if (import.meta.env.PROD) { bsCssLinkHref = `${bsCssLinkHref}.min` } @@ -21,7 +17,7 @@ export function getVersionedBsCssProps(direction: 'rtl' | undefined) { } if (import.meta.env.PROD) { - bsCssLinkProps.integrity = direction === 'rtl' ? getConfig().cdn.css_rtl_hash : getConfig().cdn.css_hash + bsCssLinkProps.integrity = getConfig().cdn.css_hash } return bsCssLinkProps diff --git a/site/src/libs/config.ts b/site/src/libs/config.ts index be01d8550e..d3fb83b759 100644 --- a/site/src/libs/config.ts +++ b/site/src/libs/config.ts @@ -22,9 +22,7 @@ const configSchema = z.object({ blog: z.string().url(), cdn: z.object({ css: z.string().url(), - css_rtl: z.string().url(), css_hash: z.string(), - css_rtl_hash: z.string(), js: z.string().url(), js_hash: z.string(), js_bundle: z.string().url(),