diff --git a/.changeset/cuddly-ends-wait.md b/.changeset/cuddly-ends-wait.md new file mode 100644 index 00000000000..61a21cc03cd --- /dev/null +++ b/.changeset/cuddly-ends-wait.md @@ -0,0 +1,15 @@ +--- +'@clerk/ui': minor +--- + +Improve RTL support by converting physical CSS properties (margins, padding, text alignment, borders) to logical equivalents and adding direction-aware arrow icons + +The changes included: + +- Positioning (left → insetInlineStart) +- Margins (marginLeft/Right → marginInlineStart/End) +- Padding (paddingLeft/Right → paddingInlineStart/End) +- Text alignment (left/right → start/end) +- Border radius (borderTopLeftRadius → borderStartStartRadius) +- Arrow icon flipping with scaleX(-1) in RTL +- Animation direction adjustments diff --git a/eslint.config.mjs b/eslint.config.mjs index 23ef3f174bd..1cef3d663c3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -171,6 +171,83 @@ const noUnstableMethods = { }, }; +const noPhysicalCssProperties = { + meta: { + type: 'problem', + docs: { + description: 'Enforce use of CSS logical properties instead of physical properties for RTL support', + recommended: false, + }, + messages: { + useLogicalProperty: + 'Use logical CSS property "{{logical}}" instead of physical property "{{physical}}" for RTL support.', + useLogicalTextAlign: + 'Use logical textAlign value "{{logical}}" instead of physical value "{{physical}}" for RTL support.', + }, + schema: [], + }, + create(context) { + // Mapping of physical properties to logical equivalents + const propertyMap = { + left: 'insetInlineStart', + right: 'insetInlineEnd', + marginLeft: 'marginInlineStart', + marginRight: 'marginInlineEnd', + paddingLeft: 'paddingInlineStart', + paddingRight: 'paddingInlineEnd', + borderLeft: 'borderInlineStart', + borderRight: 'borderInlineEnd', + borderLeftWidth: 'borderInlineStartWidth', + borderRightWidth: 'borderInlineEndWidth', + borderLeftStyle: 'borderInlineStartStyle', + borderRightStyle: 'borderInlineEndStyle', + borderLeftColor: 'borderInlineStartColor', + borderRightColor: 'borderInlineEndColor', + borderTopLeftRadius: 'borderStartStartRadius', + borderTopRightRadius: 'borderStartEndRadius', + borderBottomLeftRadius: 'borderEndStartRadius', + borderBottomRightRadius: 'borderEndEndRadius', + }; + + const checkProperty = (key, value) => { + const keyName = key.type === 'Identifier' ? key.name : key.value; + + // Check for physical property names + if (propertyMap[keyName]) { + context.report({ + node: key, + messageId: 'useLogicalProperty', + data: { + physical: keyName, + logical: propertyMap[keyName], + }, + }); + } + + // Check for textAlign with physical values + if (keyName === 'textAlign' && value) { + if (value.type === 'Literal' && (value.value === 'left' || value.value === 'right')) { + const logicalValue = value.value === 'left' ? 'start' : 'end'; + context.report({ + node: value, + messageId: 'useLogicalTextAlign', + data: { + physical: value.value, + logical: logicalValue, + }, + }); + } + } + }; + + return { + Property(node) { + checkProperty(node.key, node.value); + }, + }; + }, +}; + export default tseslint.config([ { name: 'repo/ignores', @@ -248,6 +325,7 @@ export default tseslint.config([ rules: { 'no-global-object': noGlobalObject, 'no-unstable-methods': noUnstableMethods, + 'no-physical-css-properties': noPhysicalCssProperties, }, }, 'simple-import-sort': pluginSimpleImportSort, @@ -458,6 +536,13 @@ export default tseslint.config([ 'custom-rules/no-unstable-methods': 'error', }, }, + { + name: 'packages/ui', + files: ['packages/ui/src/**/*'], + rules: { + 'custom-rules/no-physical-css-properties': 'error', + }, + }, { name: 'packages - vitest', files: ['packages/*/src/**/*.test.{ts,tsx}'], diff --git a/packages/clerk-js/sandbox/template.html b/packages/clerk-js/sandbox/template.html index 4983019bdf9..cae088d5fb6 100644 --- a/packages/clerk-js/sandbox/template.html +++ b/packages/clerk-js/sandbox/template.html @@ -1,5 +1,8 @@ - +