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.
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
title: Anchored account menu
|
||||
description: Compose an account menu with Popover's continuous tail, scroll viewport, and Base UI render prop.
|
||||
order: 31
|
||||
toc:
|
||||
- id: example
|
||||
title: Example
|
||||
- id: why-these-props
|
||||
title: Why these props
|
||||
- id: accessibility
|
||||
title: Accessibility
|
||||
---
|
||||
|
||||
## Example {#example}
|
||||
|
||||
```tsx
|
||||
import { Avatar, AvatarFallback } from "@workspace/ui/components/avatar"
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
import { SettingsIcon } from "@workspace/ui/components/icon"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverDescription,
|
||||
PopoverTitle,
|
||||
PopoverTrigger,
|
||||
} from "@workspace/ui/components/popover"
|
||||
|
||||
export function AccountMenu() {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
render={
|
||||
<Button variant="ghost" size="icon" aria-label="Account menu" />
|
||||
}
|
||||
>
|
||||
<Avatar>
|
||||
<AvatarFallback>JF</AvatarFallback>
|
||||
</Avatar>
|
||||
</PopoverTrigger>
|
||||
|
||||
<PopoverContent
|
||||
align="end"
|
||||
side="bottom"
|
||||
showArrow={{ cornerRadius: "2xl", tailSize: "sm" }}
|
||||
className="w-80 gap-0 p-0"
|
||||
viewportClassName="max-h-[min(28rem,var(--available-height))] p-4"
|
||||
>
|
||||
<PopoverTitle>Jaydon Frankie</PopoverTitle>
|
||||
<PopoverDescription>demo@minimals.cc</PopoverDescription>
|
||||
<Button variant="ghost" className="mt-4 w-full justify-start">
|
||||
<SettingsIcon />
|
||||
Appearance
|
||||
</Button>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Why these props {#why-these-props}
|
||||
|
||||
`align="end"` aligns the logical end of the panel and trigger, adapting automatically in RTL. `showArrow` enables the local continuous tail. The outer `className` styles the whole surface while `viewportClassName` styles its scrolling region, allowing a long menu to scroll without clipping the tail.
|
||||
|
||||
## Accessibility {#accessibility}
|
||||
|
||||
Provide an `aria-label` when the trigger button contains only an icon or avatar. Use `PopoverTitle` and `PopoverDescription` in the panel, and let the Base UI Root own dismissal and focus return. If the content is semantically a set of choices or commands, use DropdownMenu or Command rather than assigning a menu role manually.
|
||||
@@ -1,37 +1,98 @@
|
||||
---
|
||||
title: Compose a form
|
||||
description: Combine field, input, and button primitives while keeping domain state in the application.
|
||||
title: Compose a profile form
|
||||
description: Combine Field, Input, Select, and Button into an accessible form while keeping state in the application.
|
||||
order: 30
|
||||
toc:
|
||||
- id: create-the-form
|
||||
title: Create the form
|
||||
- id: complete-example
|
||||
title: Complete example
|
||||
- id: validation
|
||||
title: Validation
|
||||
title: Validation state
|
||||
- id: controlled-select
|
||||
title: Controlled Select
|
||||
- id: styling
|
||||
title: Styling
|
||||
title: Style overrides
|
||||
---
|
||||
|
||||
## Create the form {#create-the-form}
|
||||
## Complete example {#complete-example}
|
||||
|
||||
```tsx
|
||||
import * as React from "react"
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
import { Field, FieldError, FieldLabel } from "@workspace/ui/components/field"
|
||||
import {
|
||||
Field,
|
||||
FieldDescription,
|
||||
FieldError,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
} from "@workspace/ui/components/field"
|
||||
import { Input } from "@workspace/ui/components/input"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@workspace/ui/components/select"
|
||||
|
||||
;<form onSubmit={saveProfile}>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="display-name">Display name</FieldLabel>
|
||||
<Input id="display-name" name="displayName" />
|
||||
<FieldError>{errors.displayName}</FieldError>
|
||||
</Field>
|
||||
<Button type="submit">Save profile</Button>
|
||||
</form>
|
||||
export function ProfileForm() {
|
||||
const [role, setRole] = React.useState("editor")
|
||||
const [error, setError] = React.useState("")
|
||||
|
||||
function submit(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault()
|
||||
const data = new FormData(event.currentTarget)
|
||||
setError(data.get("displayName") ? "" : "Enter a display name")
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={submit} className="max-w-md space-y-6">
|
||||
<FieldGroup>
|
||||
<Field data-invalid={Boolean(error)}>
|
||||
<FieldLabel htmlFor="display-name">Display name</FieldLabel>
|
||||
<Input
|
||||
id="display-name"
|
||||
name="displayName"
|
||||
aria-invalid={Boolean(error)}
|
||||
aria-describedby="display-name-error"
|
||||
/>
|
||||
<FieldDescription>
|
||||
This name is visible to other members.
|
||||
</FieldDescription>
|
||||
<FieldError id="display-name-error">{error}</FieldError>
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<FieldLabel htmlFor="role">Role</FieldLabel>
|
||||
<Select
|
||||
value={role}
|
||||
onValueChange={(value) => setRole(value ?? "editor")}
|
||||
>
|
||||
<SelectTrigger id="role">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="viewer">Viewer</SelectItem>
|
||||
<SelectItem value="editor">Editor</SelectItem>
|
||||
<SelectItem value="admin">Administrator</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
|
||||
<Button type="submit">Save profile</Button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Validation {#validation}
|
||||
## Validation state {#validation}
|
||||
|
||||
The UI package renders validation state but does not select a form library or schema. Connect native form data, React state, or a form framework at the application boundary.
|
||||
The UI package does not select a validation library. The application produces errors and connects the same state to `Field data-invalid`, the control's `aria-invalid`, and `FieldError`. Multiple schema errors can be passed through `FieldError errors={errors}`; the component deduplicates them before rendering a list.
|
||||
|
||||
## Styling {#styling}
|
||||
## Controlled Select {#controlled-select}
|
||||
|
||||
Use component variants for supported semantic changes and `className` for local layout. Use `cn` from `@workspace/ui/lib/utils` when conditional classes or consumer overrides must be merged.
|
||||
The local Select is built on Base UI and uses `value` with `onValueChange`. `SelectTrigger` renders its own arrow, so it only needs `SelectValue`; do not add another chevron. The popup aligns to the trigger and adopts its width by default. Adjust `side`, `align`, `sideOffset`, and `alignItemWithTrigger` on `SelectContent` when required.
|
||||
|
||||
## Style overrides {#styling}
|
||||
|
||||
Use the existing `variant`, `size`, and structural parts first, then apply page layout through `className`. Components merge classes with `cn`, allowing consumer Tailwind classes to override default utilities. Promote overrides repeated across pages into a new variant or composite component instead of copying long class strings.
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: Message thread
|
||||
description: Compose a scroll container, message layout, and bubbles while preserving the reader's position in history.
|
||||
order: 32
|
||||
toc:
|
||||
- id: structure
|
||||
title: Component structure
|
||||
- id: messages
|
||||
title: Render messages
|
||||
- id: ownership
|
||||
title: State ownership
|
||||
---
|
||||
|
||||
## Component structure {#structure}
|
||||
|
||||
```tsx
|
||||
import {
|
||||
MessageScroller,
|
||||
MessageScrollerButton,
|
||||
MessageScrollerContent,
|
||||
MessageScrollerItem,
|
||||
MessageScrollerProvider,
|
||||
MessageScrollerViewport,
|
||||
} from "@workspace/ui/components/message-scroller"
|
||||
|
||||
export function Thread({ messages }: { messages: ChatMessage[] }) {
|
||||
return (
|
||||
<MessageScrollerProvider>
|
||||
<MessageScroller className="h-[32rem]">
|
||||
<MessageScrollerViewport>
|
||||
<MessageScrollerContent className="gap-5 p-4">
|
||||
{messages.map((message, index) => (
|
||||
<MessageScrollerItem
|
||||
key={message.id}
|
||||
messageId={message.id}
|
||||
scrollAnchor={index === messages.length - 1}
|
||||
>
|
||||
<ChatMessageRow message={message} />
|
||||
</MessageScrollerItem>
|
||||
))}
|
||||
</MessageScrollerContent>
|
||||
</MessageScrollerViewport>
|
||||
<MessageScrollerButton />
|
||||
</MessageScroller>
|
||||
</MessageScrollerProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Render messages {#messages}
|
||||
|
||||
Inside `ChatMessageRow`, use `Message`, `MessageAvatar`, `MessageContent`, `MessageHeader`, and `MessageFooter` for participant structure, then compose one or more `Bubble` elements inside the content. Sent messages can use `align="end"`; do not swap message ownership based on the language direction.
|
||||
|
||||
Place attachments in the same `MessageContent`. For content without a bubble background, use `Bubble variant="ghost"`; the header and footer automatically remove the horizontal padding associated with a bubble.
|
||||
|
||||
## State ownership {#ownership}
|
||||
|
||||
`MessageScroller` owns bottom-follow behavior, visibility, and the jump button. The application owns the message array, pagination, loading, read state, and retries. Keep stable IDs when prepending older messages and use the scroller API to preserve the visual anchor instead of copying another set of scroll listeners into the feature.
|
||||
@@ -1,30 +1,103 @@
|
||||
---
|
||||
title: Choose components
|
||||
description: Select primitives by responsibility instead of importing a single monolithic component index.
|
||||
title: Component catalog
|
||||
description: Find component entry points by responsibility and understand the local Base UI composition model.
|
||||
order: 11
|
||||
toc:
|
||||
- id: forms
|
||||
title: Forms and input
|
||||
- id: overlays
|
||||
title: Overlays and menus
|
||||
- id: content
|
||||
- id: composition-model
|
||||
title: Composition model
|
||||
- id: actions-and-forms
|
||||
title: Actions and forms
|
||||
- id: overlays-and-navigation
|
||||
title: Overlays and navigation
|
||||
- id: content-and-feedback
|
||||
title: Content and feedback
|
||||
- id: layout-and-data
|
||||
title: Layout and data
|
||||
- id: icons
|
||||
title: Icons
|
||||
---
|
||||
|
||||
## Forms and input {#forms}
|
||||
## Composition model {#composition-model}
|
||||
|
||||
Use `Button`, `Input`, `Textarea`, `Field`, and `Label` for ordinary forms. Add `Checkbox`, `RadioGroup`, `Switch`, or `Slider` for choices, and `Select`, `Combobox`, `Command`, or `Calendar` for richer selection.
|
||||
The components follow shadcn/ui's model of composing primitives from editable source, with the Base UI implementation selected in this workspace. A component is usually split into Root, Trigger, Content, Item, and related parts. The underlying primitive owns state and accessibility, while Tailwind classes and `data-slot` expose presentation.
|
||||
|
||||
## Overlays and menus {#overlays}
|
||||
Base UI uses `render` to compose a custom element, rather than the `asChild` prop commonly seen in Radix examples:
|
||||
|
||||
`Dialog`, `AlertDialog`, `Sheet`, and `Drawer` cover modal surfaces. `Popover`, `HoverCard`, and `Tooltip` provide anchored information, while the menu modules cover context, dropdown, menubar, and navigation patterns.
|
||||
```tsx
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
import { Dialog, DialogTrigger } from "@workspace/ui/components/dialog"
|
||||
|
||||
## Content and feedback {#content}
|
||||
;<Dialog>
|
||||
<DialogTrigger render={<Button variant="outline" />}>
|
||||
Edit profile
|
||||
</DialogTrigger>
|
||||
</Dialog>
|
||||
```
|
||||
|
||||
Cards, items, tables, charts, progress, skeletons, spinners, empty states, messages, and toasts provide the common display vocabulary. Accordion, Collapsible, Tabs, Carousel, and Pagination organize larger content sets.
|
||||
Use `render` only when replacing the underlying element. Pass ordinary content as children.
|
||||
|
||||
## Actions and forms {#actions-and-forms}
|
||||
|
||||
| Goal | Component entry points |
|
||||
| --------------------------- | -------------------------------------------------- |
|
||||
| Buttons and grouped actions | `button`, `button-group`, `toggle`, `toggle-group` |
|
||||
| Text input | `input`, `textarea`, `input-group`, `input-otp` |
|
||||
| Field structure | `field`, `label` |
|
||||
| Boolean and single choice | `checkbox`, `switch`, `radio-group` |
|
||||
| Range and progress | `slider`, `progress` |
|
||||
| Options and commands | `native-select`, `select`, `combobox`, `command` |
|
||||
| Dates | `calendar` |
|
||||
|
||||
Prefer `NativeSelect` for simple, mobile-friendly choices that do not need a custom popup. Choose `Combobox`, `Select`, or `Command` when the interaction needs search, custom items, or command behavior.
|
||||
|
||||
## Overlays and navigation {#overlays-and-navigation}
|
||||
|
||||
| Goal | Component entry points |
|
||||
| ------------------------------ | -------------------------------------------------------- |
|
||||
| Modal confirmation and editing | `alert-dialog`, `dialog`, `sheet`, `drawer` |
|
||||
| Anchored content | `popover`, `hover-card`, `tooltip`, `anchored-surface` |
|
||||
| Menus | `dropdown-menu`, `context-menu`, `menubar` |
|
||||
| Page navigation | `breadcrumb`, `navigation-menu`, `pagination`, `sidebar` |
|
||||
|
||||
Dialog, Sheet, and Drawer expose controlled state and composable parts. Use `AlertDialog` for consequential destructive actions, `Tooltip` for brief descriptions, and `Popover` for an anchored panel containing interactive controls.
|
||||
|
||||
## Content and feedback {#content-and-feedback}
|
||||
|
||||
| Goal | Component entry points |
|
||||
| ------------------------- | ----------------------------------------------------- |
|
||||
| Containers and states | `card`, `item`, `empty`, `alert`, `badge`, `marker` |
|
||||
| Loading and notifications | `skeleton`, `spinner`, `toast` |
|
||||
| Messaging UI | `message`, `bubble`, `attachment`, `message-scroller` |
|
||||
| Sectioned content | `accordion`, `collapsible`, `tabs`, `carousel` |
|
||||
|
||||
`Message` owns participant layout, alignment, and metadata; `Bubble` owns the message surface; `Attachment` expresses an upload lifecycle; and `MessageScroller` handles follow mode and jump controls. They are presentation primitives and do not own conversation data.
|
||||
|
||||
## Layout and data {#layout-and-data}
|
||||
|
||||
| Goal | Component entry points |
|
||||
| -------------- | ------------------------------------------------------- |
|
||||
| Data display | `table`, `chart` |
|
||||
| Layout | `separator`, `aspect-ratio`, `resizable`, `scroll-area` |
|
||||
| Direction | `direction` |
|
||||
| Keyboard hints | `kbd` |
|
||||
| Avatars | `avatar` |
|
||||
|
||||
The chart module wraps Recharts containers, tooltips, legends, and style mappings without choosing an application data model. `DirectionProvider` re-exports Base UI's direction context; the page should still synchronize the HTML `dir` attribute.
|
||||
|
||||
## Icons {#icons}
|
||||
|
||||
Import named icons from `@workspace/ui/components/icon`. The module centralizes the `@icones/react` vocabulary so applications and packages use the same icon source.
|
||||
Import named icons from the local Icones adapter:
|
||||
|
||||
```tsx
|
||||
import { SearchIcon, SettingsIcon } from "@workspace/ui/components/icon"
|
||||
```
|
||||
|
||||
Components provide a default size to SVGs that do not have an explicit size class. Icon buttons still require an accessible name:
|
||||
|
||||
```tsx
|
||||
<Button size="icon" aria-label="Search">
|
||||
<SearchIcon aria-hidden="true" />
|
||||
</Button>
|
||||
```
|
||||
|
||||
Do not reintroduce `lucide-react` in new components. Register additional icons through `createIcon` in `components/icon.tsx` so the workspace keeps one icon path.
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
title: Feedback and messaging
|
||||
description: Configure Toast and compose messaging interfaces from Message, Bubble, Attachment, and MessageScroller.
|
||||
order: 13
|
||||
toc:
|
||||
- id: toast-provider
|
||||
title: Toast Provider
|
||||
- id: send-toast
|
||||
title: Send a toast
|
||||
- id: message-composition
|
||||
title: Message composition
|
||||
- id: attachments
|
||||
title: Attachment states
|
||||
- id: scrolling
|
||||
title: Message scrolling
|
||||
---
|
||||
|
||||
## Toast Provider {#toast-provider}
|
||||
|
||||
Mount one `Toaster` near the application root:
|
||||
|
||||
```tsx
|
||||
import { Toaster } from "@workspace/ui/components/toast"
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<>
|
||||
<Routes />
|
||||
<Toaster />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
The default viewport is fixed to the bottom on small screens and the bottom-right on larger screens. Toasts support stacking, expansion, swipe dismissal, keyboard focus, and icons for `success`, `info`, `warning`, `error`, and `loading` types.
|
||||
|
||||
## Send a toast {#send-toast}
|
||||
|
||||
Use the manager exported by the module:
|
||||
|
||||
```tsx
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
import { toast } from "@workspace/ui/components/toast"
|
||||
|
||||
;<Button
|
||||
onClick={() =>
|
||||
toast.add({
|
||||
title: "Saved",
|
||||
description: "Your profile has been updated.",
|
||||
type: "success",
|
||||
})
|
||||
}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
```
|
||||
|
||||
When notifications need an isolated scope, create a manager with `createToastManager()` and pass it to both `Toaster` and the caller. The default accessible name of the close button comes from the UI localization context.
|
||||
|
||||
## Message composition {#message-composition}
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Message,
|
||||
MessageContent,
|
||||
MessageFooter,
|
||||
MessageGroup,
|
||||
} from "@workspace/ui/components/message"
|
||||
import { Bubble, BubbleContent } from "@workspace/ui/components/bubble"
|
||||
|
||||
;<MessageGroup>
|
||||
<Message align="end">
|
||||
<MessageContent>
|
||||
<Bubble align="end" variant="tinted">
|
||||
<BubbleContent>Tomorrow at three works for me.</BubbleContent>
|
||||
</Bubble>
|
||||
<MessageFooter>Read · 14:32</MessageFooter>
|
||||
</MessageContent>
|
||||
</Message>
|
||||
</MessageGroup>
|
||||
```
|
||||
|
||||
Keep message IDs, authors, delivery state, and actions in the product layer. The UI primitives derive layout only from props and `data-*` state.
|
||||
|
||||
## Attachment states {#attachments}
|
||||
|
||||
`Attachment` supports `idle`, `uploading`, `processing`, `error`, and `done`. `AttachmentMedia`, `AttachmentTitle`, `AttachmentDescription`, and `AttachmentActions` adapt their presentation to the state; the application still owns progress and retry behavior.
|
||||
|
||||
Use the default `orientation="horizontal"` for rows and `orientation="vertical"` for thumbnail grids. `AttachmentGroup` adds horizontal scrolling and snap behavior for a collection.
|
||||
|
||||
## Message scrolling {#scrolling}
|
||||
|
||||
Keep `MessageScrollerProvider`, `MessageScroller`, `MessageScrollerViewport`, and `MessageScrollerContent` in that hierarchy. Wrap each message in `MessageScrollerItem`, adding `scrollAnchor` to the item that should anchor follow mode. `MessageScrollerButton direction="end"` reveals and hides itself from the current scroll state.
|
||||
|
||||
Do not assign `scrollTop` directly when appending messages. Use the follow and visibility state exposed by these primitives so a user reading older messages is not forced back to the bottom.
|
||||
@@ -0,0 +1,113 @@
|
||||
---
|
||||
title: Overlays and positioning
|
||||
description: Compose Dialog and Popover with Base UI render semantics and the local AnchoredSurface.
|
||||
order: 12
|
||||
toc:
|
||||
- id: dialog
|
||||
title: Dialog
|
||||
- id: popover
|
||||
title: Popover
|
||||
- id: anchored-surface
|
||||
title: Continuous tail
|
||||
- id: positioning
|
||||
title: Custom positioning
|
||||
- id: choosing-an-overlay
|
||||
title: Choose an overlay
|
||||
---
|
||||
|
||||
## Dialog {#dialog}
|
||||
|
||||
```tsx
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@workspace/ui/components/dialog"
|
||||
|
||||
export function ProfileDialog() {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger render={<Button variant="outline" />}>
|
||||
Edit profile
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit profile</DialogTitle>
|
||||
<DialogDescription>Update your public information.</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div>Form content</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit">Save</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
`DialogContent` already includes the portal, overlay, and default close button. The title and description connect to the underlying accessibility attributes, so do not replace them with unstructured `div` elements.
|
||||
|
||||
## Popover {#popover}
|
||||
|
||||
```tsx
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverDescription,
|
||||
PopoverTitle,
|
||||
PopoverTrigger,
|
||||
} from "@workspace/ui/components/popover"
|
||||
|
||||
;<Popover>
|
||||
<PopoverTrigger render={<Button variant="outline" />}>
|
||||
Open settings
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end">
|
||||
<PopoverTitle>Display settings</PopoverTitle>
|
||||
<PopoverDescription>Choose the list density.</PopoverDescription>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
```
|
||||
|
||||
The local API continues to forward Base UI Popup and Positioner props. Pass `open` and `onOpenChange` to `Popover` when controlled state is required.
|
||||
|
||||
## Continuous tail {#anchored-surface}
|
||||
|
||||
The local `showArrow` is more than a triangular SVG. It renders `PopoverContent` through `AnchoredSurface`, producing one rounded surface with a continuous curved tail:
|
||||
|
||||
```tsx
|
||||
<PopoverContent
|
||||
side="bottom"
|
||||
showArrow={{ cornerRadius: "2xl", tailSize: "sm" }}
|
||||
className="w-80 p-0"
|
||||
viewportClassName="max-h-72 p-4"
|
||||
>
|
||||
{content}
|
||||
</PopoverContent>
|
||||
```
|
||||
|
||||
The outer surface remains `overflow: visible` so the tail is not clipped. Scrolling happens inside `data-slot="popover-viewport"`. Use `viewportClassName` when constraining content height or applying content padding.
|
||||
|
||||
`AnchoredSurface` can also render on its own, but it is visual only: it does not own anchor measurement, collision detection, focus, or a portal.
|
||||
|
||||
## Custom positioning {#positioning}
|
||||
|
||||
`PopoverContent` additionally exposes `anchor`, `positionMethod`, `arrowPadding`, `alignOffset`, and `sideOffset`. Pass virtual or custom anchors through the shared component instead of rebuilding the Base UI primitives.
|
||||
|
||||
For control outside React, create a handle with `createPopoverHandle<Payload>()` and connect it to the Root using Base UI's handle pattern.
|
||||
|
||||
## Choose an overlay {#choosing-an-overlay}
|
||||
|
||||
- Block background interaction with `Dialog` or `AlertDialog`.
|
||||
- Use `Sheet` for a task surface entering from a screen edge and `Drawer` for a touch-oriented mobile panel.
|
||||
- Use `Popover` for interactive content attached to a trigger.
|
||||
- Use `HoverCard` for a preview revealed by hover or focus.
|
||||
- Use `Tooltip` for short, non-interactive descriptions.
|
||||
|
||||
Compare basic usage with the [shadcn Popover documentation](https://ui.shadcn.com/docs/components/base/popover), but keep the Base UI `render` composition syntax.
|
||||
@@ -1,39 +1,91 @@
|
||||
---
|
||||
title: Project setup
|
||||
description: Import the global stylesheet and begin with explicit component subpaths.
|
||||
description: Configure the workspace dependency, global styles, Tailwind scanning, and the shadcn CLI.
|
||||
order: 10
|
||||
toc:
|
||||
- id: import-styles
|
||||
title: Import styles
|
||||
- id: import-a-component
|
||||
title: Import a component
|
||||
- id: entry-points
|
||||
title: Entry points
|
||||
- id: dependency
|
||||
title: Add the dependency
|
||||
- id: global-styles
|
||||
title: Global styles
|
||||
- id: imports
|
||||
title: Explicit imports
|
||||
- id: shadcn-cli
|
||||
title: shadcn CLI
|
||||
- id: browser-components
|
||||
title: Browser components
|
||||
- id: verify
|
||||
title: Verify the setup
|
||||
---
|
||||
|
||||
## Import styles {#import-styles}
|
||||
## Add the dependency {#dependency}
|
||||
|
||||
Import the global stylesheet once at the application entry:
|
||||
Applications reference the UI package through the workspace protocol:
|
||||
|
||||
```ts
|
||||
import "@workspace/ui/globals.css"
|
||||
```
|
||||
|
||||
It provides the shared Tailwind layers, design variables, font setup, and utilities expected by the components.
|
||||
|
||||
## Import a component {#import-a-component}
|
||||
|
||||
```tsx
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
|
||||
export function SaveButton() {
|
||||
return <Button>Save</Button>
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@workspace/ui": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Entry points {#entry-points}
|
||||
The applications in this repository are already configured this way. The package is currently private to the workspace and does not need to be installed from npm.
|
||||
|
||||
- `@workspace/ui/components/*` exposes one component module.
|
||||
- `@workspace/ui/hooks/*` exposes reusable interactions and media-query state.
|
||||
- `@workspace/ui/lib/utils` exposes shared class-name composition.
|
||||
- `@workspace/ui/locales/*` exposes language-specific catalogs.
|
||||
## Global styles {#global-styles}
|
||||
|
||||
Import the stylesheet once from the application's global CSS:
|
||||
|
||||
```css
|
||||
@import "@workspace/ui/globals.css";
|
||||
```
|
||||
|
||||
This entry loads Tailwind CSS, shadcn's Tailwind extension, animation utilities, the Inter Variable font, semantic colors, radius variables, and base styles. Themes switch through `.ui:light` and `.ui:dark` on the root element; component code should not copy these variables.
|
||||
|
||||
The `@source` directives in `globals.css` cover this repository's `apps` and component directories. Update those paths if the package moves into another directory layout, or production builds may omit utilities used by components.
|
||||
|
||||
## Explicit imports {#imports}
|
||||
|
||||
There is no aggregated barrel. Import concrete subpaths so dependencies remain visible and client bundles do not grow accidentally:
|
||||
|
||||
```tsx
|
||||
import { Button } from "@workspace/ui/components/button"
|
||||
import { SaveIcon } from "@workspace/ui/components/icon"
|
||||
import { useIsMobile } from "@workspace/ui/hooks/use-breakpoint"
|
||||
import { cn } from "@workspace/ui/lib/utils"
|
||||
```
|
||||
|
||||
Public entry points include:
|
||||
|
||||
- `@workspace/ui/components/*` for components and their local types.
|
||||
- `@workspace/ui/hooks/*` for breakpoint and ripple interactions.
|
||||
- `@workspace/ui/locales` for supported locales and message ID types.
|
||||
- `@workspace/ui/locales/en-US` and `@workspace/ui/locales/zh-Hans` for messages and calendar locales.
|
||||
- `@workspace/ui/globals.css` for global styles.
|
||||
|
||||
## shadcn CLI {#shadcn-cli}
|
||||
|
||||
Run the command from the repository root:
|
||||
|
||||
```sh
|
||||
bunx shadcn@latest add button -c apps/web
|
||||
```
|
||||
|
||||
The application and UI package `components.json` files agree on `base-vega`, neutral, CSS variables, the Lucide vocabulary, and RTL output. Their aliases place generated files under `packages/ui/src/components`, following shadcn's official [monorepo workflow](https://ui.shadcn.com/docs/monorepo).
|
||||
|
||||
Always inspect the diff after generation. The generator does not know about this workspace's Icones layer, localization, ripple behavior, or Popover extensions and can overwrite them when updating an existing component. See the official [`components.json` reference](https://ui.shadcn.com/docs/components-json) for the meaning of each setting.
|
||||
|
||||
## Browser components {#browser-components}
|
||||
|
||||
Most interactive components depend on events, portals, or browser APIs. Consumers using React Server Components should import them from their own client boundary. This workspace has `rsc: false`; component files do not receive a framework-specific directive globally, although a few components include `"use client"` when they require it.
|
||||
|
||||
## Verify the setup {#verify}
|
||||
|
||||
After changing a component or configuration, run:
|
||||
|
||||
```sh
|
||||
bun run --cwd packages/ui typecheck
|
||||
bun run lint
|
||||
bun run format:check
|
||||
```
|
||||
|
||||
When a change affects consumption, also build the application or docs so Tailwind scanning is exercised, then verify light, dark, and RTL modes.
|
||||
|
||||
@@ -1,24 +1,53 @@
|
||||
---
|
||||
title: Overview
|
||||
description: Learn where the UI package fits and how its explicit entry points support composition.
|
||||
description: Understand the role, upstream baseline, and workspace-specific extensions of @workspace/ui.
|
||||
order: 1
|
||||
toc:
|
||||
- id: package-role
|
||||
title: Package role
|
||||
- id: tutorial-map
|
||||
title: Tutorial map
|
||||
- id: ui-or-blocks
|
||||
title: UI or Blocks
|
||||
- id: source-model
|
||||
title: Source model
|
||||
- id: local-adaptations
|
||||
title: Local adaptations
|
||||
- id: learning-path
|
||||
title: Learning path
|
||||
- id: package-boundaries
|
||||
title: Package boundaries
|
||||
---
|
||||
|
||||
## Package role {#package-role}
|
||||
|
||||
`@workspace/ui` contains reusable React primitives, composite controls, hooks, icons, styles, and locale catalogs. It is the visual foundation shared by applications and higher-level feature packages.
|
||||
`@workspace/ui` is the visual foundation of the workspace. It provides editable React component source, Tailwind CSS theme variables, responsive and interaction hooks, a shared icon entry point, and the localized messages used by the components themselves.
|
||||
|
||||
## Tutorial map {#tutorial-map}
|
||||
The package owns generic interface behavior only. Routing, data access, persistence, and product workflows remain in the application or a domain package.
|
||||
|
||||
This tutorial installs the styles, introduces component subpaths, builds a small form through composition, adds responsive behavior, and connects locale catalogs.
|
||||
## Source model {#source-model}
|
||||
|
||||
## UI or Blocks {#ui-or-blocks}
|
||||
The components use the [shadcn/ui Base UI implementation](https://ui.shadcn.com/docs/components/base) and its [GitHub registry source](https://github.com/shadcn-ui/ui/tree/main/apps/v4/registry/bases/base/ui) as their baseline. Like shadcn/ui, they are editable source rather than an opaque component dependency: the implementation lives in `packages/ui/src` and can be reviewed, composed, and evolved locally.
|
||||
|
||||
Use UI components when the application owns the workflow. Use `@workspace/blocks` for complete application-shell features such as navigation and notifications, and focused packages such as `@workspace/media` or `@workspace/search` for domain workflows.
|
||||
This workspace selects the `base-vega` style, CSS variables, Tailwind CSS v4, Base UI primitives, and RTL output. `components.json` controls generation; the checked-in workspace source is the runtime authority.
|
||||
|
||||
## Local adaptations {#local-adaptations}
|
||||
|
||||
The workspace intentionally preserves these changes on top of the upstream baseline:
|
||||
|
||||
- A shared `components/icon` entry point backed by `@icones/react`, instead of direct `lucide-react` imports.
|
||||
- Pointer ripple feedback built into `Button`.
|
||||
- An `I18nProvider` that localizes Breadcrumb, Calendar, Carousel, Pagination, Sidebar, Toast, and related controls through the application's translator.
|
||||
- UI catalogs and calendar locales for `en-US` and `zh-Hans` only.
|
||||
- Mobile, tablet, and desktop breakpoint hooks with root-class synchronization.
|
||||
- Custom anchors, positioning methods, a continuous tail, and an independent scroll viewport on `PopoverContent`.
|
||||
- `AnchoredSurface` plus message, bubble, attachment, and message-scroller composition primitives.
|
||||
|
||||
Do not overwrite these differences blindly when updating from shadcn/ui. The “Workspace adaptations” chapter contains the full checklist.
|
||||
|
||||
## Learning path {#learning-path}
|
||||
|
||||
Start with “Project setup”, then use the “Component catalog” to find the right entry point. Overlays and feedback have focused guides. Continue to the advanced chapters when the application needs SSR-safe responsive behavior or localization. The examples assemble the primitives into application-ready structures.
|
||||
|
||||
## Package boundaries {#package-boundaries}
|
||||
|
||||
- Use `@workspace/ui` for buttons, forms, menus, overlays, lists, and generic feedback.
|
||||
- Use `@workspace/blocks` for complete application areas such as shells, navigation, chat, and notifications.
|
||||
- Use domain packages such as `@workspace/media`, `@workspace/search`, and `@workspace/lexical` for domain interaction.
|
||||
- Use `@workspace/appearance` with `@workspace/preferences` to compose theme preferences and persistence.
|
||||
|
||||
Reference in New Issue
Block a user