diff --git a/.changeset/fake-tabs-add-panels-rename.md b/.changeset/fake-tabs-add-panels-rename.md new file mode 100644 index 00000000000..cb0ce2e0f7d --- /dev/null +++ b/.changeset/fake-tabs-add-panels-rename.md @@ -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. diff --git a/.claude/skills/evo-app-migrate-react/SKILL.md b/.claude/skills/evo-app-migrate-react/SKILL.md index 11a1c755b15..6852f8b8c07 100644 --- a/.claude/skills/evo-app-migrate-react/SKILL.md +++ b/.claude/skills/evo-app-migrate-react/SKILL.md @@ -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-file-input`: [evo-file-input.md](components/evo-file-input.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) diff --git a/.claude/skills/evo-app-migrate-react/components/evo-fake-tabs.md b/.claude/skills/evo-app-migrate-react/components/evo-fake-tabs.md new file mode 100644 index 00000000000..70fa502c8a1 --- /dev/null +++ b/.claude/skills/evo-app-migrate-react/components/evo-fake-tabs.md @@ -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"; + + + US + Germany +

Germany Content

+

Some German content…

+
; +``` + +**After:** + +```tsx +import { + EvoFakeTabs, + EvoFakeTabList, + EvoFakeTab, + EvoFakeTabPanel, +} from "@evo-web/react/fake-tabs"; + + + + + US + + + Germany + + + +

Germany Content

+

Some German content…

+
+
; +``` + +**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. `` 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 : ; +} + + + US +; +``` diff --git a/.claude/skills/evo-app-migrate-react/components/evo-tabs.md b/.claude/skills/evo-app-migrate-react/components/evo-tabs.md index 41b35ae96b4..b979d33d1c5 100644 --- a/.claude/skills/evo-app-migrate-react/components/evo-tabs.md +++ b/.claude/skills/evo-app-migrate-react/components/evo-tabs.md @@ -23,20 +23,20 @@ Keyboard activation remains `"auto"` by default, matching both legacy React and Overview - + Overview content - + ``` **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. | diff --git a/packages/evo-marko/src/tags/evo-fake-tabs/examples/disabled.marko b/packages/evo-marko/src/tags/evo-fake-tabs/examples/disabled.marko new file mode 100644 index 00000000000..2cb121e418c --- /dev/null +++ b/packages/evo-marko/src/tags/evo-fake-tabs/examples/disabled.marko @@ -0,0 +1,14 @@ + + <@tab href="https://www.ebay.com/"> + Tab 1 + + <@tab> + Tab 2 + + <@tab href="https://www.ebay.com/"> + Tab 3 + +

+ 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. +

+
diff --git a/packages/evo-marko/src/tags/evo-fake-tabs/fake-tabs.stories.ts b/packages/evo-marko/src/tags/evo-fake-tabs/fake-tabs.stories.ts index 119e657b920..8b6c9f6f487 100644 --- a/packages/evo-marko/src/tags/evo-fake-tabs/fake-tabs.stories.ts +++ b/packages/evo-marko/src/tags/evo-fake-tabs/fake-tabs.stories.ts @@ -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", @@ -56,3 +58,8 @@ export const NoPanel = buildExtensionTemplate( NoPanelTemplate, NoPanelTemplateCode, ); + +export const Disabled = buildExtensionTemplate( + DisabledTemplate, + DisabledTemplateCode, +); diff --git a/packages/evo-marko/src/tags/evo-fake-tabs/index.marko b/packages/evo-marko/src/tags/evo-fake-tabs/index.marko index 295dc066c83..497233bc0b6 100644 --- a/packages/evo-marko/src/tags/evo-fake-tabs/index.marko +++ b/packages/evo-marko/src/tags/evo-fake-tabs/index.marko @@ -27,7 +27,9 @@ export interface Input extends Marko.HTML.Div {
  • + href=href + aria-current=href !== undefined && selected === i && tabAriaCurrent + aria-disabled=href === undefined && "true"/>
  • diff --git a/packages/evo-marko/src/tags/evo-fake-tabs/test/__snapshots__/test.server.ts.snap b/packages/evo-marko/src/tags/evo-fake-tabs/test/__snapshots__/test.server.ts.snap index 0e46371a9d6..ab240c9f5de 100644 --- a/packages/evo-marko/src/tags/evo-fake-tabs/test/__snapshots__/test.server.ts.snap +++ b/packages/evo-marko/src/tags/evo-fake-tabs/test/__snapshots__/test.server.ts.snap @@ -10,7 +10,9 @@ exports[`fake-tabs > renders default 1`] = `
  • - + Tab 1
  • @@ -19,6 +21,7 @@ exports[`fake-tabs > renders default 1`] = ` > Tab 2 @@ -26,7 +29,64 @@ exports[`fake-tabs > renders default 1`] = `
  • - + + Tab 3 + +
  • + +
    +
    +
    +
    +

    + 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. +

    +
    +
    +
    +
    +" +`; + +exports[`fake-tabs > renders disabled tab without href or aria-current 1`] = ` +"
    +