From 163f188f1c2de1efad0b0071d67a81aa6d28956c Mon Sep 17 00:00:00 2001 From: Erbil Date: Mon, 14 Oct 2024 12:40:32 +0300 Subject: [PATCH 1/3] feature(rtl-support): add rtl support for baklava components (#920) --- CONTRIBUTING.md | 10 +++ docs/rtl-support.stories.mdx | 85 +++++++++++++++++++ playground/template.html | 2 +- src/components/alert/bl-alert.css | 6 +- .../checkbox-group/checkbox/bl-checkbox.css | 2 +- src/components/dialog/bl-dialog.css | 4 +- src/components/drawer/bl-drawer.css | 10 +-- src/components/drawer/bl-drawer.ts | 3 +- src/components/input/bl-input.css | 18 ++-- .../notification/bl-notification.css | 15 +++- .../card/bl-notification-card.css | 4 +- src/components/pagination/bl-pagination.css | 4 + src/components/pagination/bl-pagination.ts | 5 +- src/components/popover/bl-popover.css | 2 +- src/components/popover/bl-popover.ts | 10 +-- .../bl-progress-indicator.css | 1 + src/components/select/bl-select.css | 8 +- src/components/spinner/bl-spinner.css | 2 +- .../split-button/bl-split-button.css | 20 ++++- .../split-button/bl-split-button.stories.mdx | 1 + .../split-button/bl-split-button.ts | 9 +- src/components/switch/bl-switch.css | 8 +- src/components/switch/bl-switch.ts | 7 ++ src/components/tab-group/tab/bl-tab.css | 20 ++--- src/components/table/bl-table.stories.mdx | 2 +- .../table/table-cell/bl-table-cell.css | 16 ++-- .../bl-table-header-cell.css | 14 +-- .../table/table-header/bl-table-header.css | 8 +- .../table/table-row/bl-table-row.css | 12 +-- src/components/textarea/bl-textarea.css | 8 +- src/components/tooltip/bl-tooltip.test.ts | 3 +- src/themes/default.css | 47 +++------- src/utilities/direction.test.ts | 41 +++++++++ src/utilities/direction.ts | 22 +++++ 34 files changed, 305 insertions(+), 124 deletions(-) create mode 100644 docs/rtl-support.stories.mdx create mode 100644 src/utilities/direction.test.ts create mode 100644 src/utilities/direction.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b254fde0..8329da8e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,6 +37,16 @@ The Baklava library has an automated release flow, and our release versions and In every push to a PR, we are running automated visual regression tests to be sure we are not breaking any components visual output accidentally. When you are done with your changes and created a PR, if you have some changes that effects visual output of any components, then our automated flow will notice this and block PR for a design review. Design reviews are done by designers involved in project. If the changes are intentional, they will be approved, and once the PR is merged, the new designs will serve as references for upcoming PRs. If there are no visual changes, this step will pass automatically, and there will be no need for a review from a designer. +### Should be RTL-Aware + +Baklava components support Right-to-Left (RTL) text direction, which is crucial for languages written from right to left. When developing or modifying components, ensure proper RTL support by following these guidelines: + +1. Use the `--bl-text-x-direction` CSS custom property for transformations and directional styling. +2. Utilize CSS logical properties (e.g., `inline-start`, `inline-end`, `margin-inline-start`) instead of directional properties. +3. Use `inset` with logical values for positioning. + +For a comprehensive guide on RTL support and detailed examples, please refer to our [Right-to-Left (RTL) documentation](/docs/documentation-right-to-left-rtl--documentation). + ### Enough approvals to merge Every PR should be reviewed and approved by at least one of the core contributors. Please add needed information to PR description to help making review process quicker and easier. diff --git a/docs/rtl-support.stories.mdx b/docs/rtl-support.stories.mdx new file mode 100644 index 00000000..34b90f88 --- /dev/null +++ b/docs/rtl-support.stories.mdx @@ -0,0 +1,85 @@ +import { Meta } from '@storybook/blocks'; + + + +# Right-to-Left (RTL) + +Baklava components support Right-to-Left (RTL) text direction, which is essential for languages that are written from right to left, such as Arabic, Hebrew, and Persian. To enable RTL support in your application using Baklava components, you need to set the `dir` attribute on the `` tag in your project. + +## Enabling Right-to-Left (RTL) + +To enable Right-to-Left (RTL), add the `dir="rtl"` attribute to your HTML tag. Here's how you can do it: + +1. In your HTML file, locate the opening `` tag. +2. Add the `dir="rtl"` attribute to the tag. + +```html + + + + + + RTL Example + + + + + + +``` + + +## Guidelines for Contributors + +When developing or modifying Baklava components, it's crucial to ensure proper RTL support. This includes being aware of RTL text and its implications on layout and styling. For a comprehensive guide on building RTL-aware web applications and websites, refer to [this article](https://hacks.mozilla.org/2015/09/building-rtl-aware-web-apps-and-websites-part-1/). Here are some guidelines to follow: + +1. Use the `--bl-text-x-direction` CSS custom property: + This property helps determine whether the element is in RTL or LTR. You can use it in your CSS like this: + + + ```css + .my-component { + transform: scaleX(var(--bl-text-x-direction)); + box-shadow: calc(8px * var(--bl-text-x-direction)) 0 16px 0 rgb(39 49 66 / 10%); + } + ``` +### + +2. Utilize CSS logical properties: + Instead of using directional properties like `left`, `right`, `margin-left`, etc., use their logical counterparts. This ensures that your components adapt correctly to both LTR and RTL layouts. Here are some examples: + + - Use `inline-start` and `inline-end` instead of `left` and `right` + - Use `block-start` and `block-end` instead of `top` and `bottom` + - Use `margin-inline-start` and `margin-inline-end` instead of `margin-left` and `margin-right` + - Use `padding-inline-start` and `padding-inline-end` instead of `padding-left` and `padding-right` + - Use `border-inline-start` and `border-inline-end` instead of `border-left` and `border-right` + + ### + + ```css + .my-component { + margin-inline-start: 1rem; + padding-inline-end: 0.5rem; + border-inline-start: 1px solid #ccc; + } + ``` + + ### + + For more information on CSS logical properties and values, please refer to the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values). + +### + +3. Use `inset` for positioning: + When using absolute or relative positioning, use the `inset` property with logical values: + + ```css + .positioned-element { + position: absolute; + inset-inline-start: 0; + inset-block-start: 0; + } + ``` + +By following these guidelines, you'll ensure that Baklava components work seamlessly in both LTR and RTL layouts, providing a consistent user experience across different language settings. + diff --git a/playground/template.html b/playground/template.html index ed952a0c..0628cba1 100644 --- a/playground/template.html +++ b/playground/template.html @@ -34,4 +34,4 @@

Baklava Playground

Baklava is ready - + \ No newline at end of file diff --git a/src/components/alert/bl-alert.css b/src/components/alert/bl-alert.css index db8c2f2f..472c4e16 100644 --- a/src/components/alert/bl-alert.css +++ b/src/components/alert/bl-alert.css @@ -16,7 +16,7 @@ box-shadow: inset 0 0 0 1px var(--main-color); border-radius: var(--bl-border-radius-l); padding: calc(var(--padding) / 2) var(--padding); - padding-right: calc(var(--padding) / 2); + padding-inline-end: calc(var(--padding) / 2); } .description { @@ -33,14 +33,14 @@ .content { display: flex; - margin-right: var(--bl-size-2xs); + margin-inline-end: var(--bl-size-2xs); flex: 20 1 70%; padding: calc(var(--padding) / 2) 0; } .icon { padding: calc(var(--padding) / 2) 0; - margin-right: var(--bl-size-2xs); + margin-inline-end: var(--bl-size-2xs); color: var(--main-color); } diff --git a/src/components/checkbox-group/checkbox/bl-checkbox.css b/src/components/checkbox-group/checkbox/bl-checkbox.css index 13b2f4e4..1e932e2b 100644 --- a/src/components/checkbox-group/checkbox/bl-checkbox.css +++ b/src/components/checkbox-group/checkbox/bl-checkbox.css @@ -64,7 +64,7 @@ input[type="checkbox"] { .required-suffix { color: var(--bl-color-danger); - margin-left: calc(var(--bl-size-2xs) * -1); + margin-inline-start: calc(var(--bl-size-2xs) * -1); } .dirty.invalid .check-mark { diff --git a/src/components/dialog/bl-dialog.css b/src/components/dialog/bl-dialog.css index dc55111d..24612866 100644 --- a/src/components/dialog/bl-dialog.css +++ b/src/components/dialog/bl-dialog.css @@ -36,7 +36,7 @@ .dialog-polyfill { display: none; position: fixed; - left: 0; + inset-inline-start: 0; top: 0; width: 100vw; height: 100vh; @@ -59,7 +59,7 @@ header { } header bl-button { - margin-left: auto; + margin-inline-start: auto; } header h2 { diff --git a/src/components/drawer/bl-drawer.css b/src/components/drawer/bl-drawer.css index 4248e9f4..757dfbcc 100644 --- a/src/components/drawer/bl-drawer.css +++ b/src/components/drawer/bl-drawer.css @@ -3,12 +3,12 @@ position: fixed; display: flex; top: 0; - right: -100%; bottom: 0; + inset-inline-end: 0; width: var(--bl-drawer-current-width, 424px); padding: var(--bl-size-xl); padding-top: max(env(safe-area-inset-top), var(--bl-size-xl)); - padding-right: max(env(safe-area-inset-right), var(--bl-size-xl)); + padding-inline-end: max(env(safe-area-inset-right), var(--bl-size-xl)); padding-bottom: max(env(safe-area-inset-bottom), var(--bl-size-xl)); background: var(--bl-color-neutral-full); box-shadow: var(--bl-size-xs) 0 var(--bl-size-2xl) rgba(0 0 0 / 50%); @@ -16,10 +16,6 @@ z-index: var(--bl-index-sticky); } -:host([open]) .drawer { - right: 0; -} - iframe { height: 100%; width: 100%; @@ -45,7 +41,7 @@ header { header .header-buttons { display: flex; gap: var(--bl-size-xl); - margin-left: auto; + margin-inline-start: auto; } header h2 { diff --git a/src/components/drawer/bl-drawer.ts b/src/components/drawer/bl-drawer.ts index 32a04348..64bef82f 100644 --- a/src/components/drawer/bl-drawer.ts +++ b/src/components/drawer/bl-drawer.ts @@ -1,5 +1,4 @@ -import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { PropertyValues } from "lit"; +import { CSSResultGroup, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators.js"; import { event, EventDispatcher } from "../../utilities/event"; import { styleToPixelConverter } from "../../utilities/style-to-px.converter"; diff --git a/src/components/input/bl-input.css b/src/components/input/bl-input.css index c8d9a452..eb18f49f 100644 --- a/src/components/input/bl-input.css +++ b/src/components/input/bl-input.css @@ -104,14 +104,14 @@ label { transition: all ease-in 0.1s; font: var(--input-font); top: var(--padding-vertical); - left: var(--bl-input-padding-start, var(--padding-horizontal)); - right: var(--bl-input-padding-end, var(--padding-horizontal)); + inset-inline-start: var(--bl-input-padding-start, var(--padding-horizontal)); + inset-inline-end: var(--bl-input-padding-end, var(--padding-horizontal)); pointer-events: none; color: var(--bl-color-neutral-light); } .has-icon label { - right: calc( + inset-inline-end: calc( var(--bl-input-padding-end, var(--padding-horizontal)) + var(--icon-size) + var(--padding-vertical) ); @@ -147,7 +147,7 @@ input::-webkit-credentials-auto-fill-button { } :where(.wrapper:focus-within, .wrapper.has-value) input { - padding-left: var(--label-padding); + padding-inline-start: var(--label-padding); } input:disabled { @@ -189,7 +189,7 @@ input:-webkit-autofill { flex-basis: var(--icon-size); align-self: center; height: var(--icon-size); - margin-right: var(--label-padding); + margin-inline-end: var(--label-padding); } bl-icon:not(.reveal-icon), @@ -263,8 +263,12 @@ bl-icon[name="eye_on"] { :host(:not([label-fixed])) :focus-within label, :host(:not([label-fixed])) .has-value label { top: 0; - left: calc(var(--bl-input-padding-start, var(--padding-horizontal)) - var(--label-padding)); - right: calc(var(--bl-input-padding-end, var(--padding-horizontal)) - var(--label-padding)); + inset-inline-start: calc( + var(--bl-input-padding-start, var(--padding-horizontal)) - var(--label-padding) + ); + inset-inline-end: calc( + var(--bl-input-padding-end, var(--padding-horizontal)) - var(--label-padding) + ); transform: translateY(-50%); font: var(--bl-font-caption); color: var(--bl-color-neutral-dark); diff --git a/src/components/notification/bl-notification.css b/src/components/notification/bl-notification.css index 727a0688..5faed62e 100644 --- a/src/components/notification/bl-notification.css +++ b/src/components/notification/bl-notification.css @@ -11,7 +11,7 @@ flex-direction: column-reverse; position: fixed; top: 0; - right: 0; + inset-inline-end: 0; max-width: var(--bl-notification-width); margin: var(--margin); width: calc(100% - var(--margin) * 2); @@ -32,6 +32,10 @@ touch-action: none; } +:host(:has([dir="rtl"])) .notification { + animation: slide-in-left var(--bl-notification-animation-duration) ease; +} + .notification[data-slide="top"] { animation: slide-in-top var(--bl-notification-animation-duration) ease; } @@ -75,6 +79,15 @@ } } +@keyframes slide-in-left { + from { + transform: translateX(var(--travel-distance, -10px)); + height: 0; + opacity: 0; + margin: 0; + } +} + @keyframes slide-out-right { to { transform: translateX(var(--travel-distance, 10px)); diff --git a/src/components/notification/card/bl-notification-card.css b/src/components/notification/card/bl-notification-card.css index 1d6b80e2..2ee0f5dc 100644 --- a/src/components/notification/card/bl-notification-card.css +++ b/src/components/notification/card/bl-notification-card.css @@ -10,8 +10,8 @@ .duration { position: absolute; - left: 0; - right: 0; + inset-inline-start: 0; + inset-inline-end: 0; bottom: 0; height: var(--bl-size-2xs); width: 100%; diff --git a/src/components/pagination/bl-pagination.css b/src/components/pagination/bl-pagination.css index 35d6d6db..c999deca 100644 --- a/src/components/pagination/bl-pagination.css +++ b/src/components/pagination/bl-pagination.css @@ -17,6 +17,10 @@ align-items: center; } +:host([dir="rtl"]) .page-container :is(bl-button.next, bl-button.previous) { + transform: rotate(180deg); +} + .page-list { display: flex; align-items: center; diff --git a/src/components/pagination/bl-pagination.ts b/src/components/pagination/bl-pagination.ts index 1cee4658..54c41b12 100644 --- a/src/components/pagination/bl-pagination.ts +++ b/src/components/pagination/bl-pagination.ts @@ -1,7 +1,8 @@ -import { CSSResultGroup, html, LitElement, TemplateResult, PropertyValues } from "lit"; +import { CSSResultGroup, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import { localized, msg } from "@lit/localize"; +import { setDirectionProperty } from "../../utilities/direction"; import { event, EventDispatcher } from "../../utilities/event"; import "../button/bl-button"; import "../input/bl-input"; @@ -103,6 +104,8 @@ export default class BlPagination extends LitElement { setTimeout(() => { window?.addEventListener("resize", () => this._paginate()); }); + + setDirectionProperty(this); } disconnectedCallback() { diff --git a/src/components/popover/bl-popover.css b/src/components/popover/bl-popover.css index 44307d5a..955924f7 100644 --- a/src/components/popover/bl-popover.css +++ b/src/components/popover/bl-popover.css @@ -45,7 +45,7 @@ transform: rotate(var(--arrow-rotation)); border: var(--border-size) solid var(--border-color); border-bottom: none; - border-right: none; + border-inline-end: none; } .popover[data-placement*="bottom"] .arrow { diff --git a/src/components/popover/bl-popover.ts b/src/components/popover/bl-popover.ts index a783e1f2..5466053d 100644 --- a/src/components/popover/bl-popover.ts +++ b/src/components/popover/bl-popover.ts @@ -2,16 +2,16 @@ import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; import { + arrow, + autoUpdate, computePosition, flip, - shift, - offset, - arrow, inline, - autoUpdate, - size, Middleware, MiddlewareState, + offset, + shift, + size, } from "@floating-ui/dom"; import { getTarget } from "../../utilities/elements"; import { event, EventDispatcher } from "../../utilities/event"; diff --git a/src/components/progress-indicator/bl-progress-indicator.css b/src/components/progress-indicator/bl-progress-indicator.css index b88d7cd9..6c4872af 100644 --- a/src/components/progress-indicator/bl-progress-indicator.css +++ b/src/components/progress-indicator/bl-progress-indicator.css @@ -10,6 +10,7 @@ height: var(--height); border-radius: var(--radius); width: 100%; + transform: scaleX(var(--bl-text-x-direction)); } .progress-indicator::before { diff --git a/src/components/select/bl-select.css b/src/components/select/bl-select.css index 030de4ab..57444a00 100644 --- a/src/components/select/bl-select.css +++ b/src/components/select/bl-select.css @@ -81,7 +81,7 @@ .label, .placeholder { color: var(--placeholder-color); - padding-left: var(--label-padding); + padding-inline-start: var(--label-padding); white-space: nowrap; max-width: 100%; overflow: hidden; @@ -153,7 +153,7 @@ .select-input .selected-options { padding: 0; - padding-left: var(--label-padding); + padding-inline-start: var(--label-padding); margin: 0; list-style: none; flex: 1; @@ -193,7 +193,7 @@ align-items: center; justify-content: center; gap: var(--bl-size-4xs); - margin-left: var(--bl-size-2xs); + margin-inline-start: var(--bl-size-2xs); } .popover { @@ -253,7 +253,7 @@ label { display: block; top: var(--padding-vertical); left: var(--padding-horizontal); - right: calc(var(--bl-size-2xs) + var(--bl-size-m) + var(--bl-size-2xs)); + inset-inline-end: calc(var(--bl-size-2xs) + var(--bl-size-m) + var(--bl-size-2xs)); transition: all ease-in 0.1s; pointer-events: none; opacity: 0; diff --git a/src/components/spinner/bl-spinner.css b/src/components/spinner/bl-spinner.css index 9927a53a..8743f5b3 100644 --- a/src/components/spinner/bl-spinner.css +++ b/src/components/spinner/bl-spinner.css @@ -11,7 +11,7 @@ :host([overlay]) { position: absolute; top: 0; - left: 0; + inset-inline-start: 0; width: 100%; height: 100%; display: flex; diff --git a/src/components/split-button/bl-split-button.css b/src/components/split-button/bl-split-button.css index 6f864aa4..14f37d8f 100644 --- a/src/components/split-button/bl-split-button.css +++ b/src/components/split-button/bl-split-button.css @@ -25,6 +25,10 @@ --bl-border-radius-m: calc(var(--bl-size-xs) / 2) 0 0 calc(var(--bl-size-xs) / 2); } +:host([dir="rtl"]) .split-main-button { + --bl-border-radius-m: 0 calc(var(--bl-size-xs) / 2) calc(var(--bl-size-xs) / 2) 0; +} + .split-main-button:focus { --bl-border-radius-l: calc(var(--bl-size-m) / 2) 0 0 calc(var(--bl-size-m) / 2); } @@ -33,16 +37,28 @@ --bl-border-radius-m: 0 calc(var(--bl-size-xs) / 2) calc(var(--bl-size-xs) / 2) 0; } +:host([dir="rtl"]) .dropdown-button { + --bl-border-radius-m: calc(var(--bl-size-xs) / 2) 0 0 calc(var(--bl-size-xs) / 2); +} + .dropdown-button:focus { --bl-border-radius-l: 0 calc(var(--bl-size-m) / 2) calc(var(--bl-size-m) / 2) 0; } +:host([variant="secondary"][dir="rtl"]) .dropdown-button { + inset-inline-end: -1px; +} + :host([variant="secondary"]) .dropdown-button { - left: -1px; + inset-inline-start: -1px; } :host([dropdown-disabled][variant="secondary"]) .dropdown-button { - left: 0; + inset-inline-start: 0; +} + +:host([dropdown-disabled][variant="secondary"][dir="rtl"]) .dropdown-button { + inset-inline-end: 0; } .split-divider { diff --git a/src/components/split-button/bl-split-button.stories.mdx b/src/components/split-button/bl-split-button.stories.mdx index 46ba218f..7d80ea47 100644 --- a/src/components/split-button/bl-split-button.stories.mdx +++ b/src/components/split-button/bl-split-button.stories.mdx @@ -53,6 +53,7 @@ export const SingleSplitButtonTemplate = (args) => html` ${args.content || 'Action 1'} diff --git a/src/components/split-button/bl-split-button.ts b/src/components/split-button/bl-split-button.ts index 9f210831..1eb18a0a 100644 --- a/src/components/split-button/bl-split-button.ts +++ b/src/components/split-button/bl-split-button.ts @@ -1,12 +1,11 @@ import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { customElement, property, state, query } from "lit/decorators.js"; +import { customElement, property, query, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import { ReferenceElement } from "@floating-ui/core"; +import { setDirectionProperty } from "../../utilities/direction"; import { event, EventDispatcher } from "../../utilities/event"; import "../button/bl-button"; -import { TargetType } from "../button/bl-button"; -import { ButtonSize, ButtonVariant, ButtonKind } from "../button/bl-button"; -import BlButton from "../button/bl-button"; +import BlButton, { ButtonKind, ButtonSize, ButtonVariant, TargetType } from "../button/bl-button"; import BlDropdownItem, { blDropdownItemTag } from "../dropdown/item/bl-dropdown-item"; import { BaklavaIcon } from "../icon/icon-list"; import BlPopover from "../popover/bl-popover"; @@ -141,6 +140,8 @@ export default class BlSplitButton extends LitElement { super.connectedCallback(); this.addEventListener("keydown", this.handleKeyDown); + + setDirectionProperty(this); } disconnectedCallback() { diff --git a/src/components/switch/bl-switch.css b/src/components/switch/bl-switch.css index 8d821082..394802a7 100644 --- a/src/components/switch/bl-switch.css +++ b/src/components/switch/bl-switch.css @@ -33,7 +33,7 @@ span { background-color: white; border-radius: var(--bl-border-radius-circle); height: var(--thumb-height); - left: var(--thumb-offset); + inset-inline-start: var(--thumb-offset); position: relative; top: var(--thumb-offset); transition: transform; @@ -77,6 +77,12 @@ label { ); } +:host([checked][dir="rtl"]) .switch::before { + transform: translateX( + calc(calc(2 * var(--thumb-offset)) - var(--track-width) + var(--thumb-width)) + ); +} + :host([disabled]) .switch { opacity: 0.5; } diff --git a/src/components/switch/bl-switch.ts b/src/components/switch/bl-switch.ts index c58705a1..8b49dd46 100644 --- a/src/components/switch/bl-switch.ts +++ b/src/components/switch/bl-switch.ts @@ -1,6 +1,7 @@ import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; +import { setDirectionProperty } from "../../utilities/direction"; import { event, EventDispatcher } from "../../utilities/event"; import style from "./bl-switch.css"; @@ -44,6 +45,12 @@ export default class BlSwitch extends LitElement { this.onToggle(this.checked); } + connectedCallback(): void { + super.connectedCallback(); + + setDirectionProperty(this); + } + private handleKeyDown(event: KeyboardEvent) { if (event.code === "Enter" || event.code === "Space") { this.toggle(); diff --git a/src/components/tab-group/tab/bl-tab.css b/src/components/tab-group/tab/bl-tab.css index 7e859a2d..ad025b3f 100644 --- a/src/components/tab-group/tab/bl-tab.css +++ b/src/components/tab-group/tab/bl-tab.css @@ -26,20 +26,20 @@ width: max-content; height: var(--tab-height); padding: 0 var(--tab-right-padding); - margin-right: 1px; + margin-inline-end: 1px; } .container::after { position: absolute; content: ""; - right: 0; + inset-inline-end: 0; top: var(--bl-size-m); height: calc(100% - var(--bl-size-2xl)); - border-right: 1px solid var(--bl-color-neutral-lighter); + border-inline-end: 1px solid var(--bl-color-neutral-lighter); } :host(:last-of-type) .container::after { - border-right: none; + border-inline-end: none; } :host(:focus-visible) { @@ -58,7 +58,7 @@ position: absolute; opacity: 0; bottom: calc(-1 * var(--bl-size-4xs)); - left: var(--border-left-space); + inset-inline-start: var(--border-left-space); width: calc(100% - var(--bl-size-4xl)); border-bottom: var(--border-bottom-width) solid var(--bl-color-primary); } @@ -94,7 +94,7 @@ } :host([help-text]) button { - padding-right: 0; + padding-inline-end: 0; } .tab-button { @@ -109,7 +109,7 @@ height: 100%; font-size: var(--bl-font-size-m); pointer-events: visible; - padding-right: var(--tab-right-padding); + padding-inline-end: var(--tab-right-padding); } bl-tooltip { @@ -137,7 +137,7 @@ bl-tooltip { } .badge-container { - padding-left: var(--bl-size-3xs); + padding-inline-start: var(--bl-size-3xs); display: flex; margin-bottom: 1px; } @@ -154,7 +154,7 @@ bl-tooltip { display: flex; color: var(--icon-color); font-size: var(--bl-font-size-l); - margin-right: var(--bl-size-3xs); + margin-inline-end: var(--bl-size-3xs); margin-bottom: 1px; } @@ -163,7 +163,7 @@ bl-tooltip { height: var(--bl-size-2xs); width: var(--bl-size-2xs); border-radius: var(--bl-size-3xs); - margin-left: var(--bl-size-3xs); + margin-inline-start: var(--bl-size-3xs); background-color: var(--bl-color-danger); margin-bottom: 1px; } diff --git a/src/components/table/bl-table.stories.mdx b/src/components/table/bl-table.stories.mdx index 652881c3..a5ed2d2e 100644 --- a/src/components/table/bl-table.stories.mdx +++ b/src/components/table/bl-table.stories.mdx @@ -78,7 +78,7 @@ export const TableTemplate = (args, {id}) => { > ${header.text} ${header.tooltip && - html`
+ html`
${header.tooltip} diff --git a/src/components/table/table-cell/bl-table-cell.css b/src/components/table/table-cell/bl-table-cell.css index c01cac3a..28f4ba0b 100644 --- a/src/components/table/table-cell/bl-table-cell.css +++ b/src/components/table/table-cell/bl-table-cell.css @@ -10,7 +10,7 @@ background-color: var(--bl-color-neutral-full); background-clip: padding-box; border-top: none; - border-right: none; + border-inline-end: none; } .table-cell { @@ -21,27 +21,27 @@ .table-cell.shadow-right::before { content: ""; position: absolute; - right: -1px; + inset-inline-end: -1px; top: 0; width: 16px; height: 100%; z-index: -1; - border-right: 1px solid var(--bl-color-neutral-lighter); - box-shadow: 8px 0 16px 0 rgb(39 49 66 / 10%); + border-inline-end: 1px solid var(--bl-color-neutral-lighter); + box-shadow: calc(8px * var(--bl-text-x-direction)) 0 16px 0 rgb(39 49 66 / 10%); } .table-cell.shadow-left::before { content: ""; position: absolute; - left: -1px; + inset-inline-start: -1px; top: 0; width: 16px; height: 100%; z-index: -1; - border-left: 1px solid var(--bl-color-neutral-lighter); - box-shadow: -8px 0 16px 0 rgb(39 49 66 / 10%); + border-inline-start: 1px solid var(--bl-color-neutral-lighter); + box-shadow: calc(-8px * var(--bl-text-x-direction)) 0 16px 0 rgb(39 49 66 / 10%); } bl-checkbox { - margin-right: var(--bl-size-m); + margin-inline-end: var(--bl-size-m); } diff --git a/src/components/table/table-header-cell/bl-table-header-cell.css b/src/components/table/table-header-cell/bl-table-header-cell.css index 2388a5b5..f61bed08 100644 --- a/src/components/table/table-header-cell/bl-table-header-cell.css +++ b/src/components/table/table-header-cell/bl-table-header-cell.css @@ -24,29 +24,29 @@ .table-header-cell.shadow-right::before { content: ""; position: absolute; - right: -1px; + inset-inline-end: -1px; top: 0; width: 16px; height: 100%; z-index: -1; - border-right: 1px solid var(--bl-color-neutral-lighter); - box-shadow: 8px 0 16px 0 rgb(39 49 66 / 10%); + border-inline-end: 1px solid var(--bl-color-neutral-lighter); + box-shadow: calc(8px * var(--bl-text-x-direction)) 0 16px 0 rgb(39 49 66 / 10%); } .table-header-cell.shadow-left::before { content: ""; position: absolute; - left: -1px; + inset-inline-start: -1px; top: 0; width: 16px; height: 100%; z-index: -1; - border-left: 1px solid var(--bl-color-neutral-lighter); - box-shadow: -8px 0 16px 0 rgb(39 49 66 / 10%); + border-inline-start: 1px solid var(--bl-color-neutral-lighter); + box-shadow: calc(-8px * var(--bl-text-x-direction)) 0 16px 0 rgb(39 49 66 / 10%); } bl-checkbox { - margin-right: var(--bl-size-m); + margin-inline-end: var(--bl-size-m); } .sort-icons-wrapper { diff --git a/src/components/table/table-header/bl-table-header.css b/src/components/table/table-header/bl-table-header.css index 23e873a8..3eb59e7b 100644 --- a/src/components/table/table-header/bl-table-header.css +++ b/src/components/table/table-header/bl-table-header.css @@ -5,8 +5,8 @@ :host([sticky]) { position: sticky; top: 0; - left: 0; - right: 0; + inset-inline-start: 0; + inset-inline-end: 0; z-index: 3; transition: top 0.05s ease; box-shadow: 0 8px 16px 0 rgb(39 49 66 / 10%); @@ -17,8 +17,8 @@ display: block; position: absolute; bottom: 0; - left: 0; - right: 0; + inset-inline-start: 0; + inset-inline-end: 0; height: 1px; background: var(--bl-color-neutral-lighter); } diff --git a/src/components/table/table-row/bl-table-row.css b/src/components/table/table-row/bl-table-row.css index c18b5b30..92ac84eb 100644 --- a/src/components/table/table-row/bl-table-row.css +++ b/src/components/table/table-row/bl-table-row.css @@ -19,16 +19,16 @@ } :host ::slotted(*:first-child) { - border-left: none; + border-inline-start: none; } :host ::slotted(*:last-child) { - border-right: none; + border-inline-end: none; } :host(:first-child) ::slotted(bl-table-header-cell) { border-top: none; - border-right: none; + border-inline-end: none; } :host(:first-child) ::slotted(bl-table-header-cell:first-child) { @@ -37,7 +37,7 @@ :host(:first-child) ::slotted(bl-table-header-cell:last-child) { border-top-right-radius: var(--bl-size-3xs); - border-right: 1px; + border-inline-end: 1px; } :host(:last-child) ::slotted(bl-table-cell) { @@ -60,12 +60,12 @@ :host([sticky-first-column]) ::slotted(bl-table-cell:first-child) { position: sticky; z-index: 2; - left: 0; + inset-inline-start: 0; } :host([sticky-last-column]) ::slotted(bl-table-header-cell:last-child), :host([sticky-last-column]) ::slotted(bl-table-cell:last-child) { position: sticky; z-index: 2; - right: 0; + inset-inline-end: 0; } diff --git a/src/components/textarea/bl-textarea.css b/src/components/textarea/bl-textarea.css index 89f88cc1..ccd8b40c 100644 --- a/src/components/textarea/bl-textarea.css +++ b/src/components/textarea/bl-textarea.css @@ -120,8 +120,8 @@ label { transition: all ease-in 0.1s; font: var(--bl-font-title-3-regular); top: var(--padding-vertical); - left: var(--padding-horizontal); - right: var(--padding-horizontal); + inset-inline-start: var(--padding-horizontal); + inset-inline-end: var(--padding-horizontal); pointer-events: none; color: var(--bl-color-neutral-light); } @@ -141,7 +141,7 @@ label { :where(.wrapper:focus-within, .wrapper.has-value) label { top: 0; - left: var(--padding-horizontal); + inset-inline-start: var(--padding-horizontal); transform: translateY(-50%); font: var(--bl-font-caption); color: var(--bl-color-neutral-dark); @@ -199,7 +199,7 @@ label { .counter-text { color: var(--bl-color-neutral-dark); - margin-left: auto; + margin-inline-start: auto; } :where(.max-len-invalid, .dirty.invalid) .hint > .counter-text { diff --git a/src/components/tooltip/bl-tooltip.test.ts b/src/components/tooltip/bl-tooltip.test.ts index 55add7bc..27a559a9 100644 --- a/src/components/tooltip/bl-tooltip.test.ts +++ b/src/components/tooltip/bl-tooltip.test.ts @@ -2,8 +2,8 @@ import { assert, elementUpdated, expect, fixture, html, oneEvent } from "@open-w import { sendKeys, sendMouse } from "@web/test-runner-commands"; import { getMiddleOfElement } from "../../utilities/elements"; import type typeOfBlPopover from "../popover/bl-popover"; -import BlTooltip from "./bl-tooltip"; import type typeOfBlTooltip from "./bl-tooltip"; +import BlTooltip from "./bl-tooltip"; describe("bl-tooltip", () => { it("should be defined tooltip instance", () => { @@ -164,7 +164,6 @@ describe("bl-tooltip", () => { setTimeout(() => { sendMouse({ type: "move", position: [bodyX, bodyY] }); }); - //then const ev = await oneEvent(el, "bl-tooltip-hide"); diff --git a/src/themes/default.css b/src/themes/default.css index ba03fad5..c77d634f 100644 --- a/src/themes/default.css +++ b/src/themes/default.css @@ -1,6 +1,9 @@ @import url("@fontsource-variable/rubik"); :root { + /* Direction */ + --bl-text-x-direction: 1; + /* Primary Color */ --bl-color-primary: #f27a1a; --bl-color-primary-highlight: #ef6114; @@ -214,44 +217,14 @@ --bl-font-caption-line-height: calc(var(--bl-font-caption-font-size) + var(--bl-size-4xs)); --bl-font-caption-size: var(--bl-font-caption-font-size) / var(--bl-font-caption-line-height); --bl-font-caption: var(--bl-font-weight-medium) var(--bl-font-caption-size) var(--bl-font-family); +} - /* - @DEPRECATED - - Color names below is kept for backward compatibility, - - Will be removed in V3 - */ - --bl-color-primary-hover: var(--bl-color-primary-highlight); - --bl-color-success-hover: var(--bl-color-success-highlight); - --bl-color-danger-hover: var(--bl-color-danger-highlight); - --bl-color-warning-hover: var(--bl-color-warning-highlight); - --bl-color-accent-primary-background: var(--bl-color-primary-contrast); - --bl-color-success-background: var(--bl-color-success-contrast); - --bl-color-danger-background: var(--bl-color-danger-contrast); - --bl-color-warning-background: var(--bl-color-warning-contrast); - --bl-color-primary-background: var(--bl-color-neutral-full); +:root[dir="rtl"] { + /* Direction */ + direction: rtl; - /* Content Colors */ - --bl-color-content-primary: var(--bl-color-neutral-darker); - --bl-color-content-secondary: var(--bl-color-neutral-dark); - --bl-color-content-tertiary: var(--bl-color-neutral-light); - --bl-color-content-passive: var(--bl-color-neutral-lighter); - --bl-color-content-primary-contrast: var(--bl-color-neutral-full); - --bl-color-content-hover: var(--bl-color-primary-highlight); - --bl-color-border: var(--bl-color-neutral-lighter); + --bl-text-x-direction: -1; - /* Removed Colors */ - --bl-color-secondary: #273142; - --bl-color-secondary-hover: #0f131a; - --bl-color-secondary-background: var(--bl-color-neutral-lightest); - --bl-color-tertiary: #f1f2f7; - --bl-color-tertiary-hover: #d5d9e1; - --bl-color-alternative: #5794ff; - --bl-color-alternative-hover: #457eff; - --bl-color-featured: #8c4eff; - --bl-color-featured-hover: #753eff; - --bl-color-tertiary-background: #f7f7fa; - --bl-color-alternative-background: #eef4ff; - --bl-color-featured-background: #f4edff; + /* Typography */ + --bl-font-family: "Cairo"; } diff --git a/src/utilities/direction.test.ts b/src/utilities/direction.test.ts new file mode 100644 index 00000000..99e1455b --- /dev/null +++ b/src/utilities/direction.test.ts @@ -0,0 +1,41 @@ +import { expect } from "@open-wc/testing"; +import { getDirection, setDirectionProperty } from "./direction"; + +describe("getDirection", () => { + it("returns empty string when the document element has no computed direction", () => { + const originalGetComputedStyle = window.getComputedStyle as typeof window.getComputedStyle; + + window.getComputedStyle = () => + ({ getPropertyValue: () => "" } as unknown as CSSStyleDeclaration); + + expect(getDirection()).to.equal(""); + + + window.getComputedStyle = originalGetComputedStyle; + }); + + it("returns the computed direction property of the document element", () => { + const originalGetComputedStyle = window.getComputedStyle as typeof window.getComputedStyle; + + window.getComputedStyle = () => + ({ getPropertyValue: () => "rtl" } as unknown as CSSStyleDeclaration); + + expect(getDirection()).to.equal("rtl"); + + window.getComputedStyle = originalGetComputedStyle; + }); +}); + +describe("setDirectionProperty", () => { + it("sets the direction property on the element based on the computed style", () => { + const element = document.createElement("div"); + + setDirectionProperty(element); + + expect(element.getAttribute("dir")).to.equal(getDirection()); + }); + + it("throws an error if the element is not an instance of Element", () => { + expect(() => setDirectionProperty({} as unknown as HTMLElement)).to.throw(TypeError); + }); +}); diff --git a/src/utilities/direction.ts b/src/utilities/direction.ts new file mode 100644 index 00000000..1e35caa3 --- /dev/null +++ b/src/utilities/direction.ts @@ -0,0 +1,22 @@ +type Direction = "ltr" | "rtl" | "null" | ""; + +/** + * Gets the computed direction property of the document element. + * + * @returns The direction property of the document element, as "ltr", "rtl", or "null". + */ +export function getDirection(): Direction { + const direction = getComputedStyle(document.documentElement).getPropertyValue("direction"); + + return direction as Direction; +} + +/** + * Sets the direction property on the document element based on the computed style. + */ + +export function setDirectionProperty(element: Element): void { + const direction = getDirection(); + + element.setAttribute("dir", direction); +} From 9bcc33e2e3f6b51edc1de48a99e1df2a72ee3b77 Mon Sep 17 00:00:00 2001 From: Erbil Date: Mon, 14 Oct 2024 13:56:57 +0300 Subject: [PATCH 2/3] feat(rtl-support): trigger beta release (#937) Co-authored-by: Erbil Nas --- docs/rtl-support.stories.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/rtl-support.stories.mdx b/docs/rtl-support.stories.mdx index 34b90f88..734ac5b7 100644 --- a/docs/rtl-support.stories.mdx +++ b/docs/rtl-support.stories.mdx @@ -83,3 +83,4 @@ When developing or modifying Baklava components, it's crucial to ensure proper R By following these guidelines, you'll ensure that Baklava components work seamlessly in both LTR and RTL layouts, providing a consistent user experience across different language settings. + From 9b9fa6c47c464ecc39d1192479d433a3c5f7edfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bu=C4=9Frahan=20Y=C3=BCnt?= <90159617+mryunt02@users.noreply.github.com> Date: Thu, 17 Oct 2024 15:16:42 +0300 Subject: [PATCH 3/3] feat(switch): update cursor style to not-allowed when disabled (#943) This PR updates the cursor style of the Switch component to `not-allowed` when it is disabled, improving user experience by indicating that the component is not interactive in its disabled state. ### Changes - Added the following CSS rule to the Switch component: ```css :host([disabled]) .switch { opacity: 0.5; cursor: not-allowed; /* added this part */ } ### Issue Resolved this PR resolves #942 Co-authored-by: Buse Selvi <106681486+buseselvi@users.noreply.github.com> --- src/components/switch/bl-switch.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/switch/bl-switch.css b/src/components/switch/bl-switch.css index 394802a7..293b6fa9 100644 --- a/src/components/switch/bl-switch.css +++ b/src/components/switch/bl-switch.css @@ -85,6 +85,7 @@ label { :host([disabled]) .switch { opacity: 0.5; + cursor: not-allowed; } :host([disabled]) {