Skip to content
Open
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
6 changes: 6 additions & 0 deletions .changeset/fake-tabs-add-panels-rename.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@evo-web/marko": patch
"@evo-web/react": patch
---

Add EvoFakeTabs, rename EvoTabPanelList to EvoTabPanels, and align React and Marko fake-tab link and disabled behavior.
1 change: 1 addition & 0 deletions .claude/skills/evo-app-migrate-react/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ When migrating a listed component, read the linked file completely and apply the
- `ebay-cta-button`: [evo-cta-button.md](components/evo-cta-button.md)
- `ebay-details`: [evo-details.md](components/evo-details.md)
- `ebay-eek`: [evo-eek.md](components/evo-eek.md)
- `ebay-fake-tabs`: [evo-fake-tabs.md](components/evo-fake-tabs.md)
- `ebay-filter-chip`: [evo-filter-chip.md](components/evo-filter-chip.md)
- `ebay-icon-button`: [evo-icon-button.md](components/evo-icon-button.md)
- `ebay-lightbox-dialog`: [evo-dialog.md](components/evo-dialog.md)
Expand Down
70 changes: 70 additions & 0 deletions .claude/skills/evo-app-migrate-react/components/evo-fake-tabs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# ebay-fake-tabs → evo-fake-tabs

This component has a **new id-based compound API**. The old positional `selectedIndex` and child-scanning (`filterByType`) approach is replaced by named sub-components and a required `selected` ID.

Fake tabs are navigation links with `aria-current`, not an interactive ARIA tab widget. Each tab is an anchor pointing to a distinct URL. The selected tab reflects the current page.

**Before:**

```tsx
import { EbayFakeTabs, EbayFakeTab } from "@ebay/ui-core-react/ebay-fake-tabs";

<EbayFakeTabs selectedIndex={1} tabMatchesCurrentUrl={false}>
<EbayFakeTab href="/us">US</EbayFakeTab>
<EbayFakeTab href="/de">Germany</EbayFakeTab>
<h3>Germany Content</h3>
<p>Some German content…</p>
</EbayFakeTabs>;
```

**After:**

```tsx
import {
EvoFakeTabs,
EvoFakeTabList,
EvoFakeTab,
EvoFakeTabPanel,
} from "@evo-web/react/fake-tabs";

<EvoFakeTabs selected="de" tabMatchesCurrentUrl={false}>
<EvoFakeTabList>
<EvoFakeTab id="us" href="/us">
US
</EvoFakeTab>
<EvoFakeTab id="de" href="/de">
Germany
</EvoFakeTab>
</EvoFakeTabList>
<EvoFakeTabPanel>
<h3>Germany Content</h3>
<p>Some German content…</p>
</EvoFakeTabPanel>
</EvoFakeTabs>;
```

**Prop changes:**

| ebayui-core-react | evo-react | Notes |
| --------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `selectedIndex` (number) | `selected` (string \| number \| null) | Assign each tab an `id`; pass that id as `selected`. Pass `null` when no tab is current. |
| Flat `EbayFakeTab` children | `EvoFakeTabList` + `EvoFakeTab` | Each `EvoFakeTab` requires a unique `id` prop. |
| Panel content as siblings | `EvoFakeTabPanel` | Wrap page content in `EvoFakeTabPanel`. |
| `size="regular"\|"large"` | `size="regular"\|"large"` | Same values; defaults to `"regular"`. |
| `tabMatchesCurrentUrl` | `tabMatchesCurrentUrl` | Same behavior; `true` → `aria-current="page"`, `false` → `aria-current="true"`. |
| `href` on `EbayFakeTab` | Optional `href` on `EvoFakeTab` | Move `href` to the tab element. Omit it to render the tab disabled. |
| No `as` support | `as` on `EvoFakeTab` | Pass a custom anchor component (e.g. `<Link>` from React Router) via `as`. It receives `href`. |

### React Router / Next.js link adapter

```tsx
import { Link } from "react-router";

function RouterTab({ href, ...props }: React.ComponentProps<"a">) {
return href === undefined ? null : <Link to={href} {...props} />;
}

<EvoFakeTab id="us" href="/us" as={RouterTab}>
US
</EvoFakeTab>;
```
22 changes: 11 additions & 11 deletions .claude/skills/evo-app-migrate-react/components/evo-tabs.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,20 @@ Keyboard activation remains `"auto"` by default, matching both legacy React and
<EvoTabList>
<EvoTab id={0}>Overview</EvoTab>
</EvoTabList>
<EvoTabPanelList>
<EvoTabPanels>
<EvoTabPanel id={0}>Overview content</EvoTabPanel>
</EvoTabPanelList>
</EvoTabPanels>
</EvoTabs>
```

**Prop changes:**

| ebayui-core-react | evo-react | Notes |
| ----------------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `selectedIndex` | `selected` / `defaultSelected` | Preserve each positional index as the matching numeric tab and panel id. Use `selected` for controlled state or `defaultSelected` for uncontrolled state. One is required. |
| `onSelect({ selectedIndex })` | `onSelectedChange(selected)` | Receives the selected string or number directly. |
| Flat `EbayTab` children | `EvoTabList` containing `EvoTab` | Every tab requires a unique `id`. |
| Flat `EbayTabPanel` children | `EvoTabPanelList` containing `EvoTabPanel` | Every panel requires the matching tab `id`. |
| `size` | `size` | Same values: `"medium" \| "large"`. |
| `activation` | `activation` | Same values: `"auto" \| "manual"`; defaults to `"auto"`. |
| `disabled` on `EbayTab` | `disabled` on `EvoTab` | No behavior change. |
| ebayui-core-react | evo-react | Notes |
| ----------------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `selectedIndex` | `selected` / `defaultSelected` | Preserve each positional index as the matching numeric tab and panel id. Use `selected` for controlled state or `defaultSelected` for uncontrolled state. One is required. |
| `onSelect({ selectedIndex })` | `onSelectedChange(selected)` | Receives the selected string or number directly. |
| Flat `EbayTab` children | `EvoTabList` containing `EvoTab` | Every tab requires a unique `id`. |
| Flat `EbayTabPanel` children | `EvoTabPanels` containing `EvoTabPanel` | Every panel requires the matching tab `id`. |
| `size` | `size` | Same values: `"medium" \| "large"`. |
| `activation` | `activation` | Same values: `"auto" \| "manual"`; defaults to `"auto"`. |
| `disabled` on `EbayTab` | `disabled` on `EvoTab` | No behavior change. |
14 changes: 14 additions & 0 deletions packages/evo-marko/src/tags/evo-fake-tabs/examples/disabled.marko
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<evo-fake-tabs selected=1 ...input>
<@tab href="https://www.ebay.com/">
Tab 1
</@tab>
<@tab>
Tab 2
</@tab>
<@tab href="https://www.ebay.com/">
Tab 3
</@tab>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla ornare, quam at lacinia pretium, lacus urna luctus nisi, eget molestie massa tortor id lacus. Aenean ac fringilla lacus. Fusce vel dui ex. Vivamus luctus egestas nulla, non hendrerit purus luctus at. Maecenas vel diam enim. Pellentesque quam neque, porttitor tincidunt vestibulum at, dapibus sit amet tortor.
</p>
</evo-fake-tabs>
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import DefaultTemplate from "./examples/default.marko";
import DefaultTemplateCode from "./examples/default.marko?raw";
import NoPanelTemplate from "./examples/no-panel-content.marko";
import NoPanelTemplateCode from "./examples/no-panel-content.marko?raw";
import DisabledTemplate from "./examples/disabled.marko";
import DisabledTemplateCode from "./examples/disabled.marko?raw";

export default {
title: "navigation & disclosure/evo-fake-tabs",
Expand Down Expand Up @@ -56,3 +58,8 @@ export const NoPanel = buildExtensionTemplate(
NoPanelTemplate,
NoPanelTemplateCode,
);

export const Disabled = buildExtensionTemplate(
DisabledTemplate,
DisabledTemplateCode,
);
4 changes: 3 additions & 1 deletion packages/evo-marko/src/tags/evo-fake-tabs/index.marko
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ export interface Input extends Marko.HTML.Div {
<li class=[tabClass, "fake-tabs__item"]>
<a
...htmlTab
aria-current=selected === i && tabAriaCurrent/>
href=href
aria-current=href !== undefined && selected === i && tabAriaCurrent
aria-disabled=href === undefined && "true"/>
</li>
</for>
</ul>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ exports[`fake-tabs > renders default 1`] = `
<li
class="fake-tabs__item"
>
<a>
<a
href="https://www.ebay.com/"
>
Tab 1
</a>
</li>
Expand All @@ -19,14 +21,72 @@ exports[`fake-tabs > renders default 1`] = `
>
<a
aria-current="page"
href="https://www.ebay.com/"
>
Tab 2
</a>
</li>
<li
class="fake-tabs__item"
>
<a>
<a
href="https://www.ebay.com/"
>
Tab 3
</a>
</li>
</ul>
<div
class="fake-tabs__content"
>
<div
class="fake-tabs__panel"
>
<div
class="fake-tabs__cell"
>
<div>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla ornare, quam at lacinia pretium, lacus urna luctus nisi, eget molestie massa tortor id lacus. Aenean ac fringilla lacus. Fusce vel dui ex. Vivamus luctus egestas nulla, non hendrerit purus luctus at. Maecenas vel diam enim. Pellentesque quam neque, porttitor tincidunt vestibulum at, dapibus sit amet tortor.
</p>
</div>
</div>
</div>
</div>
</div>"
`;

exports[`fake-tabs > renders disabled tab without href or aria-current 1`] = `
"<div
class="fake-tabs"
>
<ul
class="fake-tabs__items"
>
<li
class="fake-tabs__item"
>
<a
href="https://www.ebay.com/"
>
Tab 1
</a>
</li>
<li
class="fake-tabs__item"
>
<a
aria-disabled="true"
>
Tab 2
</a>
</li>
<li
class="fake-tabs__item"
>
<a
href="https://www.ebay.com/"
>
Tab 3
</a>
</li>
Expand Down Expand Up @@ -63,21 +123,26 @@ exports[`fake-tabs > renders no panel 1`] = `
>
<a
aria-current="page"
href="https://www.ebay.com/"
>
Tab 1
</a>
</li>
<li
class="fake-tabs__item"
>
<a>
<a
href="https://www.ebay.com/"
>
Tab 2
</a>
</li>
<li
class="fake-tabs__item"
>
<a>
<a
href="https://www.ebay.com/"
>
Tab 3
</a>
</li>
Expand Down Expand Up @@ -108,21 +173,27 @@ exports[`fake-tabs > renders with no selected index 1`] = `
<li
class="fake-tabs__item"
>
<a>
<a
href="https://www.ebay.com/"
>
Tab 1
</a>
</li>
<li
class="fake-tabs__item"
>
<a>
<a
href="https://www.ebay.com/"
>
Tab 2
</a>
</li>
<li
class="fake-tabs__item"
>
<a>
<a
href="https://www.ebay.com/"
>
Tab 3
</a>
</li>
Expand Down Expand Up @@ -157,14 +228,18 @@ exports[`fake-tabs > renders with other selected index 1`] = `
<li
class="fake-tabs__item"
>
<a>
<a
href="https://www.ebay.com/"
>
Tab 1
</a>
</li>
<li
class="fake-tabs__item"
>
<a>
<a
href="https://www.ebay.com/"
>
Tab 2
</a>
</li>
Expand All @@ -173,6 +248,7 @@ exports[`fake-tabs > renders with other selected index 1`] = `
>
<a
aria-current="page"
href="https://www.ebay.com/"
>
Tab 3
</a>
Expand Down Expand Up @@ -208,7 +284,9 @@ exports[`fake-tabs > renders with tabMatchesCurrentUrl=false 1`] = `
<li
class="fake-tabs__item"
>
<a>
<a
href="https://www.ebay.com/"
>
Tab 1
</a>
</li>
Expand All @@ -217,14 +295,17 @@ exports[`fake-tabs > renders with tabMatchesCurrentUrl=false 1`] = `
>
<a
aria-current="true"
href="https://www.ebay.com/"
>
Tab 2
</a>
</li>
<li
class="fake-tabs__item"
>
<a>
<a
href="https://www.ebay.com/"
>
Tab 3
</a>
</li>
Expand Down
13 changes: 8 additions & 5 deletions packages/evo-marko/src/tags/evo-fake-tabs/test/test.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, it } from "vitest";
import { composeStories } from "@storybook/marko";
import { snapshotHTML } from "../../../common/test-utils/snapshots";
import * as stories from "../fake-tabs.stories"; // import all stories from the stories file
const { Default, NoPanel } = composeStories(stories);
const { Default, Disabled, NoPanel } = composeStories(stories);

describe("fake-tabs", () => {
it("renders default", async () => {
Expand All @@ -13,15 +13,18 @@ describe("fake-tabs", () => {
await snapshotHTML(NoPanel);
});

it("renders disabled tab without href or aria-current", async () => {
await snapshotHTML(Disabled);
});

it("renders with tabMatchesCurrentUrl=false", async () => {
await snapshotHTML(Default, {tabMatchesCurrentUrl: false});
await snapshotHTML(Default, { tabMatchesCurrentUrl: false });
});

it("renders with other selected index", async () => {
await snapshotHTML(Default, {selected: 2});
await snapshotHTML(Default, { selected: 2 });
});
it("renders with no selected index", async () => {
await snapshotHTML(Default, {selected: -1});
await snapshotHTML(Default, { selected: -1 });
});

});
Loading