Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
adb2474
feat: add RTL direction infrastructure and migrate primitives to logi…
garrity-miepub Jul 22, 2026
f4419d8
refactor(rtl-scan): replace lookbehind with boundary group for clarity
garrity-miepub Jul 22, 2026
baabb79
fix(preset): safelist LTR data-[state=checked] Switch thumb classes
garrity-miepub Jul 23, 2026
3c1385a
Merge branch 'main' into feat/rtl-direction-switch
garrity-miepub Jul 23, 2026
1994303
fix(rtl): migrate MediaEditor/TranscriptView to logical properties; a…
garrity-miepub Jul 23, 2026
1736597
test(useDirection): cover isRtlLocale and direction resolution/observ…
garrity-miepub Jul 23, 2026
9e1991e
feat(rtl): migrate layout & navigation batch to logical properties
garrity-miepub Jul 23, 2026
4d9d244
Merge remote-tracking branch 'origin/main' into feat/rtl-direction-sw…
garrity-miepub Jul 24, 2026
a5120c4
chore(rtl): tighten baseline after main merge (507 -> 488 matches)
garrity-miepub Jul 24, 2026
20b030f
feat(rtl): add physical->logical codemod, pilot on CookieConsent
garrity-miepub Jul 25, 2026
0feb04c
feat(rtl): migrate DocumentScanner, Messaging, AI to logical properties
garrity-miepub Jul 25, 2026
36a8ffa
Merge remote-tracking branch 'origin/main' into feat/rtl-direction-sw…
garrity-miepub Jul 25, 2026
5a9fbc8
fix(rtl): reconcile media safelist after main merge
garrity-miepub Jul 25, 2026
4457b26
fix(rtl): sync document lang with locale global; filter explicit code…
garrity-miepub Jul 25, 2026
bf90b2b
fix(rtl): format codemod-touched files; re-sync useDirection on ref swap
garrity-miepub Jul 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ jobs:
- name: Lint
run: pnpm lint

- name: RTL guard (no new physical-direction classes)
run: pnpm rtl:scan

- name: Type check
run: pnpm typecheck

Expand Down
53 changes: 50 additions & 3 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { webchartBrand } from '../src/brands/webchart';
import type { BrandConfig } from '../src/brands/types';
import { CodeLookup } from '../src/components/CodeLookup';
import { CodeLookupProvider } from '../src/components/CodeLookup/context';
import { isRtlLocale } from '../src/hooks/useDirection';

// Map of available brands
const brands: Record<string, BrandConfig> = {
Expand All @@ -29,6 +30,16 @@ const brands: Record<string, BrandConfig> = {
webchart: webchartBrand,
};

/*
* Resolve the effective text direction from the direction/locale globals.
* 'auto' derives it from the locale (rtl for ar/he/fa/ur).
*/
function resolveGlobalDirection(globals: Record<string, unknown>): 'ltr' | 'rtl' {
const direction = (globals?.direction as string) || 'auto';
if (direction === 'ltr' || direction === 'rtl') return direction;
return isRtlLocale((globals?.locale as string) || 'en') ? 'rtl' : 'ltr';
}

/*
* Global theme listener — ensures data-theme and brand styles are applied
* even on docs-only MDX pages (like Introduction) where no story decorator runs.
Expand All @@ -55,6 +66,16 @@ function applyGlobalTheme(globals: Record<string, unknown>) {
document.body.classList.remove('condensed');
}

// Apply text direction (RTL preview) at the document level so CSS logical
// properties and `rtl:` variants respond everywhere, including docs pages.
document.documentElement.setAttribute('dir', resolveGlobalDirection(globals));
// Keep the document language in sync with the locale global so screen
// readers and locale-sensitive text shaping reflect the selected locale.
document.documentElement.setAttribute(
'lang',
(globals?.locale as string) || 'en'
);

document.body.style.backgroundColor = semanticColors.background;
document.body.style.color = semanticColors.foreground;
applyBrandStyles(brand, isDark);
Expand Down Expand Up @@ -84,7 +105,13 @@ try {
const [key, value] = pair.split(':');
if (key && value) globals[key] = value;
}
if (globals.theme || globals.brand || globals.density) {
if (
globals.theme ||
globals.brand ||
globals.density ||
globals.direction ||
globals.locale
) {
applyGlobalTheme(globals);
}
} catch {
Expand Down Expand Up @@ -201,11 +228,13 @@ const withBrand: Decorator = (Story, context) => {
const semanticColors = isDark ? brand.colors.dark : brand.colors.light;

const isCondensed = context.globals.density === 'condensed';
const direction = context.globals.direction as string | undefined;
const locale = context.globals.locale as string | undefined;

useEffect(() => {
// Delegate to shared applyGlobalTheme to keep a single source of truth
applyGlobalTheme(context.globals);
}, [brand, isDark, isCondensed, semanticColors]);
}, [brand, isDark, isCondensed, semanticColors, direction, locale]);

// Load Google Fonts for the brand
const fontLink = useMemo(() => {
Expand Down Expand Up @@ -251,8 +280,11 @@ const withBrand: Decorator = (Story, context) => {
// context); pass `codeLookup={false}` in a story to demo the plain-text opt-out.
const withCodeLookup: Decorator = (Story, context) => {
const locale = (context.globals.locale as string) || 'en';
// Codify shards only exist for these locales; fall back to English for
// preview-only locales (e.g. the RTL Arabic sample).
const lookupLocale = ['en', 'es'].includes(locale) ? locale : 'en';
return (
<CodeLookupProvider component={CodeLookup} indexUrl="/codify" locale={locale}>
<CodeLookupProvider component={CodeLookup} indexUrl="/codify" locale={lookupLocale}>
<Story />
</CodeLookupProvider>
);
Expand All @@ -264,6 +296,7 @@ const preview: Preview = {
theme: 'light',
density: 'standard',
locale: 'en',
direction: 'auto',
},
globalTypes: {
brand: {
Expand Down Expand Up @@ -316,6 +349,20 @@ const preview: Preview = {
items: [
{ value: 'en', title: '🇺🇸 English' },
{ value: 'es', title: '🇪🇸 Español (sample)' },
{ value: 'ar', title: '🇸🇦 العربية (RTL sample)' },
],
dynamicTitle: true,
},
},
direction: {
name: 'Direction',
description: 'Text direction (LTR/RTL preview)',
toolbar: {
icon: 'transfer',
items: [
{ value: 'auto', title: 'Auto (from language)' },
{ value: 'ltr', title: 'LTR' },
{ value: 'rtl', title: 'RTL' },
],
dynamicTitle: true,
},
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@
"typecheck": "tsc --noEmit",
"lint": "eslint \"src/**/*.{ts,tsx}\"",
"lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix",
"rtl:scan": "node scripts/rtl-scan.mjs",
"rtl:scan:update": "node scripts/rtl-scan.mjs --update",
"format": "prettier --check \"src/**/*.{ts,tsx,css}\"",
"format:fix": "prettier --write \"src/**/*.{ts,tsx,css}\"",
"test": "vitest run",
Expand Down
98 changes: 98 additions & 0 deletions scripts/rtl-baseline.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
{
"src/components/AGGrid/EnhancedCellRenderers.tsx": 2,
"src/components/AddContactModal/AddContactModal.tsx": 2,
"src/components/AdditionalFields/AdditionalFields.tsx": 2,
"src/components/Alert/Alert.tsx": 4,
"src/components/AllergyList/AllergyList.tsx": 1,
"src/components/Assessment/Assessment.tsx": 6,
"src/components/AudioPlayer/AudioPlayer.tsx": 1,
"src/components/AuthDialog/AuthDialog.tsx": 9,
"src/components/Autocomplete/Autocomplete.tsx": 1,
"src/components/Avatar/Avatar.tsx": 1,
"src/components/BookingDialog/BookingDialog.tsx": 3,
"src/components/BusinessHours/BusinessHours.tsx": 2,
"src/components/BusinessHoursEditor/BusinessHoursEditor.tsx": 2,
"src/components/CheckrIntegration/CheckrIntegration.tsx": 7,
"src/components/ClaimProviderForm/ClaimProviderForm.tsx": 2,
"src/components/CodeLookup/CodeLookup.tsx": 5,
"src/components/CommandPalette/CommandPalette.tsx": 10,
"src/components/ConditionEditor/ConditionEditor.tsx": 3,
"src/components/ConnectionStatus/ConnectionStatus.tsx": 4,
"src/components/CountBadge/CountBadge.tsx": 9,
"src/components/CountryCodeDropdown/CountryCodeDropdown.tsx": 1,
"src/components/CreateInvoiceModal/CreateInvoiceModal.tsx": 3,
"src/components/CreateReferralModal/CreateReferralModal.tsx": 4,
"src/components/DashboardWidget/DashboardWidget.tsx": 9,
"src/components/DateInput/DateInput.tsx": 3,
"src/components/DateRangePicker/DateRangePicker.tsx": 7,
"src/components/Dropdown/Dropdown.tsx": 1,
"src/components/EditUserRoleModal/EditUserRoleModal.tsx": 2,
"src/components/EmployeeProfile/EmployeeProfile.tsx": 1,
"src/components/EmployerContactCard/EmployerContactCard.tsx": 1,
"src/components/EmployerList/EmployerList.tsx": 3,
"src/components/EmployerPricingCard/EmployerPricingCard.tsx": 3,
"src/components/EmployerServiceModal/EmployerServiceModal.tsx": 11,
"src/components/EmployerView/EmployerView.tsx": 2,
"src/components/ErrorPage/ErrorPage.tsx": 1,
"src/components/FileManager/FileManager.tsx": 2,
"src/components/FloatingWindow/FloatingWindow.tsx": 13,
"src/components/HRISProviderSelector/HRISProviderSelector.tsx": 5,
"src/components/HealthSurveillance/HealthSurveillance.tsx": 3,
"src/components/HelpSupportPanel/HelpSupportPanel.tsx": 3,
"src/components/InventoryManager/InventoryManager.tsx": 11,
"src/components/InviteUserModal/InviteUserModal.tsx": 2,
"src/components/InvoiceList/InvoiceList.tsx": 9,
"src/components/InvoicePaymentPage/InvoicePaymentPage.tsx": 5,
"src/components/InvoiceView/InvoiceView.tsx": 12,
"src/components/LanguageSelector/LanguageSelector.tsx": 7,
"src/components/LoadingPage/LoadingPage.tsx": 1,
"src/components/Markdown/CsvBlock.tsx": 2,
"src/components/Markdown/SurveyBlock.tsx": 1,
"src/components/MedicationList/MedicationList.tsx": 1,
"src/components/OnboardingWizard/OnboardingWizard.tsx": 6,
"src/components/OrderCard/OrderCard.tsx": 2,
"src/components/OrderConfirmationWizard/OrderConfirmationWizard.tsx": 5,
"src/components/OrderList/OrderList.tsx": 4,
"src/components/OrderLookupForm/OrderLookupForm.tsx": 2,
"src/components/OrderSidebar/OrderSidebar.tsx": 3,
"src/components/PatientHeader/PatientHeader.tsx": 3,
"src/components/PaymentHistoryTable/PaymentHistoryTable.tsx": 8,
"src/components/PendingClaimsTable/PendingClaimsTable.tsx": 5,
"src/components/PermissionsEditor/PermissionsEditor.tsx": 10,
"src/components/PresentingProblems/PresentingProblems.tsx": 3,
"src/components/ProblemList/ProblemList.tsx": 7,
"src/components/ProductVersion/ProductVersion.tsx": 1,
"src/components/ProviderSearchBar/ProviderSearchBar.tsx": 2,
"src/components/ProviderSearchFilters/ProviderSearchFilters.tsx": 15,
"src/components/ProviderSelector/ProviderSelector.tsx": 5,
"src/components/ProviderUsersTable/ProviderUsersTable.tsx": 2,
"src/components/QuickAction/QuickAction.tsx": 1,
"src/components/QuickLinksCard/QuickLinksCard.tsx": 3,
"src/components/RecurringServiceCard/RecurringServiceCard.tsx": 3,
"src/components/RejectionModal/RejectionModal.tsx": 3,
"src/components/ReportDashboard/ReportDashboard.tsx": 1,
"src/components/ResultsEntryForm/ResultsEntryForm.tsx": 9,
"src/components/RichTextEditor/RichTextEditor.tsx": 1,
"src/components/RowActionToolbar/RowActionToolbar.tsx": 5,
"src/components/ScheduleCalendar/ScheduleCalendar.tsx": 7,
"src/components/ServiceAccordion/ServiceAccordion.tsx": 4,
"src/components/ServiceBadge/ServiceBadge.tsx": 5,
"src/components/ServiceCard/ServiceCard.tsx": 1,
"src/components/ServiceGeneralSettings/ServiceGeneralSettings.tsx": 5,
"src/components/ServicePicker/ServicePicker.tsx": 6,
"src/components/ServicePricingManager/ServicePricingManager.tsx": 6,
"src/components/SetupServiceModal/SetupServiceModal.tsx": 8,
"src/components/SiteFooter/SiteFooter.tsx": 2,
"src/components/SiteHeader/SiteHeader.tsx": 10,
"src/components/Slider/Slider.tsx": 1,
"src/components/SuperChat/SuperChat.tsx": 1,
"src/components/SuperChat/SuperChatConversations.tsx": 2,
"src/components/SuperChat/parts.tsx": 1,
"src/components/SuperChat/plugins/code.tsx": 1,
"src/components/SuperChat/plugins/nitroTable.tsx": 1,
"src/components/SuperChat/render/createMarkdownRenderer.tsx": 1,
"src/components/TableOfContents/TableOfContents.tsx": 4,
"src/components/Timeline/Timeline.tsx": 1,
"src/components/Toast/Toast.tsx": 4,
"src/components/WebChartReportViewer/WebChartReportViewer.tsx": 2
}
Loading
Loading