-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #94 from element-hq/germain-gg/nav-bar
Create Nav component
- Loading branch information
Showing
21 changed files
with
914 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+5.21 KB
playwright/visual.test.ts-snapshots/Nav-Nav-Item-Active-1-chromium-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+5.16 KB
...ight/visual.test.ts-snapshots/Nav-Nav-Item-Active-Disabled-1-chromium-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+5.21 KB
playwright/visual.test.ts-snapshots/Nav-Nav-Item-Active-Link-1-chromium-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+5.13 KB
playwright/visual.test.ts-snapshots/Nav-Nav-Item-Default-1-chromium-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+5.06 KB
playwright/visual.test.ts-snapshots/Nav-Nav-Item-Disabled-1-chromium-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+5.13 KB
playwright/visual.test.ts-snapshots/Nav-Nav-Item-Link-1-chromium-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+8.39 KB
playwright/visual.test.ts-snapshots/Nav-Tab-Role-1-chromium-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
/* Copyright 2023 The Matrix.org Foundation C.I.C. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
.nav-bar { | ||
border-block-end: var(--cpd-border-width-1) solid var(--cpd-color-gray-400); | ||
margin: var(--cpd-space-6x) 0; | ||
padding: 0; | ||
} | ||
|
||
.nav-bar-items { | ||
display: flex; | ||
flex-direction: row; | ||
justify-content: flex-start; | ||
align-items: center; | ||
gap: var(--cpd-space-3x); | ||
list-style: none; | ||
padding: 0; | ||
margin: 0; | ||
} | ||
|
||
.nav-tab { | ||
padding: var(--cpd-space-4x) 0; | ||
position: relative; | ||
} | ||
|
||
/* Underline effect */ | ||
.nav-tab::before { | ||
content: ""; | ||
position: absolute; | ||
inset-block-end: 0; | ||
inset-inline: 0; | ||
block-size: 0; | ||
border-radius: var(--cpd-radius-pill-effect) var(--cpd-radius-pill-effect) 0 0; | ||
background-color: var(--cpd-color-bg-action-primary-rest); | ||
transition: height 0.1s ease-in-out; | ||
} | ||
|
||
.nav-tab[data-current]::before { | ||
/* This is not exactly right: designs says 3px, but there are no variables for that */ | ||
block-size: var(--cpd-border-width-4); | ||
} | ||
|
||
.nav-item { | ||
padding-block: var(--cpd-space-1x); | ||
padding-inline: var(--cpd-space-2x); | ||
border-radius: var(--cpd-radius-pill-effect); | ||
cursor: pointer; | ||
appearance: none; | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
gap: var(--cpd-space-2x); | ||
box-sizing: border-box; | ||
background: transparent; | ||
border: 0; | ||
font: var(--cpd-font-body-md-regular); | ||
color: var(--cpd-color-text-secondary); | ||
text-decoration: none; | ||
} | ||
|
||
@media (hover) { | ||
.nav-item:not([disabled]):hover { | ||
color: var(--cpd-color-text-primary); | ||
background-color: var(--cpd-color-bg-subtle-secondary); | ||
} | ||
} | ||
|
||
.nav-item:focus-visible { | ||
outline: var(--cpd-color-border-focused) var(--cpd-border-width-2) solid; | ||
} | ||
|
||
.nav-item:not([disabled]):active { | ||
color: var(--cpd-color-text-primary); | ||
background-color: var(--cpd-color-bg-subtle-primary); | ||
} | ||
|
||
.nav-item[aria-current] { | ||
color: var(--cpd-color-text-primary); | ||
} | ||
|
||
.nav-item[disabled] { | ||
cursor: not-allowed; | ||
|
||
/* Enable pointer events for svgs even with fill=none */ | ||
pointer-events: all !important; | ||
color: var(--cpd-color-text-disabled); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
// Copyright 2022 The Matrix.org Foundation C.I.C. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
import React, { useEffect, useState } from "react"; | ||
import { NavBar, NavItem } from "."; | ||
|
||
export default { | ||
title: "Nav", | ||
component: NavBar, | ||
tags: ["autodocs"], | ||
parameters: { | ||
controls: { | ||
include: ["aria-label"], | ||
}, | ||
design: { | ||
type: "figma", | ||
url: "https://www.figma.com/file/rTaQE2nIUSLav4Tg3nozq7/Compound-Web-Components?type=design&node-id=669-2723&mode=design&t=9Hy0h7BBDH0kJ2Ow-0", | ||
}, | ||
}, | ||
args: { | ||
"aria-label": "Main", | ||
}, | ||
}; | ||
|
||
export const Default = { | ||
args: { | ||
children: ( | ||
<> | ||
<NavItem onClick={() => {}}>Info</NavItem> | ||
<NavItem onClick={() => {}} active> | ||
People | ||
</NavItem> | ||
<NavItem onClick={() => {}} disabled> | ||
Disabled | ||
</NavItem> | ||
<NavItem href="https://example.com">External</NavItem> | ||
</> | ||
), | ||
}, | ||
}; | ||
|
||
export const TabRole = { | ||
render: function Component() { | ||
// An example tab implementation | ||
const [activePanelId, setActivePanelId] = useState("panel-2"); | ||
const changeDisplay = (id: string, display: string) => { | ||
const e = document.querySelector(`#${id}`) as HTMLDivElement; | ||
if (e) e.style.display = display; | ||
}; | ||
useEffect(() => { | ||
["panel-1", "panel-2"].forEach((id) => { | ||
changeDisplay(id, "none"); | ||
}); | ||
changeDisplay(activePanelId, "block"); | ||
}, [activePanelId]); | ||
|
||
return ( | ||
<div> | ||
<NavBar role="tablist" aria-label="main"> | ||
<NavItem | ||
aria-controls="panel-1" | ||
onClick={() => { | ||
setActivePanelId("panel-1"); | ||
}} | ||
active={activePanelId === "panel-1"} | ||
> | ||
Tab 1 | ||
</NavItem> | ||
<NavItem | ||
aria-controls="panel-2" | ||
onClick={() => { | ||
setActivePanelId("panel-2"); | ||
}} | ||
active={activePanelId === "panel-2"} | ||
> | ||
Tab 2 | ||
</NavItem> | ||
<NavItem aria-controls="panel-3" onClick={() => {}} disabled> | ||
Disabled Tab | ||
</NavItem> | ||
</NavBar> | ||
<div id="panel-1" style={{ display: "none" }}> | ||
This is panel 1 | ||
</div> | ||
<div id="panel-2" style={{ display: "none" }}> | ||
This is panel 2 | ||
</div> | ||
<div id="panel-3" style={{ display: "none" }}> | ||
This is panel 3 | ||
</div> | ||
</div> | ||
); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/* | ||
Copyright 2023 New Vector Ltd | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
import { describe, it, expect } from "vitest"; | ||
import { render } from "@testing-library/react"; | ||
import React from "react"; | ||
import { composeStories } from "@storybook/react"; | ||
|
||
import * as stories from "./NavBar.stories"; | ||
|
||
const { Default, TabRole } = composeStories(stories); | ||
|
||
describe("<NavBar />", () => { | ||
it("render a default nav bar", () => { | ||
const { container } = render(<Default />); | ||
expect(container).toMatchSnapshot(); | ||
}); | ||
|
||
it("render a tabbed nav bar", () => { | ||
const { container } = render(<TabRole />); | ||
expect(container).toMatchSnapshot(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// Copyright 2023 The Matrix.org Foundation C.I.C. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
import React from "react"; | ||
import classNames from "classnames"; | ||
|
||
import styles from "./Nav.module.css"; | ||
|
||
type NavBarProps = { | ||
/** | ||
* The CSS class name | ||
*/ | ||
className?: string; | ||
|
||
/** | ||
* Require a label for navigation landmarks | ||
*/ | ||
"aria-label": string; | ||
|
||
/** | ||
* Accessibility role that defaults to navigation. | ||
* | ||
* If you pass tablist you must also pass `aria-controls` as prop to your NavItem and must | ||
* ensure that the conditions stated in https://www.w3.org/WAI/ARIA/apg/patterns/tabs/#wai-ariaroles,states,andproperties | ||
* are met. | ||
*/ | ||
role?: "navigation" | "tablist"; | ||
}; | ||
|
||
/** | ||
* A navigation bar component | ||
*/ | ||
export const NavBar = ({ | ||
children, | ||
className, | ||
role, | ||
"aria-label": ariaLabel, | ||
...rest | ||
}: React.PropsWithChildren<NavBarProps>) => { | ||
const classes = classNames(className, styles["nav-bar"]); | ||
/** | ||
* We sometimes want to use this NavBar for tabs. | ||
* This is done by passing `role=tablist` to this component. | ||
* By default, this component is used as a navigation bar. | ||
* | ||
* Depending on this role, a different set of accessibility | ||
* attributes need to be applied to the nav/ul element. | ||
*/ | ||
const a11yAttributesForNav = | ||
role !== "tablist" | ||
? /** | ||
* If role isn't tablist, default to navigation. | ||
*/ | ||
{ role: "navigation", "aria-label": ariaLabel } | ||
: /** | ||
* If role is tablist, give nav presentation role to remove | ||
* any semantic meaning. | ||
*/ | ||
{ role: "presentation" }; | ||
|
||
/** | ||
* When used as tabs, the tablist role must be applied to ul. | ||
* When used as navigation, no special accessibility attribute | ||
* is needed for the ul element. | ||
*/ | ||
const a11yAttributesForUl = | ||
role === "tablist" ? { role: "tablist", "aria-label": ariaLabel } : {}; | ||
|
||
return ( | ||
<nav {...a11yAttributesForNav} {...rest} className={classes}> | ||
<ul {...a11yAttributesForUl} className={styles["nav-bar-items"]}> | ||
{children} | ||
</ul> | ||
</nav> | ||
); | ||
}; |
Oops, something went wrong.