docs(ui): expand bilingual component guides
This commit is contained in:
@@ -1,31 +1,98 @@
|
||||
---
|
||||
title: Add responsive behavior
|
||||
description: Use shared breakpoint and interaction hooks when CSS alone cannot express the behavior.
|
||||
title: Responsive and interaction hooks
|
||||
description: Synchronize breakpoints when behavior must follow the viewport and add ripple feedback to custom controls.
|
||||
order: 20
|
||||
toc:
|
||||
- id: breakpoints
|
||||
title: Breakpoints
|
||||
- id: breakpoint-contract
|
||||
title: Breakpoint contract
|
||||
- id: reading-breakpoints
|
||||
title: Read breakpoints
|
||||
- id: root-classes
|
||||
title: Root classes
|
||||
- id: server-rendering
|
||||
title: Server rendering
|
||||
- id: ripple
|
||||
title: Ripple interaction
|
||||
---
|
||||
|
||||
## Breakpoints {#breakpoints}
|
||||
## Breakpoint contract {#breakpoint-contract}
|
||||
|
||||
The shared breakpoint splits the viewport into three ranges:
|
||||
|
||||
| Value | Range |
|
||||
| --------- | -------------------- |
|
||||
| `mobile` | Below 768px |
|
||||
| `tablet` | 768px through 1023px |
|
||||
| `desktop` | 1024px and above |
|
||||
|
||||
Prefer CSS for visual changes. Read a breakpoint during rendering only when mounted content, interaction behavior, or data volume must change.
|
||||
|
||||
## Read breakpoints {#reading-breakpoints}
|
||||
|
||||
```tsx
|
||||
import { useBreakpoint, useIsMobile } from "@workspace/ui/hooks/use-breakpoint"
|
||||
import {
|
||||
useBreakpoint,
|
||||
useIsDesktop,
|
||||
useIsMobile,
|
||||
useIsTablet,
|
||||
useOnBreakpoint,
|
||||
} from "@workspace/ui/hooks/use-breakpoint"
|
||||
|
||||
const breakpoint = useBreakpoint()
|
||||
const isMobile = useIsMobile()
|
||||
```
|
||||
|
||||
Prefer CSS responsive variants for visual changes. Use these hooks only when component behavior or mounted content must change.
|
||||
Every hook subscribes to the same pair of `matchMedia` queries through `useSyncExternalStore`. `useOnBreakpoint(listener)` runs only after the breakpoint changes; it does not report the initial value as a change.
|
||||
|
||||
## Root classes {#root-classes}
|
||||
|
||||
After mounting `BreakpointReactor`, the document root has exactly one of `.ui:mobile`, `.ui:tablet`, or `.ui:desktop`:
|
||||
|
||||
```tsx
|
||||
import { BreakpointReactor } from "@workspace/ui/hooks/use-breakpoint"
|
||||
|
||||
;<>
|
||||
<BreakpointReactor />
|
||||
<App />
|
||||
</>
|
||||
```
|
||||
|
||||
`globals.css` registers these as the custom `mobile:`, `tablet:`, and `desktop:` variants, allowing CSS and behavioral code to share the same three-range definition.
|
||||
|
||||
## Server rendering {#server-rendering}
|
||||
|
||||
The breakpoint store uses a deterministic desktop server snapshot. `getBreakpointInitializationScript` can assign the matching root class before hydration when the application needs CSS and behavioral breakpoints to agree immediately.
|
||||
The server snapshot is fixed to `desktop` so SSR remains deterministic. To write the real class before hydration, place the result of `getBreakpointInitializationScript()` in an inline script in the document head:
|
||||
|
||||
```tsx
|
||||
import { getBreakpointInitializationScript } from "@workspace/ui/hooks/use-breakpoint"
|
||||
|
||||
;<script
|
||||
dangerouslySetInnerHTML={{ __html: getBreakpointInitializationScript() }}
|
||||
/>
|
||||
```
|
||||
|
||||
Pass the application's nonce under a strict CSP. The script only synchronizes the initial class; it does not replace `BreakpointReactor` for later viewport changes.
|
||||
|
||||
## Ripple interaction {#ripple}
|
||||
|
||||
`useRipple` returns a ref callback that adds pointer-driven feedback to an interactive element. The element should establish a positioned containing block so the generated overlay uses the correct bounds.
|
||||
`Button` already uses `useRippleRef`, so ordinary buttons need no extra setup. Use the hook directly for a custom interactive surface:
|
||||
|
||||
```tsx
|
||||
import { useRipple } from "@workspace/ui/hooks/use-ripple"
|
||||
|
||||
export function Tile() {
|
||||
const ref = useRipple<HTMLButtonElement>({
|
||||
duration: 500,
|
||||
opacity: 0.2,
|
||||
rippleClassName: "bg-primary",
|
||||
})
|
||||
|
||||
return (
|
||||
<button ref={ref} className="relative">
|
||||
Open project
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
The element must establish a positioned containing block, normally with `relative`. The hook ignores disabled controls, `aria-disabled="true"`, and non-primary mouse buttons, and removes listeners and animation nodes when the ref changes or unmounts. Use `useRippleRef(ref, options)` when merging an external ref.
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
---
|
||||
title: Localize UI
|
||||
description: Load only the UI catalog and calendar locale required by the active language.
|
||||
description: Bridge the application translator into UI components and load the active calendar locale.
|
||||
order: 21
|
||||
toc:
|
||||
- id: catalog-source
|
||||
title: Catalog source
|
||||
- id: direct-imports
|
||||
title: Direct imports
|
||||
- id: supported-locales
|
||||
title: Supported locales
|
||||
- id: two-layers
|
||||
title: Two responsibilities
|
||||
- id: catalogs
|
||||
title: Merge catalogs
|
||||
- id: bridge-provider
|
||||
title: Bridge the provider
|
||||
- id: fallback
|
||||
title: Fallback behavior
|
||||
- id: direction
|
||||
title: Text direction
|
||||
---
|
||||
|
||||
## Catalog source {#catalog-source}
|
||||
## Two responsibilities {#two-layers}
|
||||
|
||||
Add the UI locale source to the application's internationalization configuration:
|
||||
`@workspace/i18n` manages the application locale, catalog loading, formatting, and switching. `@workspace/ui/components/i18n` is a small adapter that keeps UI primitives independent of an internationalization framework. It accepts a compatible translator, a language tag, and a `react-day-picker` locale.
|
||||
|
||||
This separation keeps the UI package reusable while allowing applications to use the full workspace internationalization runtime.
|
||||
|
||||
## Merge catalogs {#catalogs}
|
||||
|
||||
Add the UI catalog source to the application's `i18n.config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -21,16 +31,50 @@ Add the UI locale source to the application's internationalization configuration
|
||||
}
|
||||
```
|
||||
|
||||
The locales root contains metadata and types. It does not import every language catalog.
|
||||
The `@workspace/ui/locales` root exports only `uiCatalogLocales`, `uiMessageIds`, and types; it does not statically import every language. The current supported set is `en-US` and `zh-Hans`.
|
||||
|
||||
## Direct imports {#direct-imports}
|
||||
## Bridge the provider {#bridge-provider}
|
||||
|
||||
```ts
|
||||
import { calendarLocale, messages } from "@workspace/ui/locales/en-US"
|
||||
Read the translator from the current application provider and pass it to the UI provider:
|
||||
|
||||
```tsx
|
||||
import { useLocaleDefinition, useTranslate } from "@workspace/i18n"
|
||||
import {
|
||||
I18nProvider as UiI18nProvider,
|
||||
type CalendarLocale,
|
||||
} from "@workspace/ui/components/i18n"
|
||||
import { calendarLocale as enUS } from "@workspace/ui/locales/en-US"
|
||||
import { calendarLocale as zhHans } from "@workspace/ui/locales/zh-Hans"
|
||||
|
||||
const calendarLocales: Record<string, CalendarLocale> = {
|
||||
"en-US": enUS,
|
||||
"zh-Hans": zhHans,
|
||||
}
|
||||
|
||||
export function AppUiI18nProvider({ children }: React.PropsWithChildren) {
|
||||
const locale = useLocaleDefinition()
|
||||
const t = useTranslate()
|
||||
|
||||
return (
|
||||
<UiI18nProvider
|
||||
calendarLocale={calendarLocales[locale.locale]}
|
||||
locale={locale.languageTag ?? locale.locale}
|
||||
t={t}
|
||||
>
|
||||
{children}
|
||||
</UiI18nProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Each language entry also exports its matching `react-day-picker` locale.
|
||||
`Calendar` prefers its own `locale` prop, then the provider's `calendarLocale`. Month formatting and the default `lang` use the provider language tag.
|
||||
|
||||
## Supported locales {#supported-locales}
|
||||
## Fallback behavior {#fallback}
|
||||
|
||||
The UI catalogs include American English (`en-US`) and Simplified Chinese (`zh-Hans`).
|
||||
Without a provider, the UI layer uses `en-US` and the English source text from each descriptor. Isolated tests and stories therefore remain usable, but production applications should bridge the provider so accessible names and visible text use the same locale.
|
||||
|
||||
Custom components may use `uiMessages`, `useMessage`, `useTranslate`, `LocalizedText`, `useLocale`, and `useCalendarLocale`. Product copy belongs in the application or domain package, not in the UI catalog.
|
||||
|
||||
## Text direction {#direction}
|
||||
|
||||
Translation locale and layout direction are separate concerns. When switching locales, the application should synchronize `document.documentElement.lang` and `dir`, and use `DirectionProvider` when needed. Local components use logical utilities such as `start`, `end`, `ps`, and `pe`; `components.json` also enables `rtl` generation.
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: Workspace adaptations
|
||||
description: Record the implementation differences that must survive updates from the shadcn/ui Base UI source.
|
||||
order: 22
|
||||
toc:
|
||||
- id: baseline
|
||||
title: Upstream baseline
|
||||
- id: icons-and-ripple
|
||||
title: Icons and ripple
|
||||
- id: localization
|
||||
title: Localization
|
||||
- id: popover
|
||||
title: Popover extensions
|
||||
- id: select
|
||||
title: Select correction
|
||||
- id: upgrade-checklist
|
||||
title: Upgrade checklist
|
||||
---
|
||||
|
||||
## Upstream baseline {#baseline}
|
||||
|
||||
The local components began from shadcn/ui's Base UI registry with the `base-vega` style. Use the upstream [component documentation](https://ui.shadcn.com/docs/components/base) and [GitHub source directory](https://github.com/shadcn-ui/ui/tree/main/apps/v4/registry/bases/base/ui) as references, but do not assume the latest upstream version is identical to the workspace snapshot.
|
||||
|
||||
When determining behavior, prefer the local source, then this documentation, then local `components.json`, and only then the latest upstream example.
|
||||
|
||||
## Icons and ripple {#icons-and-ripple}
|
||||
|
||||
`components/icon.tsx` maps Lucide icon names to `@icones/react` components and preserves a set of upstream-compatible export names. Existing components import through that entry point. Convert any `lucide-react` import introduced by the CLI back to the local module.
|
||||
|
||||
`Button` merges its incoming ref with `useRippleRef`. Preserve relative positioning, ref merging, and disabled behavior when updating Button.
|
||||
|
||||
## Localization {#localization}
|
||||
|
||||
Breadcrumb, Calendar, Carousel, Dialog, Pagination, Sheet, Sidebar, Spinner, Toast, and related components replace some hard-coded English with descriptors from `components/i18n.tsx`. When updating these files, inspect accessible labels, screen-reader-only text, and default descriptions in addition to visible UI.
|
||||
|
||||
Only `en-US` and `zh-Hans` catalogs are published. Adding a message requires synchronized changes to:
|
||||
|
||||
1. The `uiMessages` descriptor.
|
||||
2. The `uiMessageIds` type list.
|
||||
3. `messages` in both locale modules.
|
||||
4. The component that consumes the message.
|
||||
|
||||
## Popover extensions {#popover}
|
||||
|
||||
The local `PopoverContent` adds these capabilities beyond the upstream wrapper:
|
||||
|
||||
- Forwarded `anchor` and `positionMethod` options.
|
||||
- `showArrow`, accepting an `AnchoredSurface` configuration object.
|
||||
- `viewportClassName`, separating scrollable content from the overflow-visible outer surface.
|
||||
- An anchor-sized default side offset and continuous curved tail.
|
||||
- `createPopoverHandle` and `PopoverHandle` exports.
|
||||
|
||||
Do not replace the file wholesale with upstream source. In particular, test long-content scrolling, every side, collision flips, narrow triggers, and RTL.
|
||||
|
||||
## Select correction {#select}
|
||||
|
||||
`SelectTrigger` renders `ChevronDownIcon` as a child of `SelectPrimitive.Icon` instead of passing the icon component through `render`. This correction is intentional for the current Base UI and Icones combination. After a Select update, verify that the trigger shows exactly one icon and test SSR and hydration.
|
||||
|
||||
## Upgrade checklist {#upgrade-checklist}
|
||||
|
||||
1. Review the shadcn docs and matching registry source diff before replacing anything.
|
||||
2. Generate through the CLI on a temporary branch or stage files individually, preserving local public exports.
|
||||
3. Convert direct icon dependencies back to `components/icon`.
|
||||
4. Restore translated messages, logical-direction utilities, and local theme classes.
|
||||
5. Give Button, Popover, Select, Calendar, Toast, and Sidebar focused regression coverage.
|
||||
6. Run the UI typecheck, repository lint and format checks, then build a real consuming application.
|
||||
7. Manually inspect light, dark, LTR, RTL, mobile, tablet, and desktop behavior.
|
||||
Reference in New Issue
Block a user