From ad905c668aecb217a41982151dfd3c407e3b98f0 Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Mon, 15 Jun 2026 14:41:21 -0400 Subject: [PATCH 1/4] feat(ui): Implement LocalizationProvider within mosaic --- .../swingset/src/components/StoryEmbed.tsx | 12 +++- .../swingset/src/components/StoryPreview.tsx | 4 +- packages/ui/package.json | 1 + .../mosaic/__tests__/MosaicProvider.test.tsx | 9 ++- .../src/mosaic/__tests__/slot-recipe.test.ts | 4 +- .../aio/organization-profile.messages.ts | 8 +++ .../src/mosaic/aio/organization-profile.tsx | 11 ++-- .../components/__tests__/button.test.tsx | 7 +-- packages/ui/src/mosaic/components/box.tsx | 2 +- packages/ui/src/mosaic/i18n-registry.ts | 6 ++ .../appearance-provider.tsx} | 45 +++++++------- .../providers/localization-provider.tsx | 60 +++++++++++++++++++ .../src/mosaic/providers/mosaic-provider.tsx | 21 +++++++ packages/ui/src/mosaic/slot-recipe.ts | 2 +- packages/ui/src/mosaic/useSlot.ts | 2 +- 15 files changed, 153 insertions(+), 41 deletions(-) create mode 100644 packages/ui/src/mosaic/aio/organization-profile.messages.ts create mode 100644 packages/ui/src/mosaic/i18n-registry.ts rename packages/ui/src/mosaic/{MosaicProvider.tsx => providers/appearance-provider.tsx} (53%) create mode 100644 packages/ui/src/mosaic/providers/localization-provider.tsx create mode 100644 packages/ui/src/mosaic/providers/mosaic-provider.tsx diff --git a/packages/swingset/src/components/StoryEmbed.tsx b/packages/swingset/src/components/StoryEmbed.tsx index 1457332727c..59029e1b8b6 100644 --- a/packages/swingset/src/components/StoryEmbed.tsx +++ b/packages/swingset/src/components/StoryEmbed.tsx @@ -1,6 +1,6 @@ 'use client'; -import { MosaicProvider } from '@clerk/ui/mosaic/MosaicProvider'; +import { MosaicProvider } from '@clerk/ui/mosaic/providers/mosaic-provider'; import { Layers2Icon } from 'lucide-react'; import type React from 'react'; import { useState } from 'react'; @@ -32,7 +32,15 @@ export function StoryEmbed({ name, storyModule, composition }: StoryEmbedProps) const preview = (
- +
diff --git a/packages/swingset/src/components/StoryPreview.tsx b/packages/swingset/src/components/StoryPreview.tsx index ac202903489..841e3990fc6 100644 --- a/packages/swingset/src/components/StoryPreview.tsx +++ b/packages/swingset/src/components/StoryPreview.tsx @@ -1,6 +1,6 @@ 'use client'; -import { MosaicProvider } from '@clerk/ui/mosaic/MosaicProvider'; +import { MosaicProvider } from '@clerk/ui/mosaic/providers/mosaic-provider'; import { RotateCcwIcon, SlidersHorizontalIcon } from 'lucide-react'; import type React from 'react'; import { useEffect, useState } from 'react'; @@ -61,8 +61,8 @@ export function StoryPreview({ name, storyModule }: StoryPreviewProps) {
{mounted && ( diff --git a/packages/ui/package.json b/packages/ui/package.json index b885f3a8ff2..dd48cf45413 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -94,6 +94,7 @@ "type-check": "tsc --noEmit" }, "dependencies": { + "@clerk/i18n": "workspace:^", "@clerk/localizations": "workspace:^", "@clerk/shared": "workspace:^", "@emotion/cache": "11.11.0", diff --git a/packages/ui/src/mosaic/__tests__/MosaicProvider.test.tsx b/packages/ui/src/mosaic/__tests__/MosaicProvider.test.tsx index 1e8958c7718..f87fcc147fe 100644 --- a/packages/ui/src/mosaic/__tests__/MosaicProvider.test.tsx +++ b/packages/ui/src/mosaic/__tests__/MosaicProvider.test.tsx @@ -4,7 +4,7 @@ import { describe, expect, it } from 'vitest'; import type { MosaicAppearance } from '../appearance'; import { parseMosaicAppearance, useMosaicAppearance } from '../appearance'; -import { MosaicProvider, useMosaicTheme } from '../MosaicProvider'; +import { MosaicProvider, useMosaicTheme } from '../providers/mosaic-provider'; const appearance: MosaicAppearance = { elements: { @@ -42,7 +42,12 @@ describe('parseMosaicAppearance', () => { describe('MosaicProvider appearance context', () => { it('exposes [global, scoped] layers via useMosaicAppearance', () => { const { result } = renderHook(() => useMosaicAppearance(), { - wrapper: ({ children }) => React.createElement(MosaicProvider, { appearance, scope: 'signIn' }, children), + wrapper: ({ children }) => + React.createElement( + MosaicProvider, + { appearance: { elements: appearance.elements, scope: 'signIn' } }, + children, + ), }); expect(result.current).toEqual([{ button: { color: 'green' } }, { button: { color: 'red' } }]); }); diff --git a/packages/ui/src/mosaic/__tests__/slot-recipe.test.ts b/packages/ui/src/mosaic/__tests__/slot-recipe.test.ts index f50b76e7046..24b8a5084f5 100644 --- a/packages/ui/src/mosaic/__tests__/slot-recipe.test.ts +++ b/packages/ui/src/mosaic/__tests__/slot-recipe.test.ts @@ -3,13 +3,13 @@ import React from 'react'; import { describe, expect, it } from 'vitest'; import type { MosaicAppearance } from '../appearance'; -import { MosaicProvider } from '../MosaicProvider'; +import { MosaicProvider } from '../providers/mosaic-provider'; import { defineSlotRecipe, useRecipe } from '../slot-recipe'; import { slot, useSlot } from '../useSlot'; function wrapper(appearance?: MosaicAppearance, scope?: string) { return function Wrapper({ children }: { children: React.ReactNode }) { - return React.createElement(MosaicProvider, { appearance, scope }, children); + return React.createElement(MosaicProvider, { appearance: { ...appearance, scope } }, children); }; } diff --git a/packages/ui/src/mosaic/aio/organization-profile.messages.ts b/packages/ui/src/mosaic/aio/organization-profile.messages.ts new file mode 100644 index 00000000000..41796315da5 --- /dev/null +++ b/packages/ui/src/mosaic/aio/organization-profile.messages.ts @@ -0,0 +1,8 @@ +export const orgProfileBase = { + title: 'Organization Profile', + tab: { + general: 'General', + members: 'Members', + }, + membersContent: 'Members content', +}; diff --git a/packages/ui/src/mosaic/aio/organization-profile.tsx b/packages/ui/src/mosaic/aio/organization-profile.tsx index 743da8386b5..43b277a6c03 100644 --- a/packages/ui/src/mosaic/aio/organization-profile.tsx +++ b/packages/ui/src/mosaic/aio/organization-profile.tsx @@ -1,8 +1,11 @@ import { Box } from '../components/box'; import { Tabs } from '../components/tabs'; import { OrganizationProfileGeneral } from '../panels/organization-profile-general'; +import { useMessages } from '../providers/localization-provider'; +import { orgProfileBase } from './organization-profile.messages'; export function OrganizationProfile() { + const m = useMessages('organizationProfile', orgProfileBase); return ( ({ @@ -17,12 +20,12 @@ export function OrganizationProfile() { marginBlockEnd: t.spacing(8), })} > - Organization Profile + {m.title} - General - Members + {m.tab.general} + {m.tab.members} @@ -36,7 +39,7 @@ export function OrganizationProfile() { textAlign: 'center', })} > - Members content + {m.membersContent} diff --git a/packages/ui/src/mosaic/components/__tests__/button.test.tsx b/packages/ui/src/mosaic/components/__tests__/button.test.tsx index beada857bc8..9860e2fa8fc 100644 --- a/packages/ui/src/mosaic/components/__tests__/button.test.tsx +++ b/packages/ui/src/mosaic/components/__tests__/button.test.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { describe, expect, it } from 'vitest'; import type { MosaicAppearance } from '../../appearance'; -import { MosaicProvider } from '../../MosaicProvider'; +import { MosaicProvider } from '../../providers/mosaic-provider'; import { Button } from '../button'; /** Concatenates every inserted Emotion `