Skip to content

SkipNavigation improvements #4739

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 9, 2025
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
2 changes: 1 addition & 1 deletion .github/contribution/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ yarn
And start the site.

```shell
yarn dev:docs
yarn docs dev
```

Your site is now running at `http://localhost:8000`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ import SkipRepeatedContentSnippet from "snippets/skip-repeated-content.mdx";
## Behavior

<SkipRepeatedContentSnippet />

### Navigation Integration

Set `isInNav` to `true` when placing inside navigation components for appropriate styling.
19 changes: 10 additions & 9 deletions packages/orbit-components/src/SkipNavigation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ After adding an import to your project you can use it simply like:

The table below contains all types of props available in the SkipNavigation component.

| Name | Type | Default | Description |
| :---------------- | :---------------------- | :---------------- | -------------------------------------------- |
| feedbackUrl | `string` | | Url to a feedback form. |
| feedbackLabel | `React.Node` | `Send feedback` | Text for a feedback form. |
| **actions** | [`Actions[]`](#actions) | | An array specifying common actions on a page |
| dataTest | `string` | | Optional prop for testing purposes |
| id | `string` | | Optional id attribute |
| firstSectionLabel | `React.Node` | `Jump to section` | Label for a first section link. |
| firstActionLabel | `React.Node` | `Jump to action` | Label for a first action link. |
| Name | Type | Default | Description |
| :---------------- | :---------------------- | :---------------- | ------------------------------------------------------------------------------------- |
| feedbackUrl | `string` | | Url to a feedback form. |
| feedbackLabel | `React.Node` | `Send feedback` | Text for a feedback form. |
| **actions** | [`Actions[]`](#actions) | | An array specifying common actions on a page |
| dataTest | `string` | | Optional prop for testing purposes |
| id | `string` | | Optional id attribute |
| firstSectionLabel | `React.Node` | `Jump to section` | Label for a first section link. |
| firstActionLabel | `React.Node` | `Jump to action` | Label for a first action link. |
| isInNav | `boolean` | | Use when placed inside a navigation component. Adjusts styling and width constraints. |

## actions

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ import * as React from "react";

import SkipNavigation from ".";

export default function SkipNavigationStory() {
interface Props {
isInNav?: boolean;
}

export default function SkipNavigationStory({ isInNav }: Props) {
return (
<div className="p-400">
<div className="border border-solid">
<div className={`border border-solid ${isInNav ? "bg-white-normal" : ""}`}>
<SkipNavigation
dataTest="SkipNavigation"
isInNav={isInNav}
firstSectionLabel={isInNav ? "Section" : "Jump to section"}
firstActionLabel={isInNav ? "Action" : "Common actions"}
actions={[
{
name: "Go to terms and conditions",
Expand Down
24 changes: 24 additions & 0 deletions packages/orbit-components/src/SkipNavigation/SkipNavigation.ct.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,28 @@ test.describe("visual SkipNavigation", () => {

await expect(component).toHaveScreenshot();
});

test("in-nav", async ({ mount }) => {
const component = await mount(<SkipNavigationStory isInNav />);

await expect(component).toHaveScreenshot();
});

test("in-nav focus", async ({ mount }) => {
const component = await mount(<SkipNavigationStory isInNav />);
await component.evaluate(forceShow);

await expect(component).toHaveScreenshot();
});

test("in-nav rtl", async ({ mount }) => {
const component = await mount(
<RenderInRtl>
<SkipNavigationStory isInNav />
</RenderInRtl>,
);
await component.evaluate(forceShow);

await expect(component).toHaveScreenshot();
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
149 changes: 122 additions & 27 deletions packages/orbit-components/src/SkipNavigation/SkipNavigation.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,19 @@ import Text from "../Text";
import TextLink from "../TextLink";
import Card, { CardSection } from "../Card";
import Stack from "../Stack";
import NavigationBar from "../NavigationBar";
import ButtonLink from "../ButtonLink";
import AccountCircle from "../icons/AccountCircle";

import SkipNavigation from ".";

// Define a custom story type that includes both component props
type CombinedProps = React.ComponentProps<typeof SkipNavigation> &
Pick<React.ComponentProps<typeof NavigationBar>, "transparentBgAtTop" | "hideOnScroll">;

type Story = StoryObj<typeof SkipNavigation>;
type CombinedStory = StoryObj<CombinedProps>;

const Content = (
<Stack>
<Card
Expand Down Expand Up @@ -59,23 +69,130 @@ const Content = (
const meta: Meta<typeof SkipNavigation> = {
title: "SkipNavigation",
component: SkipNavigation,

args: {
actions: [
{
link: "https://www.kiwi.com/cz/pages/content/terms",
name: "Go to terms and conditions",
},
{
name: "Add baggage",
onClick: action("Add baggage"),
},
{
name: "Request refund",
onClick: action("Request refund"),
},
],
feedbackUrl: "#",
feedbackLabel: "Send feedback",
firstSectionLabel: "Jump to section",
firstActionLabel: "Common actions",
id: "ID",
isInNav: false,
},

argTypes: {
id: {
table: {
category: "SkipNavigation",
},
},
isInNav: {
table: {
category: "SkipNavigation",
},
},
feedbackUrl: {
table: {
category: "SkipNavigation",
},
},
feedbackLabel: {
table: {
category: "SkipNavigation",
},
},
firstSectionLabel: {
table: {
category: "SkipNavigation",
},
},
firstActionLabel: {
table: {
category: "SkipNavigation",
},
},
actions: {
table: {
category: "SkipNavigation",
},
},
},
};

export default meta;
type Story = StoryObj<typeof SkipNavigation>;

export const Default: Story = {
render: () => (
render: args => (
<>
<SkipNavigation />
<SkipNavigation {...args} />
{Content}
</>
),

parameters: {
info: "Default configuration of SkipNavigation. SkipNavigation is displayed only when focused. Use Tab or Shift + Tab to focus it.",
controls: {
disable: true,
},
};

export const InNavigationBar: CombinedStory = {
render: args => {
const { transparentBgAtTop, hideOnScroll, ...skipNavigationProps } = args;

return (
<>
<NavigationBar transparentBgAtTop={transparentBgAtTop} hideOnScroll={hideOnScroll}>
<Stack justify="between" spacing="none">
<SkipNavigation {...skipNavigationProps} />
<ButtonLink type="secondary">Flights</ButtonLink>
<Stack direction="row" spacing="100" justify="end" shrink as="nav">
<ButtonLink aria-label="Account" iconLeft={<AccountCircle />} type="secondary" />
</Stack>
</Stack>
</NavigationBar>
<div className="mt-100">{Content}</div>
</>
);
},

parameters: {
info: "Styling for SkipNavigation when used in navigation bar. SkipNavigation is displayed only when focused. Use Tab or Shift + Tab to focus it.",
},

args: {
isInNav: true,
feedbackUrl: "",
feedbackLabel: "",
firstSectionLabel: "Section",
firstActionLabel: "",
actions: undefined,
// NavigationBar props
transparentBgAtTop: false,
hideOnScroll: true,
},

argTypes: {
transparentBgAtTop: {
table: {
category: "NavigationBar",
},
},
hideOnScroll: {
table: {
category: "NavigationBar",
},
},
},
};
Expand All @@ -91,26 +208,4 @@ export const Playground: Story = {
parameters: {
info: "All possible options for SkipNavigation. SkipNavigation is displayed only when focused. Use Tab or Shift + Tab to focus it.",
},

args: {
actions: [
{
link: "https://www.kiwi.com/cz/pages/content/terms",
name: "Go to terms and conditions",
},
{
name: "Add baggage",
onClick: action("Add baggage"),
},
{
name: "Request refund",
onClick: action("Request refund"),
},
],
feedbackUrl: "#",
feedbackLabel: "Send feedback",
firstSectionLabel: "Jump to section",
firstActionLabel: "Common actions",
id: "ID",
},
};
23 changes: 19 additions & 4 deletions packages/orbit-components/src/SkipNavigation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const SkipNavigation = ({
firstActionLabel = "Common actions",
dataTest,
id,
isInNav,
}: Props) => {
const [links, setLinks] = React.useState<HTMLAnchorElement[]>([]);
const [mappedLinks, setMappedLinks] = React.useState<MappedOptions[]>([]);
Expand Down Expand Up @@ -85,18 +86,32 @@ const SkipNavigation = ({

return (
<div
className={cx("orbit-skip-navigation bg-cloud-light p-400 w-full", !show && "sr-only")}
className={cx(
"orbit-skip-navigation",
isInNav ? "bg-inherit p-0" : "bg-cloud-light p-400",
!show && "sr-only",
)}
tabIndex={-1}
onFocus={handleFocus}
onBlur={() => setShow(false)}
data-test={dataTest}
id={id}
>
<Stack justify="between">
<div className="max-w-[800px]">
<div className={isInNav ? "max-w-[250px]" : "max-w-[800px]"}>
<Stack align="center">
<Select options={mappedLinks} onChange={handleLinksClick} />
{innerPages.length > 0 && <Select options={innerPages} onChange={handlePageClick} />}
<Select
options={mappedLinks}
onChange={handleLinksClick}
ariaLabel={firstSectionLabel}
/>
{innerPages.length > 0 && (
<Select
options={innerPages}
onChange={handlePageClick}
ariaLabel={firstActionLabel}
/>
)}
</Stack>
</div>
{feedbackUrl && (
Expand Down
1 change: 1 addition & 0 deletions packages/orbit-components/src/SkipNavigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface Props extends Common.Globals {
readonly firstSectionLabel?: string;
readonly firstActionLabel?: string;
readonly feedbackLabel?: string;
readonly isInNav?: boolean;
}

export interface MappedOptions {
Expand Down
Loading