0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

refactor(console,core,demo-app,elements,experience): improve rtl support (#6549)

* refactor(console,experience): improve rtl support

* chore: add changeset
This commit is contained in:
Charles Zhao 2024-09-09 19:19:15 +08:00 committed by GitHub
parent 3b9714b993
commit fae8725a44
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
211 changed files with 701 additions and 355 deletions

View file

@ -0,0 +1,9 @@
---
"@logto/experience": patch
"@logto/demo-app": patch
"@logto/elements": patch
"@logto/console": patch
"@logto/core": patch
---
improve RTL language support

View file

@ -123,6 +123,7 @@ function Providers() {
// hook will cause a re-render following some bugs here. This still works for the
// initial render, so we're good for now. Consider refactoring this in the future.
lang: i18next.language,
dir: i18next.dir(),
}}
/>
<Toast />

View file

@ -43,7 +43,7 @@
}
.tag {
margin-left: _.unit(-2);
margin-inline-start: _.unit(-2);
}
}

View file

@ -36,7 +36,7 @@
.expander {
display: inline-flex;
align-items: center;
margin-left: _.unit(2);
margin-inline-start: _.unit(2);
margin-top: _.unit(2);
color: var(--color-primary);
cursor: pointer;

View file

@ -8,7 +8,7 @@
.icon {
flex-shrink: 0;
margin-right: _.unit(1);
margin-inline-end: _.unit(1);
width: 24px;
height: 24px;

View file

@ -12,11 +12,11 @@
.eventSelector {
width: 300px;
margin-left: _.unit(2);
margin-inline-start: _.unit(2);
}
.applicationSelector {
width: 250px;
margin-left: _.unit(2);
margin-inline-start: _.unit(2);
}
}

View file

@ -15,10 +15,7 @@
border-radius: 8px;
display: flex;
align-items: center;
.icon {
margin-right: _.unit(3);
}
gap: _.unit(3);
.content {
span {
@ -26,7 +23,7 @@
}
> ul {
padding-left: _.unit(3);
padding-inline-start: _.unit(3);
}
}
}

View file

@ -103,9 +103,7 @@ function BasicForm({ isAllowEditTarget, isStandard, conflictConnectorName }: Pro
</div>
{conflictConnectorName && (
<div className={styles.error}>
<div className={styles.icon}>
<Error />
</div>
<Error />
<div className={styles.content}>
<Trans
components={{

View file

@ -8,5 +8,5 @@
.multiSelect {
padding-top: _.unit(1);
padding-left: _.unit(1);
padding-inline-start: _.unit(1);
}

View file

@ -10,7 +10,7 @@
}
.send {
margin-left: _.unit(1.5);
margin-inline-start: _.unit(1.5);
margin-bottom: 1px;
}
}

View file

@ -3,15 +3,14 @@
.connector {
font: var(--font-body-2);
display: flex;
gap: _.unit(3);
.content {
flex: 1;
margin-left: _.unit(3);
.name {
font: var(--font-label-2);
@include _.multi-line-ellipsis(1);
padding-right: _.unit(3);
}
.connectorId {

View file

@ -39,5 +39,5 @@
}
.planNameTag {
margin-left: _.unit(1);
margin-inline-start: _.unit(1);
}

View file

@ -12,6 +12,7 @@ import { TenantsContext } from '@/contexts/TenantsProvider';
import Button, { type Props as ButtonProps } from '@/ds-components/Button';
import DangerousRaw from '@/ds-components/DangerousRaw';
import DynamicT from '@/ds-components/DynamicT';
import FlipOnRtl from '@/ds-components/FlipOnRtl';
import TextLink from '@/ds-components/TextLink';
import FeaturedSkuContent from './FeaturedSkuContent';
@ -67,7 +68,11 @@ function SkuCardItem({ sku, onSelect, buttonProps }: Props) {
isTrailingIcon
href={pricingLink}
targetBlank="noopener"
icon={<ArrowRight className={styles.linkIcon} />}
icon={
<FlipOnRtl>
<ArrowRight className={styles.linkIcon} />
</FlipOnRtl>
}
className={styles.link}
>
<DynamicT forKey="upsell.create_tenant.view_all_features" />

View file

@ -105,12 +105,13 @@ function DetailsPageHeader({
additionalCustomElement,
actionMenuItems,
}: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { t, i18n } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const [showIcon, setShowIcon] = useState(true);
const [isCompact, setIsCompact] = useState(false);
const [showAdditionalCustomElement, setShowAdditionalCustomElement] = useState(true);
const identifierRef = useRef<HTMLDivElement>(null);
const operationRef = useRef<HTMLDivElement>(null);
const isRtl = i18n.dir() === 'rtl';
useWindowResize(() => {
if (!identifierRef.current || !operationRef.current) {
@ -119,37 +120,45 @@ function DetailsPageHeader({
// Dynamically handle the visibility of the icon and action button styles. Sources:
// https://www.figma.com/file/hqAWH3Di8gkiV5TXAt6juO/%F0%9F%8C%B9-%5BAC%5D-Layout-UI-Optimization?type=design&node-id=896-75673
const { right: identifierRightEdge, width: identifierWidth } =
identifierRef.current.getBoundingClientRect();
const operationLeftEdge = operationRef.current.getBoundingClientRect().left;
const {
left: identifierLeftEdge,
right: identifierRightEdge,
width: identifierWidth,
} = identifierRef.current.getBoundingClientRect();
const { left: operationLeftEdge, right: operationRightEdge } =
operationRef.current.getBoundingClientRect();
const identifierEdge = isRtl ? identifierLeftEdge : identifierRightEdge;
const operationEdge = isRtl ? operationRightEdge : operationLeftEdge;
// When the operation buttons are in regular form, and the gap between the operation area and the identifier copy box is
// only 24px. This means the window is shrinking and reaching the 1st breakpoint. Set operation buttons to compact form.
if (!isCompact && operationLeftEdge - identifierRightEdge <= 24) {
if (!isCompact && Math.abs(operationEdge - identifierEdge) <= 24) {
setIsCompact(true);
}
// When the operation buttons are compact, and the gap between the operation area and the identifier copy box is only 24px.
// This means the window keeps shrinking and reaching the 2nd breakpoint. Hide the main icon on the very left.
if (isCompact && showIcon && operationLeftEdge - identifierRightEdge <= 24) {
if (isCompact && showIcon && Math.abs(operationEdge - identifierEdge) <= 24) {
setShowIcon(false);
}
// When the identifier copy box is 50px, and the gap between the operation area and the identifier copy box is only 24px.
// This is when the page header is extremely narrow and barely has space to hold the identifier. Hide the additional custom element.
if (identifierWidth <= 50 && operationLeftEdge - identifierRightEdge <= 24) {
if (identifierWidth <= 50 && Math.abs(operationEdge - identifierEdge) <= 24) {
setShowAdditionalCustomElement(false);
}
// When the gap between the operation buttons and the identifier copy box is greater than 80px, show the additional custom element.
if (!showAdditionalCustomElement && operationLeftEdge - identifierRightEdge > 80) {
if (!showAdditionalCustomElement && Math.abs(operationEdge - identifierEdge) > 80) {
setShowAdditionalCustomElement(true);
}
// When the operation buttons are compact, icon is hidden, and the operation area is 120px away from the identifier copy box.
// This means the window is enlarging and there is enough room to hold the icon. Show the icon.
// 120px is a bit greater than the space required to hold the icon (60px + 24px padding), in order to avoid jittering.
if (isCompact && !showIcon && operationLeftEdge - identifierRightEdge > 120) {
if (isCompact && !showIcon && Math.abs(operationEdge - identifierEdge) > 120) {
setShowIcon(true);
}
@ -160,7 +169,7 @@ function DetailsPageHeader({
if (
isCompact &&
showIcon &&
operationLeftEdge - identifierRightEdge > (additionalCustomElement ? 240 : 180)
Math.abs(operationEdge - identifierEdge) > (additionalCustomElement ? 240 : 180)
) {
setIsCompact(false);
}

View file

@ -16,12 +16,12 @@
padding: _.unit(6);
border-radius: 16px;
background-color: var(--color-layer-1);
gap: _.unit(6);
.icon {
width: 60px;
height: 60px;
border-radius: 12px;
margin-right: _.unit(6);
@include _.shimmering-animation;
}

View file

@ -6,6 +6,7 @@ import { type To } from 'react-router-dom';
import Back from '@/assets/icons/back.svg?react';
import type DangerousRaw from '@/ds-components/DangerousRaw';
import DynamicT from '@/ds-components/DynamicT';
import FlipOnRtl from '@/ds-components/FlipOnRtl';
import TextLink from '@/ds-components/TextLink';
import type { RequestError } from '@/hooks/use-api';
@ -35,7 +36,15 @@ function DetailsPage({
}: Props) {
return (
<div className={classNames(styles.container, className)}>
<TextLink to={backLink} icon={<Back />} className={styles.backLink}>
<TextLink
to={backLink}
icon={
<FlipOnRtl>
<Back />
</FlipOnRtl>
}
className={styles.backLink}
>
{typeof backLinkTitle === 'string' ? <DynamicT forKey={backLinkTitle} /> : backLinkTitle}
</TextLink>
{isLoading ? (

View file

@ -10,10 +10,10 @@
flex: 1 1 0;
font: var(--font-body-2);
@include _.text-ellipsis;
margin-left: _.unit(2);
margin-inline-start: _.unit(2);
max-width: fit-content;
}
.suspended {
margin-left: _.unit(1);
margin-inline-start: _.unit(1);
}

View file

@ -17,12 +17,12 @@
flex: 1 1 0;
font: var(--font-body-2);
@include _.text-ellipsis;
margin-left: _.unit(2);
margin-inline-start: _.unit(2);
max-width: fit-content;
}
.suspended {
margin-left: _.unit(1);
margin-inline-start: _.unit(1);
}
&:hover {

View file

@ -22,7 +22,7 @@
flex: 1 1 0;
font: var(--font-body-2);
@include _.text-ellipsis;
margin-left: _.unit(2);
margin-inline-start: _.unit(2);
max-width: fit-content;
}

View file

@ -58,12 +58,12 @@
display: flex;
align-items: flex-start; // align name and the feature tag to the top
justify-content: space-between;
gap: _.unit(1);
}
.name {
font: var(--font-label-2);
color: var(--color-text);
margin-right: _.unit(1);
}
.description {

View file

@ -15,12 +15,12 @@
justify-content: space-between;
max-width: dim.$guide-main-content-max-width;
margin: 0 auto;
gap: _.unit(3);
}
.text {
font: var(--font-body-2);
color: var(--color-text);
margin-right: _.unit(3);
@include _.multi-line-ellipsis(2);
}
}

View file

@ -1,11 +1,11 @@
@use '@/scss/underscore' as _;
.requestSdkButton {
margin-right: _.unit(15);
margin-inline-end: _.unit(15);
}
@media screen and (max-width: 918px) {
.requestSdkButton {
margin-right: 0;
margin-inline-end: 0;
}
}

View file

@ -9,12 +9,12 @@
background-color: var(--color-layer-1);
max-width: dim.$guide-main-content-max-width;
margin: 0 auto;
gap: _.unit(4);
.index {
width: 28px;
height: 28px;
border-radius: 50%;
margin-right: _.unit(4);
@include _.shimmering-animation;
}

View file

@ -88,8 +88,7 @@
max-width: dim.$guide-main-content-max-width;
> button {
margin-right: 0;
margin-left: auto;
margin-inline: auto 0;
}
}
}

View file

@ -10,11 +10,11 @@
}
> div:not(:first-child) {
margin-left: _.unit(3);
margin-inline-start: _.unit(3);
}
.content {
padding-right: _.unit(4);
padding-inline-end: _.unit(4);
overflow: hidden;
display: flex;
align-items: center;
@ -22,7 +22,7 @@
margin-top: _.unit(-1);
> div:not(:last-child) {
margin-right: _.unit(2);
margin-inline-end: _.unit(2);
}
.meta {
@ -56,7 +56,7 @@
align-items: baseline;
.title {
margin-right: _.unit(1);
margin-inline-end: _.unit(1);
}
}
}

View file

@ -7,6 +7,7 @@ import ExternalLinkIcon from '@/assets/icons/external-link.svg?react';
import { AppDataContext } from '@/contexts/AppDataProvider';
import type { Props as ButtonProps, ButtonType } from '@/ds-components/Button';
import Button from '@/ds-components/Button';
import FlipOnRtl from '@/ds-components/FlipOnRtl';
import { Tooltip } from '@/ds-components/Tip';
import styles from './index.module.scss';
@ -29,12 +30,14 @@ function LivePreviewButton({ size, type, isDisabled }: Props) {
disabled={isDisabled}
title="sign_in_exp.preview.live_preview"
trailingIcon={
<ExternalLinkIcon
className={conditional(
type !== 'violet' &&
classNames(styles.defaultIcon, isDisabled && styles.disabledDefaultIcon)
)}
/>
<FlipOnRtl>
<ExternalLinkIcon
className={conditional(
type !== 'violet' &&
classNames(styles.defaultIcon, isDisabled && styles.disabledDefaultIcon)
)}
/>
</FlipOnRtl>
}
onClick={() => {
window.open(new URL('/demo-app', tenantEndpoint), '_blank');

View file

@ -77,7 +77,7 @@
color: var(--color-text);
padding: _.unit(3);
border-bottom: 1px solid var(--color-divider);
text-align: left;
text-align: start;
}
}

View file

@ -8,9 +8,9 @@
.factorIcon {
color: var(--color-text-secondary);
margin-right: _.unit(3);
margin-inline-end: _.unit(3);
}
.factorTip {
margin-left: _.unit(1);
margin-inline-start: _.unit(1);
}

View file

@ -57,7 +57,7 @@
.delete {
width: 20px;
height: 20px;
margin-right: _.unit(-0.5);
margin-inline-end: _.unit(-0.5);
}
input {

View file

@ -1,5 +1,5 @@
@use '@/scss/underscore' as _;
.headlineWithMultiInputs {
padding-right: _.unit(9);
padding-inline-end: _.unit(9);
}

View file

@ -1,6 +1,7 @@
import { useTranslation } from 'react-i18next';
import ExternalLinkIcon from '@/assets/icons/external-link.svg?react';
import FlipOnRtl from '@/ds-components/FlipOnRtl';
import IconButton from '@/ds-components/IconButton';
import { Tooltip } from '@/ds-components/Tip';
@ -18,7 +19,9 @@ function OpenExternalLink({ link }: Props) {
window.open(link, '_blank');
}}
>
<ExternalLinkIcon />
<FlipOnRtl>
<ExternalLinkIcon />
</FlipOnRtl>
</IconButton>
</Tooltip>
);

View file

@ -11,7 +11,7 @@
align-items: center;
.createButton {
margin-left: _.unit(2);
margin-inline-start: _.unit(2);
}
}
@ -25,6 +25,6 @@
}
.actionColumn {
text-align: right;
text-align: end;
}
}

View file

@ -4,7 +4,7 @@
font: var(--font-label-2);
.comingSoon {
margin-left: _.unit(1);
margin-inline-start: _.unit(1);
font: var(--font-body-2);
color: var(--color-text-secondary);
}

View file

@ -16,7 +16,7 @@
overflow: hidden;
.caret {
margin-right: _.unit(2);
margin-inline-end: _.unit(2);
}
.name {
@ -28,7 +28,7 @@
flex-shrink: 0;
font: var(--font-body-2);
color: var(--color-text-secondary);
margin-left: _.unit(2);
margin-inline-start: _.unit(2);
}
}
}

View file

@ -12,6 +12,6 @@
}
.closeIcon {
margin-left: _.unit(1);
margin-inline-start: _.unit(1);
}
}

View file

@ -13,13 +13,13 @@
// Todo @xiaoyijun Remove this `count` style together with the dev feature flag
.count {
flex-shrink: 0;
margin-left: _.unit(2);
margin-inline-start: _.unit(2);
color: var(--color-text-secondary);
}
.flag {
flex-shrink: 0;
margin-left: _.unit(2);
margin-inline-start: _.unit(2);
color: var(--color-text-secondary);
display: flex;
align-items: center;

View file

@ -22,7 +22,7 @@
width: _screenSize.$web-iframe-width;
height: _screenSize.$web-iframe-height;
transform: scaleX(_screenSize.$web-scale-x) scaleY(_screenSize.$web-scale-y);
margin-left: _screenSize.$web-to-wrapper-offset-x;
margin-inline-start: _screenSize.$web-to-wrapper-offset-x;
margin-top: _screenSize.$web-to-wrapper-offset-y;
}
}
@ -79,7 +79,7 @@
width: _screenSize.$mobile-iframe-width;
height: _screenSize.$mobile-iframe-height;
transform: scaleX(_screenSize.$mobile-scale-x) scaleY(_screenSize.$mobile-scale-y);
margin-left: _screenSize.$mobile-to-wrapper-offset-x;
margin-inline-start: _screenSize.$mobile-to-wrapper-offset-x;
margin-top: _screenSize.$mobile-to-wrapper-offset-y;
}
}

View file

@ -19,10 +19,7 @@
border-radius: 12px 12px 0 0;
transform: translateY(100%);
transition: transform 0.3s ease-out;
> button + button {
margin-left: _.unit(3);
}
gap: _.unit(3);
}
&.active {

View file

@ -9,7 +9,7 @@
padding: _.unit(3) _.unit(4);
.icon {
margin-right: _.unit(6);
margin-inline-end: _.unit(6);
}
&:not(:last-child) {

View file

@ -20,7 +20,7 @@
.info {
display: flex;
flex-direction: column;
margin-right: _.unit(4);
margin-inline-end: _.unit(4);
.meta {
display: flex;

View file

@ -11,8 +11,8 @@ $dropdown-item-height: 40px;
display: flex;
align-items: center;
padding: _.unit(1);
padding-left: _.unit(2);
margin-left: _.unit(4);
padding-inline-start: _.unit(2);
margin-inline-start: _.unit(4);
max-width: 500px;
border-radius: _.unit(2);
transition: background-color 0.2s ease-in-out;
@ -62,6 +62,13 @@ $dropdown-item-height: 40px;
pointer-events: none;
cursor: default;
}
&.rtl {
&::before {
left: unset;
right: _.unit(-3);
}
}
}
.dropdown {

View file

@ -1,4 +1,5 @@
import { OrganizationInvitationStatus } from '@logto/schemas';
import classNames from 'classnames';
import { useContext, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
@ -20,7 +21,7 @@ import TenantInvitationDropdownItem from './TenantInvitationDropdownItem';
import styles from './index.module.scss';
export default function TenantSelector() {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { t, i18n } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const {
tenants,
prependTenant,
@ -44,7 +45,7 @@ export default function TenantSelector() {
<div
ref={anchorRef}
tabIndex={0}
className={styles.currentTenantCard}
className={classNames(styles.currentTenantCard, styles[i18n.dir()])}
role="button"
onKeyDown={onKeyDownHandler(() => {
setShowDropdown(true);

View file

@ -8,6 +8,7 @@
font: var(--font-body-2);
border-radius: _.unit(2);
cursor: pointer;
gap: _.unit(4);
&:hover {
background: var(--color-hover);
@ -22,7 +23,6 @@
.title {
font: var(--font-body-2);
margin-left: _.unit(4);
}
.menu {
@ -38,6 +38,11 @@
&.visible {
visibility: visible;
}
&.rtl {
right: unset;
left: calc(100% + 5px);
}
}
.menuOption {
@ -55,4 +60,13 @@
left: 8px;
top: 10px;
}
&.rtl {
padding: _.unit(2.5) _.unit(8) _.unit(2.5) _.unit(5.5);
.tick {
left: unset;
right: 8px;
}
}
}

View file

@ -1,11 +1,13 @@
import type { AdminConsoleKey } from '@logto/phrases';
import classNames from 'classnames';
import { type ReactNode, useCallback, useState, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import ArrowRight from '@/assets/icons/arrow-right.svg?react';
import Tick from '@/assets/icons/tick.svg?react';
import { DropdownItem } from '@/ds-components/Dropdown';
import DynamicT from '@/ds-components/DynamicT';
import FlipOnRtl from '@/ds-components/FlipOnRtl';
import OverlayScrollbar from '@/ds-components/OverlayScrollbar';
import type { Option } from '@/ds-components/Select';
import Spacer from '@/ds-components/Spacer';
@ -42,6 +44,7 @@ function SubMenu<T extends string>({
const mouseEnterTimeoutRef = useRef(0);
const mouseLeaveTimeoutRef = useRef(0);
const [menuHeight, setMenuHeight] = useState<number>();
const { i18n } = useTranslation();
const calculateDropdownHeight = useCallback(() => {
if (anchorRef.current) {
@ -90,9 +93,11 @@ function SubMenu<T extends string>({
<DynamicT forKey={title} />
</span>
<Spacer />
<ArrowRight className={styles.icon} />
<FlipOnRtl>
<ArrowRight className={styles.icon} />
</FlipOnRtl>
<OverlayScrollbar
className={classNames(styles.menu, showMenu && styles.visible)}
className={classNames(styles.menu, showMenu && styles.visible, styles[i18n.dir()])}
style={{ maxHeight: menuHeight }}
>
{options.map(({ value, title }) => {
@ -104,7 +109,8 @@ function SubMenu<T extends string>({
className={classNames(
styles.menuOption,
selected && styles.selected,
menuItemClassName
menuItemClassName,
styles[i18n.dir()]
)}
onClick={() => {
onItemClick(value);

View file

@ -4,13 +4,13 @@
display: flex;
align-items: center;
padding: _.unit(2);
margin-left: _.unit(4);
margin-inline-start: _.unit(4);
border-radius: 8px;
.image {
width: 36px;
height: 36px;
margin-right: _.unit(2);
margin-inline-end: _.unit(2);
border-radius: 6px;
@include _.shimmering-animation;
}

View file

@ -57,6 +57,6 @@
}
.spinner {
margin-left: _.unit(6);
margin-inline-start: _.unit(6);
}
}

View file

@ -15,6 +15,7 @@ import UserInfoCard from '@/components/UserInfoCard';
import { isCloud } from '@/consts/env';
import Divider from '@/ds-components/Divider';
import Dropdown, { DropdownItem } from '@/ds-components/Dropdown';
import FlipOnRtl from '@/ds-components/FlipOnRtl';
import Spacer from '@/ds-components/Spacer';
import { Ring as Spinner } from '@/ds-components/Spinner';
import useCurrentUser from '@/hooks/use-current-user';
@ -90,7 +91,9 @@ function UserInfo() {
{t('menu.profile')}
<Spacer />
<div className={styles.icon}>
<ExternalLinkIcon />
<FlipOnRtl>
<ExternalLinkIcon />
</FlipOnRtl>
</div>
</DropdownItem>
<Divider />
@ -132,7 +135,11 @@ function UserInfo() {
<Divider />
<DropdownItem
className={classNames(styles.dropdownItem, isLoading && styles.loading)}
icon={<SignOut className={styles.icon} />}
icon={
<FlipOnRtl>
<SignOut className={styles.icon} />
</FlipOnRtl>
}
onClick={(event) => {
event.stopPropagation();

View file

@ -37,7 +37,7 @@
user-select: none;
outline: none;
cursor: pointer;
margin-left: _.unit(-1);
margin-inline-start: _.unit(-1);
text-decoration: none;
gap: _.unit(1);
font: var(--font-label-2);
@ -71,5 +71,5 @@
height: 8px;
border-radius: 50%;
background-color: var(--color-error);
margin-left: _.unit(0.5);
margin-inline-start: _.unit(0.5);
}

View file

@ -20,11 +20,11 @@
.infoContent {
font: var(--font-label-2);
padding-left: _.unit(1);
padding-inline-start: _.unit(1);
}
.operation {
padding-left: _.unit(1);
padding-inline-start: _.unit(1);
}
.eyeIcon {

View file

@ -45,7 +45,11 @@
width: 48px;
height: 48px;
object-fit: cover;
transform-origin: 0 0;
transform-origin: top left;
&.rtl {
transform-origin: top right;
}
&.micro {
transform: scale(0.416);
@ -90,6 +94,6 @@
.value {
// Fixed font color should used in Tooltip component as the color does not change when theme changes.
color: #f7f8f8;
margin-left: _.unit(1);
margin-inline-start: _.unit(1);
}
}

View file

@ -46,7 +46,8 @@ function UserInfoTipContent({ user }: { readonly user: Partial<UserInfo> }) {
}
function UserAvatar({ className, size = 'medium', user, hasTooltip = false }: Props) {
const avatarClassName = classNames(styles.avatar, styles[size]);
const { i18n } = useTranslation();
const avatarClassName = classNames(styles.avatar, styles[size], styles[i18n.dir()]);
const wrapperClassName = classNames(styles.wrapper, styles[size], className);
const defaultColorPalette = [
'#E74C3C',

View file

@ -5,12 +5,12 @@
align-items: center;
user-select: none;
cursor: default;
gap: _.unit(3);
}
.nameWrapper {
display: flex;
flex-direction: column;
margin-left: _.unit(3);
.name {
font: var(--font-label-2);

View file

@ -11,7 +11,7 @@
@include _.text-ellipsis;
span {
margin-left: _.unit(2);
margin-inline-start: _.unit(2);
}
}

View file

@ -33,6 +33,6 @@
.errorMessage {
font: var(--font-body-2);
color: var(--color-error);
margin-left: _.unit(0.5);
margin-inline-start: _.unit(0.5);
margin-top: _.unit(1);
}

View file

@ -13,6 +13,11 @@
background: none;
border: none;
width: calc(100% - _.unit(10));
gap: _.unit(4);
&.rtl {
margin: _.unit(1) _.unit(4) _.unit(1) _.unit(6);
}
.icon {
height: _.unit(5);
@ -38,10 +43,6 @@
}
}
> div + div {
margin-left: _.unit(4);
}
.title {
font: var(--font-label-2);
}

View file

@ -18,7 +18,7 @@ type Props = {
};
function Item({ icon, titleKey, modal, externalLink, isActive = false }: Props) {
const { t } = useTranslation(undefined, {
const { t, i18n } = useTranslation(undefined, {
keyPrefix: 'admin_console.tabs',
});
const [isOpen, setIsOpen] = useState(false);
@ -37,7 +37,7 @@ function Item({ icon, titleKey, modal, externalLink, isActive = false }: Props)
return (
<>
<button
className={styles.row}
className={classNames(styles.row, styles[i18n.dir()])}
onClick={() => {
setIsOpen(true);
}}
@ -60,7 +60,10 @@ function Item({ icon, titleKey, modal, externalLink, isActive = false }: Props)
}
return (
<Link to={getPath(titleKey)} className={classNames(styles.row, isActive && styles.active)}>
<Link
to={getPath(titleKey)}
className={classNames(styles.row, isActive && styles.active, styles[i18n.dir()])}
>
{content}
</Link>
);

View file

@ -15,6 +15,10 @@
height: 100%;
padding: 0 _.unit(6) 0 _.unit(2);
&.rtl {
padding: 0 _.unit(2) 0 _.unit(6);
}
> :not(svg) {
@include _.main-content-width;
}
@ -30,6 +34,11 @@
position: absolute;
bottom: _.unit(3);
left: _.unit(4);
&.rtl {
left: unset;
right: _.unit(4);
}
}
.daisy {

View file

@ -1,4 +1,6 @@
import classNames from 'classnames';
import { Suspense } from 'react';
import { useTranslation } from 'react-i18next';
import { useOutletContext, useRoutes } from 'react-router-dom';
import { safeLazy } from 'react-safe-lazy';
@ -21,6 +23,8 @@ function ConsoleContent() {
const { scrollableContent } = useOutletContext<AppContentOutletContext>();
const routeObjects = useConsoleRoutes();
const routes = useRoutes(routeObjects);
const { i18n } = useTranslation();
const direction = i18n.dir();
usePlausiblePageview(routeObjects, ':tenantId');
// Use this hook here to make sure console listens to user tenant scope changes.
@ -32,12 +36,17 @@ function ConsoleContent() {
<Sidebar />
</Suspense>
<OverlayScrollbar className={styles.overlayScrollbarWrapper}>
<div ref={scrollableContent} className={styles.main}>
<div ref={scrollableContent} className={classNames(styles.main, styles[direction])}>
<Suspense fallback={<DelayedSuspenseFallback />}>{routes}</Suspense>
</div>
</OverlayScrollbar>
{isDevFeaturesEnabled && (
<Tag type="state" status="success" variant="plain" className={styles.devStatus}>
<Tag
type="state"
status="success"
variant="plain"
className={classNames(styles.devStatus, styles[direction])}
>
Dev features enabled
</Tag>
)}

View file

@ -205,7 +205,7 @@
&.text {
background: none;
border-color: none;
border-color: transparent;
font: var(--font-label-2);
color: var(--color-text-link);
padding: _.unit(0.5) _.unit(1);

View file

@ -58,7 +58,7 @@ function CardTitle({
{learnMoreLink?.href && (
<>
{/* Use a space to keep the link and the text separate.
* Avoid using `margin-left` since it will cause an unexpected gap when the "learn more" text is at the start of a new line
* Avoid using `margin-inline-start` since it will cause an unexpected gap when the "learn more" text is at the start of a new line
*/}{' '}
<TextLink href={learnMoreLink.href} targetBlank={learnMoreLink.targetBlank}>
{t('general.learn_more')}

View file

@ -44,12 +44,12 @@
display: flex;
align-items: center;
cursor: pointer;
gap: _.unit(2);
}
.tooltipAnchor {
display: flex;
align-items: center;
margin-right: _.unit(2);
}
.label {

View file

@ -7,6 +7,7 @@
background: #34353f;
position: relative;
overflow-y: auto;
direction: ltr;
.title {
margin-top: _.unit(-2);

View file

@ -108,9 +108,9 @@ function CodeEditor({
value={value}
style={
isShowingPlaceholder
? { marginLeft: '8px', width: 'calc(100% - 8px)' }
? { marginInlineStart: '8px', width: 'calc(100% - 8px)' }
: {
marginLeft: `calc(${maxLineNumberDigits}ch + 20px)`,
marginInlineStart: `calc(${maxLineNumberDigits}ch + 20px)`,
width: `calc(100% - ${maxLineNumberDigits}ch - 20px)`,
}
}

View file

@ -5,17 +5,17 @@ export const lineNumberContainerStyle = (): CSSProperties => {
display: 'flex',
flexDirection: 'column',
textAlign: 'right',
paddingLeft: '0px',
paddingRight: '0px',
paddingInlineStart: '0px',
paddingInlineEnd: '0px',
};
};
export const lineNumberStyle = (numberOfLines: number): CSSProperties => {
return {
minWidth: `calc(${numberOfLines}ch + 20px)`,
marginLeft: '0px',
paddingRight: '20px',
paddingLeft: '0px',
marginInlineStart: '0px',
paddingInlineEnd: '20px',
paddingInlineStart: '0px',
display: 'inline-flex',
justifyContent: 'flex-end',
counterIncrement: 'line',

View file

@ -11,6 +11,7 @@
font: var(--font-body-2);
display: flex;
align-items: center;
gap: _.unit(2);
&:focus,
&.highlight {
@ -25,7 +26,6 @@
height: 24px;
border: 1px solid var(--color-divider);
border-radius: 4px;
margin-right: _.unit(2);
}
}

View file

@ -37,7 +37,7 @@
display: inline-flex;
align-items: center;
gap: _.unit(2);
margin-right: _.unit(1);
margin-inline-end: _.unit(1);
flex-wrap: nowrap;
&.wrapContent {

View file

@ -16,7 +16,7 @@
overflow: hidden;
.caret {
margin-right: _.unit(2);
margin-inline-end: _.unit(2);
}
.name {
@ -28,7 +28,7 @@
flex-shrink: 0;
font: var(--font-body-2);
color: var(--color-text-secondary);
margin-left: _.unit(2);
margin-inline-start: _.unit(2);
}
}
}
@ -39,5 +39,5 @@
}
.dataList {
padding-left: _.unit(10);
padding-inline-start: _.unit(10);
}

View file

@ -8,6 +8,7 @@
display: flex;
align-items: center;
overflow: hidden;
gap: _.unit(4);
&:hover {
background: var(--color-hover);
@ -20,6 +21,5 @@
.icon {
display: flex;
align-items: center;
margin-right: _.unit(4);
}
}

View file

@ -0,0 +1,3 @@
.flip {
transform: scaleX(-1);
}

View file

@ -0,0 +1,38 @@
import classNames from 'classnames';
import { cloneElement, isValidElement, type ReactElement } from 'react';
import { useTranslation } from 'react-i18next';
import styles from './index.module.scss';
type Props = {
readonly children: ReactElement<HTMLElement>;
};
/**
* This component flips its child element horizontally if the browser's text direction is RTL (right-to-left).
*
* @component
* @example
* ```tsx
* <FlipOnRtl>
* <SVG />
* </FlipOnRtl>
* ```
*
* @param {React.ReactNode} children - The SVG or other HTML content to render and flip if RTL.
* @returns {JSX.Element} The flipped content.
*/
function FlipOnRtl({ children }: Props) {
const { i18n } = useTranslation();
const isRtl = i18n.dir() === 'rtl';
if (!isValidElement(children)) {
return children;
}
return cloneElement(children, {
className: classNames(children.props.className, isRtl && styles.flip),
});
}
export default FlipOnRtl;

View file

@ -20,14 +20,14 @@
color: var(--color-text);
.multiple {
margin-left: _.unit(1);
margin-inline-start: _.unit(1);
font: var(--font-body-2);
color: var(--color-text-secondary);
}
}
.toggleTipButton {
margin-left: _.unit(0.5);
margin-inline-start: _.unit(0.5);
}
.required {
@ -38,7 +38,7 @@
.tagsWrapper {
display: flex;
align-items: center;
margin-left: _.unit(2);
margin-inline-start: _.unit(2);
gap: _.unit(1);
}
}

View file

@ -4,7 +4,7 @@
border: none;
outline: none;
background: none;
border-color: none;
border-color: transparent;
color: var(--color-primary);
border-radius: 6px;
font: var(--font-label-2);

View file

@ -36,7 +36,7 @@
justify-content: flex-end;
> :not(:first-child) {
margin-left: _.unit(4);
margin-inline-start: _.unit(4);
}
}

View file

@ -6,7 +6,7 @@
}
.firstFieldWithMultiInputs {
padding-right: _.unit(9);
padding-inline-end: _.unit(9);
}
.deletableInput {
@ -18,7 +18,7 @@
}
> :not(:first-child) {
margin-left: _.unit(2);
margin-inline-start: _.unit(2);
}
}

View file

@ -1,11 +1,17 @@
import classNames from 'classnames';
import type { OverlayScrollbarsComponentProps } from 'overlayscrollbars-react';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import { useTranslation } from 'react-i18next';
function OverlayScrollbar(props: OverlayScrollbarsComponentProps) {
const { i18n } = useTranslation();
const isRtl = i18n.dir() === 'rtl';
return (
<OverlayScrollbarsComponent
options={{ scrollbars: { autoHide: 'leave', autoHideDelay: 0 } }}
{...props}
className={classNames(props.className, isRtl && 'os-scrollbar-rtl')}
/>
);
}

View file

@ -21,7 +21,7 @@
list-style: none;
&:not(:first-child) {
margin-left: _.unit(2);
margin-inline-start: _.unit(2);
}
.button {

View file

@ -13,12 +13,12 @@
.content {
display: flex;
align-items: center;
gap: _.unit(2);
.indicator {
border-radius: 50%;
border: 2px solid var(--color-neutral-60);
display: inline-block;
margin-right: _.unit(2);
&::before {
content: '';
@ -33,7 +33,7 @@
.icon,
.trailingIcon {
margin-right: _.unit(2);
margin-inline-end: _.unit(2);
color: var(--color-text-secondary);
> svg {
@ -42,8 +42,8 @@
}
.trailingIcon {
margin-right: unset;
margin-left: _.unit(2);
margin-inline-end: unset;
margin-inline-start: _.unit(2);
}
}
}
@ -66,7 +66,7 @@
border-radius: unset;
border: unset;
display: block;
margin-right: unset;
margin-inline-end: unset;
position: absolute;
right: 0;
top: 0;
@ -82,7 +82,7 @@
.icon,
.trailingIcon {
margin-right: _.unit(2);
margin-inline-end: _.unit(2);
vertical-align: middle;
color: var(--color-text-secondary);
@ -92,8 +92,8 @@
}
.trailingIcon {
margin-right: unset;
margin-left: _.unit(2);
margin-inline-end: unset;
margin-inline-start: _.unit(2);
}
.disabledLabel {
@ -104,6 +104,11 @@
color: var(--color-text);
}
}
&.rtl .content .indicator {
right: unset;
left: 0;
}
}
.compact {
@ -128,16 +133,31 @@
margin-bottom: unset;
}
&.rtl {
&:first-child {
border-radius: 0 12px 12px 0;
}
&:last-child {
border-radius: 12px 0 0 12px;
}
&:not(:first-child) {
border-left: 1px solid var(--color-border);
border-right: none;
}
}
.content {
padding: _.unit(5);
height: 100%;
.icon {
margin-right: _.unit(4);
margin-inline-end: _.unit(4);
}
.trailingIcon {
margin-left: _.unit(4);
margin-inline-start: _.unit(4);
}
}
}
@ -165,6 +185,21 @@
margin-bottom: unset;
}
&.rtl {
&:first-child {
border-radius: 0 6px 6px 0;
}
&:last-child {
border-radius: 6px 0 0 6px;
}
&:not(:first-child) {
border-left: 1px solid var(--color-border);
border-right: none;
}
}
.content {
height: 100%;
display: flex;
@ -219,6 +254,17 @@
bottom: -1px;
background-color: var(--color-primary);
}
&.rtl {
&:not(:first-child) {
border-left: 1px solid var(--color-primary);
&::before {
left: unset;
right: -1px;
}
}
}
}
.small.checked {
@ -235,6 +281,17 @@
bottom: -1px;
background-color: var(--color-text-link);
}
&.rtl {
&:not(:first-child) {
border-color: var(--color-text-link);
&::before {
left: unset;
right: -1px;
}
}
}
}

View file

@ -2,6 +2,7 @@ import type { AdminConsoleKey } from '@logto/phrases';
import classNames from 'classnames';
import type { KeyboardEventHandler, ReactElement, ReactNode } from 'react';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import type DangerousRaw from '../DangerousRaw';
import DynamicT from '../DynamicT';
@ -52,6 +53,7 @@ function Radio({
trailingIcon,
hasCheckIconForCard = true,
}: Props) {
const { i18n } = useTranslation();
const handleKeyPress: KeyboardEventHandler<HTMLDivElement> = useCallback(
(event) => {
if (isDisabled) {
@ -73,7 +75,8 @@ function Radio({
styles[type],
isChecked && styles.checked,
isDisabled && styles.disabled,
className
className,
styles[i18n.dir()]
)}
// eslint-disable-next-line jsx-a11y/role-has-required-aria-props
role="radio"

View file

@ -5,7 +5,7 @@
align-items: center;
>:not(:first-child) {
margin-left: _.unit(2);
margin-inline-start: _.unit(2);
}
.searchIcon {

View file

@ -4,7 +4,7 @@
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 _.unit(2) 0 _.unit(3);
padding: 0 _.unit(2);
background: var(--color-layer-1);
border: 1px solid var(--color-border);
border-radius: 8px;
@ -46,7 +46,7 @@
.delete {
width: 20px;
height: 20px;
margin-right: _.unit(-0.5);
margin-inline-end: _.unit(-0.5);
}
input {
@ -63,6 +63,7 @@
}
.title {
padding: 0 _.unit(1);
@include _.text-ellipsis;
}
@ -88,7 +89,6 @@
.icon {
display: flex;
margin-left: _.unit(1);
color: var(--color-text-secondary);
}
@ -147,7 +147,7 @@
align-items: center;
.search {
margin-right: _.unit(2);
margin-inline-end: _.unit(2);
color: var(--color-text-secondary);
width: 20px;
height: 20px;

View file

@ -76,7 +76,8 @@ function Select<T extends string>({
}
const element = anchorRef.current;
const cs = getComputedStyle(element);
const paddingX = Number.parseFloat(cs.paddingLeft) + Number.parseFloat(cs.paddingRight);
const paddingX =
Number.parseFloat(cs.paddingInlineStart) + Number.parseFloat(cs.paddingInlineEnd);
const paddingY = Number.parseFloat(cs.paddingTop) + Number.parseFloat(cs.paddingBottom);
const borderX = Number.parseFloat(cs.borderLeftWidth) + Number.parseFloat(cs.borderRightWidth);
const borderY = Number.parseFloat(cs.borderTopWidth) + Number.parseFloat(cs.borderBottomWidth);
@ -92,7 +93,7 @@ function Select<T extends string>({
left: `${
element.getBoundingClientRect().left +
Number.parseFloat(cs.borderLeftWidth) +
Number.parseFloat(cs.paddingLeft)
Number.parseFloat(cs.paddingInlineStart)
}px`,
backgroundColor: cs.backgroundColor,
};

View file

@ -34,6 +34,11 @@
}
}
&.rtl .slider::before {
left: unset;
right: 2px;
}
input:checked + .slider {
background-color: var(--color-success-70);
}
@ -50,6 +55,10 @@
background-color: var(--color-specific-toggle-thumb-disabled);
box-shadow: unset;
}
&.rtl input:checked + .slider::before {
transform: translateX(-16px);
}
}
.wrapper {

View file

@ -2,6 +2,7 @@ import { type AdminConsoleKey } from '@logto/phrases';
import classNames from 'classnames';
import type { HTMLProps, ReactElement, ReactNode, Ref } from 'react';
import { forwardRef } from 'react';
import { useTranslation } from 'react-i18next';
import DynamicT from '../DynamicT';
@ -20,6 +21,7 @@ type Props =
function Switch(props: Props, ref?: Ref<HTMLInputElement>) {
const { label, hasError, ...rest } = props;
const { i18n } = useTranslation();
return (
<div className={classNames(styles.wrapper, hasError && styles.error)}>
@ -33,7 +35,7 @@ function Switch(props: Props, ref?: Ref<HTMLInputElement>) {
</div>
)}
{label && <div className={styles.label}>{label}</div>}
<label className={styles.switch}>
<label className={classNames(styles.switch, styles[i18n.dir()])}>
<input type="checkbox" {...rest} ref={ref} />
<span className={styles.slider} />
</label>

View file

@ -4,10 +4,6 @@
display: flex;
align-items: center;
&:not(:last-child) {
margin-right: _.unit(6);
}
.link {
font: var(--font-label-2);
padding: _.unit(0.5) _.unit(1.5);
@ -48,7 +44,7 @@
}
.errors {
margin-left: _.unit(0.5);
margin-inline-start: _.unit(0.5);
font: var(--font-label-3);
color: var(--color-white);
padding: _.unit(0.5) _.unit(1.5);

View file

@ -4,4 +4,5 @@
border-bottom: 1px solid var(--color-surface-5);
display: flex;
margin-top: _.unit(1);
gap: _.unit(6);
}

View file

@ -14,7 +14,7 @@
.avatar {
width: 40px;
height: 40px;
margin-right: _.unit(4);
margin-inline-end: _.unit(4);
border-radius: 12px;
flex-shrink: 0;
@include _.shimmering-animation;

View file

@ -43,7 +43,7 @@
color: var(--color-text);
border-bottom: unset;
padding: _.unit(3);
text-align: left;
text-align: start;
}
}
}
@ -104,11 +104,11 @@
border: 1px solid var(--color-divider);
tr th:first-child {
padding-left: _.unit(7);
padding-inline-start: _.unit(7);
}
tr th:last-child {
padding-right: _.unit(7);
padding-inline-end: _.unit(7);
}
}
@ -126,11 +126,11 @@
}
tr td:first-child {
padding-left: _.unit(7);
padding-inline-start: _.unit(7);
}
tr td:last-child {
padding-right: _.unit(7);
padding-inline-end: _.unit(7);
}
}
}
@ -174,3 +174,17 @@
.pagination {
margin-top: _.unit(4);
}
.container.rtl {
.tableContainer {
tr.hoverEffect:hover {
td:first-child {
border-radius: 0 8px 8px 0;
}
td:last-child {
border-radius: 8px 0 0 8px;
}
}
}
}

View file

@ -3,6 +3,7 @@ import classNames from 'classnames';
import type { ReactNode } from 'react';
import { Fragment } from 'react';
import type { FieldPath, FieldValues } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import type { Props as PaginationProps } from '@/ds-components/Pagination';
import Pagination from '@/ds-components/Pagination';
@ -98,6 +99,7 @@ function Table<
onRetry,
footer,
}: Props<TFieldValues, TName>) {
const { i18n } = useTranslation();
const totalColspan = columns.reduce((result, { colSpan }) => {
return result + (colSpan ?? 1);
}, 0);
@ -108,7 +110,7 @@ function Table<
const isLoaded = !isLoading && hasData;
return (
<div className={classNames(styles.container, className)}>
<div className={classNames(styles.container, className, styles[i18n.dir()])}>
<div className={classNames(styles.tableContainer, hasBorder && styles.hasBorder)}>
{filter && (
<div className={styles.filterContainer}>

View file

@ -4,13 +4,13 @@
display: inline-flex;
align-items: center;
font: var(--font-body-2);
gap: _.unit(2);
@include _.text-ellipsis;
.icon {
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: _.unit(2);
background: var(--color-on-success-container);
}

View file

@ -7,6 +7,11 @@
top: 1px;
bottom: 1px;
&.rtl {
right: unset;
left: 1px;
}
.button {
user-select: none;
width: 32px;

View file

@ -1,5 +1,6 @@
import classNames from 'classnames';
import { type ComponentProps } from 'react';
import { useTranslation } from 'react-i18next';
import CaretDown from '@/assets/icons/caret-down.svg?react';
import CaretUp from '@/assets/icons/caret-up.svg?react';
@ -47,6 +48,7 @@ type Props = Omit<ComponentProps<typeof TextInput>, 'type' | 'suffix'> & {
/** A numeric text input with up and down buttons for incrementing and decrementing the value. */
function NumericInput({ onValueUp, onValueDown, ...props }: Props) {
const { i18n } = useTranslation();
const isDisabled = Boolean(props.disabled) || Boolean(props.readOnly);
return (
@ -55,7 +57,7 @@ function NumericInput({ onValueUp, onValueDown, ...props }: Props) {
alwaysShowSuffix
type="number"
suffix={
<div className={styles.container}>
<div className={classNames(styles.container, styles[i18n.dir()])}>
<Button
className={styles.up}
isDisabled={

View file

@ -30,6 +30,7 @@
height: 36px;
background-color: var(--color-layer-1);
font: var(--font-body-2);
gap: _.unit(2);
&.withIcon {
display: flex;
@ -38,7 +39,6 @@
.icon {
width: 20px;
height: 20px;
margin-right: _.unit(2);
}
}

View file

@ -32,7 +32,7 @@
// Leave space for scrollbar by setting `box-sizing` and `padding-right`
box-sizing: content-box;
padding-right: 10px;
padding-inline-end: 10px;
&::placeholder {
color: var(--color-placeholder);

View file

@ -29,6 +29,12 @@
}
}
&.rtl {
.delete {
left: _.unit(2);
right: unset;
}
}
&:hover {
.delete {

View file

@ -1,5 +1,6 @@
import type { AllowedUploadMimeType } from '@logto/schemas';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import Delete from '@/assets/icons/delete.svg?react';
import ImageWithErrorFallback from '@/ds-components/ImageWithErrorFallback';
@ -29,9 +30,13 @@ function ImageUploader({
uploadedClassName,
...rest
}: Props) {
const { i18n } = useTranslation();
const { allowedMimeTypes } = useImageMimeTypes(imageMimeTypes);
return value ? (
<div className={classNames(styles.imageUploader, className, uploadedClassName)}>
<div
className={classNames(styles.imageUploader, className, uploadedClassName, styles[i18n.dir()])}
>
<ImageWithErrorFallback
containerClassName={styles.container}
src={value}

View file

@ -17,7 +17,7 @@
cursor: pointer;
.arrow {
margin-right: _.unit(2);
margin-inline-end: _.unit(2);
transform: rotate(0deg);
transition: transform 0.3s;
}

View file

@ -14,7 +14,7 @@
position: sticky;
top: _.unit(6);
flex-shrink: 0;
margin-right: _.unit(7.5);
margin-inline-end: _.unit(7.5);
width: 220px;
> :not(:last-child) {
@ -61,7 +61,7 @@
@media screen and (max-width: dim.$guide-content-max-width) {
.content {
margin: 0;
padding-right: dim.$guide-content-padding;
padding-inline-end: dim.$guide-content-padding;
max-width: calc(dim.$guide-main-content-max-width + dim.$guide-sidebar-width + dim.$guide-panel-gap + 2 * dim.$guide-content-padding);
}
}

View file

@ -9,10 +9,10 @@
display: flex;
margin: _.unit(1) 0;
padding: 0;
gap: _.unit(6);
li {
list-style: none;
margin-right: _.unit(6);
padding-bottom: _.unit(1);
font: var(--font-label-2);
color: var(--color-text-secondary);

View file

@ -14,7 +14,7 @@
.icon {
color: var(--color-text-secondary);
margin-right: _.unit(4);
margin-inline-end: _.unit(4);
vertical-align: middle;
> svg {
@ -29,7 +29,7 @@
}
.trailingTag {
margin-left: _.unit(1);
margin-inline-start: _.unit(1);
}
}

View file

@ -10,7 +10,7 @@
justify-content: space-between;
.inspireContent {
margin-right: _.unit(6);
margin-inline-end: _.unit(6);
display: flex;
flex-direction: column;
@ -27,7 +27,7 @@
.button {
border-color: var(--color-neutral-variant-80);
// Note: this is a special case for the inspire me button since the bulb icon does not follow the standard icon size
padding-right: _.unit(7);
padding-inline-end: _.unit(7);
&:not(:disabled) {
&:not(:active),

Some files were not shown because too many files have changed in this diff Show more