Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/sunny-titles-resolve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tailor-platform/app-shell": patch
---

Fix `SidebarItem` title overrides accepting localized strings. Pass a value from `defineI18nLabels` directly to `title` and it resolves with the active AppShell locale.
14 changes: 7 additions & 7 deletions docs-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -1147,10 +1147,10 @@
"claims": [],
"symbols": ["SidebarItem", "SidebarItemProps"],
"hashes": {
"typeSurface": "7be21739c231ede3",
"outline": "1cc674b956d09fab",
"typeSurface": "37530cafa374d799",
"outline": "d1bbcfac50071047",
"snapshot": null,
"outputMd": "d55d38352040ef2c",
"outputMd": "9236c03e53aa79f2",
"examples": null
}
},
Expand Down Expand Up @@ -1182,9 +1182,9 @@
"symbols": [],
"hashes": {
"typeSurface": null,
"outline": "74f353988f15c82b",
"outline": "91f0fa065f3a3192",
"snapshot": null,
"outputMd": "96f1cf893c231861",
"outputMd": "dc6b3afc049b4148",
"examples": null
}
},
Expand Down Expand Up @@ -1587,7 +1587,7 @@
"packages/core/skills/app-shell-patterns/references/concepts/graphql.md": "ba5107604e4f42e8",
"packages/core/skills/app-shell-patterns/references/concepts/modules-and-resources.md": "bf15b5460f22d165",
"packages/core/skills/app-shell-patterns/references/concepts/routing-navigation.md": "2f3ff3f095c2b1cc",
"packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "96f1cf893c231861",
"packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "dc6b3afc049b4148",
"packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "0133fdae405f467c",
"packages/core/skills/app-shell-patterns/references/components/action-panel.md": "71794d4b561e4d4b",
"packages/core/skills/app-shell-patterns/references/components/activity-card.md": "4f386e49380fb0b0",
Expand Down Expand Up @@ -1622,7 +1622,7 @@
"packages/core/skills/app-shell-patterns/references/components/select.md": "ae287e2bb5abc1c6",
"packages/core/skills/app-shell-patterns/references/components/sheet.md": "1f81ebd562c50371",
"packages/core/skills/app-shell-patterns/references/components/sidebar-group.md": "7288fdf8bb5c19b9",
"packages/core/skills/app-shell-patterns/references/components/sidebar-item.md": "d55d38352040ef2c",
"packages/core/skills/app-shell-patterns/references/components/sidebar-item.md": "9236c03e53aa79f2",
"packages/core/skills/app-shell-patterns/references/components/sidebar-layout.md": "e50b2d649289d772",
"packages/core/skills/app-shell-patterns/references/components/spinner.md": "e05d94ba9c3d17da",
"packages/core/skills/app-shell-patterns/references/components/table.md": "f85ec64ceb7f4acf",
Expand Down
12 changes: 10 additions & 2 deletions docs-src/components/sidebar-item.docs.outline.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ sources:

# SidebarItem

`SidebarItem` represents an individual navigation link in the sidebar. It automatically resolves titles and icons from resource definitions and provides active state highlighting.
`SidebarItem` represents an individual navigation link in the sidebar. It automatically resolves titles and icons from resource definitions, supports localized title overrides, and provides active state highlighting.

## Import

Expand All @@ -36,7 +36,7 @@ When you only provide `to`, the title and icon are automatically resolved from t
| Prop | Type | Default | Description |
| ------------- | ---------------------- | ------------- | ---------------------------------------------- |
| `to` | `string` | **Required** | Target URL (internal or external) |
| `title` | `string` | Auto-resolved | Override the display title |
| `title` | `LocalizedString` | Auto-resolved | Override the display title (i18n supported) |
| `icon` | `React.ReactNode` | Auto-resolved | Override the icon |
| `external` | `boolean` | `false` | Opens link in new tab with external icon |
| `activeMatch` | `"exact" \| "prefix"` | `"prefix"` | How to match the current path for active state |
Expand Down Expand Up @@ -106,6 +106,14 @@ const modules = [
/>
```

### Localized Override

Pass a `LocalizedString`, such as a value from `defineI18nLabels`, to resolve the override using the current AppShell locale:

```tsx
<SidebarItem to="/dashboard" title={labels.t("dashboard")} />
```

### Override Icon

```tsx
Expand Down
14 changes: 7 additions & 7 deletions docs-src/concepts/sidebar-navigation.docs.outline.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,13 @@ A navigation item that automatically resolves title and icon from resource defin

#### Props

| Prop | Type | Required | Description |
| ---------- | ----------- | -------- | --------------------------------------------------------------------- |
| `to` | `string` | Yes | Target URL. External URLs (http://...) are rendered as external links |
| `title` | `string` | No | Override title. When omitted, auto-resolved from resource meta |
| `icon` | `ReactNode` | No | Override icon. When omitted, auto-resolved from resource meta |
| `external` | `boolean` | No | Opens link in new tab with external link icon |
| `render` | `function` | No | Custom render function for full UI control |
| Prop | Type | Required | Description |
| ---------- | ----------------- | -------- | ------------------------------------------------------------------------------- |
| `to` | `string` | Yes | Target URL. External URLs (http://...) are rendered as external links |
| `title` | `LocalizedString` | No | Override title (i18n supported). When omitted, auto-resolved from resource meta |
| `icon` | `ReactNode` | No | Override icon. When omitted, auto-resolved from resource meta |
| `external` | `boolean` | No | Opens link in new tab with external link icon |
| `render` | `function` | No | Custom render function for full UI control |

#### Examples

Expand Down
12 changes: 10 additions & 2 deletions docs/components/sidebar-item.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: Individual navigation item for customizing sidebar with icons, acti

# SidebarItem

`SidebarItem` represents an individual navigation link in the sidebar. It automatically resolves titles and icons from resource definitions and provides active state highlighting.
`SidebarItem` represents an individual navigation link in the sidebar. It automatically resolves titles and icons from resource definitions, supports localized title overrides, and provides active state highlighting.

## Import

Expand All @@ -34,7 +34,7 @@ When you only provide `to`, the title and icon are automatically resolved from t
| Prop | Type | Default | Description |
| ------------- | ---------------------- | ------------- | ---------------------------------------------- |
| `to` | `string` | **Required** | Target URL (internal or external) |
| `title` | `string` | Auto-resolved | Override the display title |
| `title` | `LocalizedString` | Auto-resolved | Override the display title (i18n supported) |
| `icon` | `React.ReactNode` | Auto-resolved | Override the icon |
| `external` | `boolean` | `false` | Opens link in new tab with external icon |
| `activeMatch` | `"exact" \| "prefix"` | `"prefix"` | How to match the current path for active state |
Expand Down Expand Up @@ -104,6 +104,14 @@ const modules = [
/>
```

### Localized Override

Pass a `LocalizedString`, such as a value from `defineI18nLabels`, to resolve the override using the current AppShell locale:

```tsx
<SidebarItem to="/dashboard" title={labels.t("dashboard")} />
```

### Override Icon

```tsx
Expand Down
14 changes: 7 additions & 7 deletions docs/concepts/sidebar-navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,13 @@ A navigation item that automatically resolves title and icon from resource defin

#### Props

| Prop | Type | Required | Description |
| ---------- | ----------- | -------- | --------------------------------------------------------------------- |
| `to` | `string` | Yes | Target URL. External URLs (http://...) are rendered as external links |
| `title` | `string` | No | Override title. When omitted, auto-resolved from resource meta |
| `icon` | `ReactNode` | No | Override icon. When omitted, auto-resolved from resource meta |
| `external` | `boolean` | No | Opens link in new tab with external link icon |
| `render` | `function` | No | Custom render function for full UI control |
| Prop | Type | Required | Description |
| ---------- | ----------------- | -------- | ------------------------------------------------------------------------------- |
| `to` | `string` | Yes | Target URL. External URLs (http://...) are rendered as external links |
| `title` | `LocalizedString` | No | Override title (i18n supported). When omitted, auto-resolved from resource meta |
| `icon` | `ReactNode` | No | Override icon. When omitted, auto-resolved from resource meta |
| `external` | `boolean` | No | Opens link in new tab with external link icon |
| `render` | `function` | No | Custom render function for full UI control |

#### Examples

Expand Down
14 changes: 11 additions & 3 deletions packages/core/src/components/sidebar/sidebar-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { useLocation, Link } from "react-router";
import { ExternalLink } from "lucide-react";
import { SidebarMenuItem, SidebarMenuButton } from "./primitives";
import { usePageMeta } from "@/hooks/use-page-meta";
import { useAppShellConfig } from "@/contexts/appshell-context";
import { buildLocaleResolver, type LocalizedString } from "@/lib/i18n";

export type SidebarItemRenderProps = {
/** Title auto-resolved from resource meta */
Expand All @@ -23,10 +25,10 @@ export type SidebarItemProps = {
to: string;

/**
* Override title.
* Override title (i18n supported).
* When omitted, title is auto-resolved from resource meta.
*/
title?: string;
title?: LocalizedString;

/**
* Override icon.
Expand Down Expand Up @@ -96,13 +98,19 @@ export const SidebarItem = (props: SidebarItemProps) => {
icon: iconOverride,
} = props;
const { pathname: currentPath } = useLocation();
const { configurations } = useAppShellConfig();
const pageMeta = usePageMeta(to);
const resolve = buildLocaleResolver(configurations.locale);

const isActive =
activeMatch === "exact"
? currentPath === to
: currentPath === to || currentPath.startsWith(`${to}/`);
const title = titleOverride ?? pageMeta?.title ?? extractTitleFromUrl(to);
const fallbackTitle = pageMeta?.title ?? extractTitleFromUrl(to);
const title =
typeof titleOverride === "function"
? resolve(titleOverride, fallbackTitle)
: (titleOverride ?? fallbackTitle);
const icon = iconOverride ?? pageMeta?.icon;

const renderProps: SidebarItemRenderProps = {
Expand Down
20 changes: 18 additions & 2 deletions packages/core/src/components/sidebar/sidebar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,15 @@ const testConfig: RootConfiguration = {
/**
* Wrapper to render sidebar components with all required providers.
*/
const renderWithProviders = (ui: React.ReactNode, initialPath = "/dashboard/overview") => {
const renderWithProviders = (
ui: React.ReactNode,
initialPath = "/dashboard/overview",
locale = "en",
) => {
stubDesktopViewport();
return render(
<MemoryRouter initialEntries={[initialPath]}>
<AppShellConfigContext.Provider value={{ configurations: testConfig }}>
<AppShellConfigContext.Provider value={{ configurations: { ...testConfig, locale } }}>
<SidebarProvider>
<SidebarMenu data-testid="sidebar-menu">{ui}</SidebarMenu>
</SidebarProvider>
Expand All @@ -118,6 +122,18 @@ describe("SidebarItem", () => {
expect(menu.getByRole("link", { name: /dashboard/i })).toBeDefined();
});

it("resolves localized title overrides", () => {
renderWithProviders(<SidebarItem to="/dashboard" title={localizedTitle} />, undefined, "ja");

expect(getSidebarMenu().getByRole("link", { name: "製品" })).toBeDefined();
});

it("preserves empty string title overrides", () => {
renderWithProviders(<SidebarItem to="/dashboard" title="" />);

expect(getSidebarMenu().getByRole("link").textContent).toBe("");
});

it("renders with custom render function", () => {
renderWithProviders(
<SidebarItem
Expand Down
Loading