Compare commits

...
Author SHA1 Message Date
github-actions[bot] 26612e371d Lerna versionbump [ci skip]
- @navikt/ds-css@0.2.4
 - @navikt/ds-react@0.2.5
 - @navikt/ds-tokens@0.1.2
2021-03-25 15:35:16 +00:00
Andreas NordahlandKen b194506943 Accordion menu refactor (#1054)
* accordion-menu-refactor

* lint

* styling

* smaller padding for items

* remove placeholder hover and focus style

* use accordion-menu in accordion-anchor-menu

* delete old css

* balance title margins

* aria-current

* remove smooth scrolling code

* remove unused import

* fikset smoothscroll

* fix reponsive padding for accordion-anchor-menu in product page

* accordion-menu focus and hover style

* oppdatert export navn

* oppdatert export navn anchor-menu

* oppdatert index import/export

* oppdatert props

* increase height

* remove unused style

Co-authored-by: Ken <ken.aleksander@gmail.com>
2021-03-25 16:20:26 +01:00
github-actions[bot] 79dbffff1d Lerna versionbump [ci skip]
- @navikt/ds-css@0.2.3
2021-03-24 14:11:12 +00:00
Ken 0359934bb9 Smooth scrollbehavior baseline.css (#1055) 2021-03-24 14:58:44 +01:00
30 changed files with 635 additions and 429 deletions

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
.navds-accordion-anchor-menu {
padding: 1rem 0;
}
.navds-accordion-anchor-menu__title {
margin-left: 1rem;
margin-right: 1rem;
}
+69 -97
View File
@@ -1,113 +1,85 @@
/*******************
* .accordion-menu *
*******************/
.navds-accordion-menu__html {
scroll-behavior: smooth;
}
.navds-accordion-menu__nav {
padding: var(--navds-layout-padding-small) 0;
overflow: hidden;
}
.navds-accordion-menu__container {
padding: 0;
margin: 0;
.navds-accordion-menu__list {
list-style: none;
}
.navds-accordion-menu__title {
margin: var(--navds-layout-padding-small) 0;
padding: 0 var(--navds-layout-padding-small);
}
.navds-accordion-menu__item {
position: relative;
margin: 0;
padding: 0;
font-weight: var(--navds-font-weight-bold);
}
.navds-accordion-menu__item--active::after {
content: "";
position: absolute;
top: 0;
width: 0.5rem;
height: 100%;
background-color: var(--navds-color-blue-50);
}
.navds-link.navds-accordion-menu__link,
.navds-link.navds-accordion-menu__button {
min-height: 3rem;
display: flex;
align-items: center;
padding: 0 var(--navds-layout-padding-small);
background: none;
.navds-accordion-menu-collapsable__button {
cursor: pointer;
}
.navds-link.navds-accordion-menu__link--active:not(:active):not(:focus) {
font-size: 1.125rem;
font-weight: 600;
color: var(--navds-color-text-primary);
text-decoration: none;
}
.navds-accordion-menu__chevron {
margin-right: 0.25rem;
font-size: 1rem;
}
.navds-accordion-menu__button {
display: flex;
clear: both;
align-items: center;
justify-content: space-between;
border: 0;
background-color: transparent;
border: none;
font: inherit;
text-align: inherit;
margin: 0;
padding: 0;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
font-weight: var(--navds-font-weight-bold);
color: var(--navds-color-text-link);
text-decoration: underline;
}
.navds-accordion-menu__dropdown--content .navds-accordion-menu__link,
.navds-accordion-menu__dropdown--content .navds-accordion-menu__button {
padding-left: calc(var(--navds-layout-padding-small) + 2rem);
.navds-accordion-menu-collapsable .navds-accordion-menu__list,
.navds-accordion-menu-collapsable
.navds-accordion-menu-collapsable
.navds-accordion-menu-collapsable__button {
font-weight: var(--navds-font-weight-regular);
}
@media (min-width: 448px) {
.navds-accordion-menu__nav {
padding: var(--navds-layout-padding-medium-and-larger) 0;
}
.navds-accordion-menu__title {
padding: 0 var(--navds-layout-padding-medium-and-larger);
}
.navds-link.navds-accordion-menu__link,
.navds-link.navds-accordion-menu__button {
padding: 0 var(--navds-layout-padding-medium-and-larger);
}
.navds-accordion-menu__dropdown--content .navds-accordion-menu__link,
.navds-accordion-menu__dropdown--content .navds-accordion-menu__button {
padding: 0 calc(var(--navds-layout-padding-medium-and-larger) + 2rem);
}
.navds-accordion-menu-item,
.navds-accordion-menu-collapsable__button {
display: flex;
padding: 0.75rem 1rem;
line-height: 1.5rem; /* TODO: fix typography. */
}
@media (min-width: 960px) {
.navds-accordion-menu__nav {
padding: var(--navds-layout-padding-small) 0;
}
.navds-accordion-menu-collapsable__expand-icon {
font-size: 1.5rem;
color: var(--navds-color-text-link);
flex-shrink: 0;
margin-left: 0.5rem;
}
.navds-accordion-menu__title {
padding: 0 var(--navds-layout-padding-small);
}
.navds-accordion-menu-collapsable--open
> .navds-accordion-menu-collapsable__button
> .navds-accordion-menu-collapsable__expand-icon {
transform: rotate(180deg);
}
.navds-link.navds-accordion-menu__link,
.navds-link.navds-accordion-menu__button {
padding: 0 calc(var(--navds-layout-padding-small));
}
.navds-accordion-menu-collapsable .navds-accordion-menu-item,
.navds-accordion-menu-collapsable
.navds-accordion-menu-collapsable
.navds-accordion-menu-collapsable__button {
padding-left: 2rem;
}
.navds-accordion-menu__dropdown--content .navds-accordion-menu__link,
.navds-accordion-menu__dropdown--content .navds-accordion-menu__button {
padding: 0 calc(var(--navds-layout-padding-small) + 1rem);
}
.navds-accordion-menu-collapsable
.navds-accordion-menu-collapsable
.navds-accordion-menu-item,
.navds-accordion-menu-collapsable
.navds-accordion-menu-collapsable
.navds-accordion-menu-collapsable
.navds-accordion-menu-collapsable__button {
padding-left: 3rem;
}
.navds-accordion-menu-item:focus,
.navds-accordion-menu-collapsable__button:focus {
outline: 3px solid var(--navds-text-focus);
outline-offset: 0;
}
.navds-accordion-menu-item:hover,
.navds-accordion-menu-collapsable__button:hover {
background-color: var(--navds-color-gray-20);
}
.navds-accordion-menu-item--active {
box-shadow: inset 0.5rem 0 var(--navds-color-text-link);
color: var(--navds-color-text);
text-decoration: none;
font-weight: var(--navds-font-weight-bold);
}
+17
View File
@@ -6,6 +6,10 @@
@import "print.css";
@import "utility.css";
html {
scroll-behavior: smooth;
}
body {
font-family: var(--navds-font-family);
line-height: 1.375;
@@ -16,3 +20,16 @@ body {
a {
color: var(--navds-color-text-link);
}
/* https://web.dev/prefers-reduced-motion/ */
@media (prefers-reduced-motion: reduce) {
*, ::before, ::after {
animation-delay: -1ms !important;
animation-duration: 1ms !important;
animation-iteration-count: 1 !important;
background-attachment: initial !important;
scroll-behavior: auto !important;
transition-duration: 0s !important;
transition-delay: 0s !important;
}
}
+1
View File
@@ -1,5 +1,6 @@
@import "baseline/baseline.css";
@import "accordion.css";
@import "accordion-anchor-menu.css";
@import "accordion-menu.css";
@import "alert.css";
@import "button.css";
+38
View File
@@ -265,6 +265,44 @@
}
}
@media (min-width: 448px) and (max-width: 959px) {
.navds-layout__section .navds-accordion-anchor-menu__title {
margin-left: 2.5rem;
margin-right: 2.5rem;
}
.navds-layout__section .navds-accordion-menu-item,
.navds-layout__section
.navds-accordion-menu-collapsable
.navds-accordion-menu-collapsable__button {
padding-left: 2.5rem;
padding-right: 2.5rem;
}
.navds-layout__section
.navds-accordion-menu-collapsable
.navds-accordion-menu-item,
.navds-layout__section
.navds-accordion-menu-collapsable
.navds-accordion-menu-collapsable
.navds-accordion-menu-collapsable__button {
padding-left: 3.5rem;
padding-right: 3.5rem;
}
.navds-layout__section
.navds-accordion-menu-collapsable
.navds-accordion-menu-collapsable
.navds-accordion-menu-item,
.navds-layout__section
.navds-accordion-menu-collapsable
.navds-accordion-menu-collapsable
.navds-accordion-menu-collapsable
.navds-accordion-menu-collapsable__button {
padding-left: 4.5rem;
padding-right: 4.5rem;
}
}
@media (min-width: 648px) {
/* prettier-ignore */
.navds-layout__container--2-columns .navds-layout__panel--icon-margin:first-child {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css",
"version": "0.2.2",
"version": "0.2.4",
"private": false,
"description": "Css for NAV Designsystem components",
"author": "NAV Designsystem team",
@@ -21,7 +21,7 @@
"watch-css": "postcss -w --use postcss-import -o dist/index.css index.css"
},
"devDependencies": {
"@navikt/ds-tokens": "^0.1.1",
"@navikt/ds-tokens": "^0.1.2",
"normalize.css": "^8.0.1",
"postcss": "^8.2.7",
"postcss-cli": "^8.3.1",
@@ -0,0 +1,40 @@
import React, { forwardRef, HTMLAttributes } from "react";
import { AccordionMenu as BaseAccordionMenu } from "../accordion-menu";
import { Heading } from "../index";
import { ActiveAnchorProvider } from "./ActiveAnchorStore";
import cl from "classnames";
export interface AccordionAnchorMenuProps
extends HTMLAttributes<HTMLDivElement> {
title?: string;
children: React.ReactNode;
className?: string;
}
const AccordionAnchorMenu = forwardRef<
HTMLDivElement,
AccordionAnchorMenuProps
>(({ children, title, className, ...rest }, ref) => (
<div
className={cl("navds-accordion-anchor-menu", className)}
ref={ref}
{...rest}
>
{title && (
<Heading
level={2}
size="medium"
className="navds-accordion-anchor-menu__title"
>
{title}
</Heading>
)}
<ActiveAnchorProvider>
<BaseAccordionMenu aria-label={title || "Meny"}>
{children}
</BaseAccordionMenu>
</ActiveAnchorProvider>
</div>
));
export default AccordionAnchorMenu;
@@ -0,0 +1 @@
export { AccordionMenuCollapsable as AccordionAnchorMenuCollapsable } from "../accordion-menu";
@@ -0,0 +1,39 @@
import React, { forwardRef, useEffect } from "react";
import { AccordionMenuItem, AccordionMenuItemType } from "../accordion-menu";
import { useStore } from "./ActiveAnchorStore";
const AccordionAnchorMenuItem: AccordionMenuItemType = forwardRef(
(props, ref) => {
const isAnchorActive = useIsAnchorActive(
props.href && props.href.split("#")[1]
);
return (
<AccordionMenuItem
{...props}
ref={ref}
active={props.active || isAnchorActive}
/>
);
}
);
const useIsAnchorActive = (anchor: string | undefined) => {
const { activeAnchor, registerAnchor, unregisterAnchor } = useStore();
useEffect(() => {
if (anchor) {
const target = document.getElementById(anchor);
if (target) {
registerAnchor(anchor);
return () => {
unregisterAnchor(anchor);
};
}
}
}, [anchor, registerAnchor, unregisterAnchor]);
return (anchor && activeAnchor === anchor) || false;
};
export default AccordionAnchorMenuItem;
@@ -0,0 +1,69 @@
import React, {
createContext,
useCallback,
useContext,
useState,
useEffect,
} from "react";
export interface ActiveAnchorStore {
activeAnchor?: string;
registerAnchor: (anchor: string) => void;
unregisterAnchor: (anchor: string) => void;
}
export const ActiveAnchorContext = createContext({} as ActiveAnchorStore);
export const ActiveAnchorProvider = ({ children }) => {
const [anchors, setAnchors] = useState<string[]>([]);
const [activeAnchor, setActiveAnchor] = useState<string>();
useEffect(() => {
const scrollListener = () => {
const offset = 100;
const lastPassedAnchor = anchors
.map((anchor) => document.getElementById(anchor))
.map((element: HTMLElement) => ({
id: element.id,
top: element.getBoundingClientRect().top - offset,
scrolledToBottom:
window.innerHeight + window.pageYOffset >=
document.body.offsetHeight,
}))
.filter((element) => element.scrolledToBottom || element.top <= 0)
.sort((a, b) => (a.top < b.top ? -1 : 1))
.map((anchor) => anchor.id)
.pop();
// Set active anchor and related url hash
if (lastPassedAnchor && activeAnchor !== lastPassedAnchor) {
const { href, hash } = window.location;
const urlWithoutHash = href.replace(hash, "");
const urlWithAnchor = `${urlWithoutHash}#${lastPassedAnchor}`;
const title = document.title;
window.history.pushState(lastPassedAnchor, title, urlWithAnchor);
setActiveAnchor(lastPassedAnchor);
}
};
window.addEventListener("scroll", scrollListener);
return () => {
window.removeEventListener("scroll", scrollListener);
};
}, [anchors, activeAnchor]);
return (
<ActiveAnchorContext.Provider
value={{
activeAnchor,
registerAnchor: useCallback((anchor) => {
setAnchors((anchors) => [...anchors, anchor]);
}, []),
unregisterAnchor: useCallback((anchor) => {
setAnchors((anchors) => anchors.filter((a) => a !== anchor));
}, []),
}}
>
{children}
</ActiveAnchorContext.Provider>
);
};
export const useStore = () => useContext(ActiveAnchorContext);
@@ -0,0 +1,3 @@
export { default as AccordionAnchorMenu } from "./AccordionAnchorMenu";
export { AccordionAnchorMenuCollapsable } from "./AccordionAnchorMenuCollapsable";
export { default as AccordionAnchorMenuItem } from "./AccordionAnchorMenuItem";
@@ -1,5 +1,9 @@
import * as React from "react";
import Menu from "../index";
import {
AccordionAnchorMenu as Menu,
AccordionAnchorMenuCollapsable as Collapsable,
AccordionAnchorMenuItem as Item,
} from "../index";
import { useEffect } from "react";
import { ProductPageLayout } from "../../index";
import { Right } from "../../layouts/stories/components/sections/Right";
@@ -9,10 +13,8 @@ import { MainTwo } from "../../layouts/stories/components/sections/Main";
import { MainThree } from "../../layouts/stories/components/sections/Main";
import { Link, HashRouter as Router } from "react-router-dom";
import "./components/styles.css";
export default {
title: "ds-react/accordion-menu",
title: "ds-react/accordion-anchor-menu",
component: { Menu },
decorators: [
(Story) => {
@@ -35,13 +37,13 @@ export default {
export const All = () => (
<ProductPageLayout title={"Nunc et lorem"}>
<ProductPageLayout.Section left sticky withPadding={false}>
<Menu title={"Minima"}>
<Menu.Item href={"#leo-quis"}>Leo quis</Menu.Item>
<Menu.Collapsable title={"Proin accumsan"}>
<Menu.Item href={"#nulla-pariatur"}>Nulla pariatur</Menu.Item>
<Menu.Item href={"#luctus-justo"}>Luctus justo</Menu.Item>
</Menu.Collapsable>
<Menu.Item href={"#maecenas-in-pretium"}>Maecenas in pretium</Menu.Item>
<Menu title={"Minima"} smoothScrollBehavior>
<Item href={"#leo-quis"}>Leo quis</Item>
<Collapsable title={"Proin accumsan"}>
<Item href={"#nulla-pariatur"}>Nulla pariatur</Item>
<Item href={"#luctus-justo"}>Luctus justo</Item>
</Collapsable>
<Item href={"#maecenas-in-pretium"}>Maecenas in pretium</Item>
</Menu>
</ProductPageLayout.Section>
<ProductPageLayout.Section whiteBackground={false} withPadding={false}>
@@ -77,28 +79,28 @@ export const ReactRouter = () => (
<ProductPageLayout title={"Nunc et lorem"}>
<ProductPageLayout.Section left sticky withPadding={false}>
<Menu>
<Menu.Item component={Link} to="/link1">
<Item component={Link} to="/link1">
Leo quis
</Menu.Item>
<Menu.Collapsable title={"Proin accumsan"}>
<Menu.Item component={Link} to="/link2">
</Item>
<Collapsable title={"Proin accumsan"}>
<Item component={Link} to="/link2">
Nulla pariatur
</Menu.Item>
<Menu.Item component={Link} to="/link3">
</Item>
<Item component={Link} to="/link3">
Luctus justo
</Menu.Item>
</Menu.Collapsable>
<Menu.Collapsable title={"Sint cupidatat"}>
<Menu.Item component={Link} to="/link4">
</Item>
</Collapsable>
<Collapsable title={"Sint cupidatat"}>
<Item component={Link} to="/link4">
Nulla pariatur
</Menu.Item>
<Menu.Item component={Link} to="/link5">
</Item>
<Item component={Link} to="/link5">
Luctus justo
</Menu.Item>
</Menu.Collapsable>
<Menu.Item component={Link} to="/link6">
</Item>
</Collapsable>
<Item component={Link} to="/link6">
Maecenas in pretium
</Menu.Item>
</Item>
</Menu>
</ProductPageLayout.Section>
<ProductPageLayout.Section whiteBackground={false} withPadding={false}>
@@ -0,0 +1,25 @@
import React, { forwardRef, HTMLAttributes } from "react";
import cl from "classnames";
import MenuItems from "./MenuItems";
export interface AccordionMenuProps extends HTMLAttributes<HTMLElement> {
children: React.ReactNode;
className?: string;
}
const AccordionMenu = forwardRef<HTMLElement, AccordionMenuProps>(
({ children, className, ...rest }, ref) => {
return (
<nav
role="navigation"
ref={ref}
className={cl("navds-accordion-menu", className)}
{...rest}
>
<MenuItems>{children}</MenuItems>
</nav>
);
}
);
export default AccordionMenu;
@@ -0,0 +1,44 @@
import React, { forwardRef, HTMLAttributes, useState } from "react";
import cl from "classnames";
import { Expand } from "@navikt/ds-icons";
import MenuItems from "./MenuItems";
export interface AccordionMenuCollapsableProps
extends HTMLAttributes<HTMLDivElement> {
title: string;
defaultOpen?: boolean;
children: React.ReactNode;
className?: string;
}
const AccordionMenuCollapsable = forwardRef<
HTMLDivElement,
AccordionMenuCollapsableProps
>(({ children, defaultOpen = false, title, className, ...rest }, ref) => {
const [isOpen, setIsOpen] = useState(defaultOpen);
return (
<div
ref={ref}
className={cl("navds-accordion-menu-collapsable", className, {
"navds-accordion-menu-collapsable--open": isOpen,
})}
{...rest}
>
<button
onClick={() => setIsOpen(!isOpen)}
className="navds-accordion-menu-collapsable__button"
>
{title}
<Expand
role="img"
aria-label={isOpen ? "Pil peker opp" : "Pil peker ned"}
className="navds-accordion-menu-collapsable__expand-icon"
/>
</button>
{isOpen && <MenuItems>{children}</MenuItems>}
</div>
);
});
export default AccordionMenuCollapsable;
@@ -0,0 +1,40 @@
import React, { forwardRef } from "react";
import { OverridableComponent } from "../util";
import cl from "classnames";
export type AccordionMenuItemType = OverridableComponent<AccordionMenuItemProps>;
export interface AccordionMenuItemProps {
props: {
children: React.ReactNode;
active?: boolean;
} & React.HTMLAttributes<HTMLAnchorElement>;
defaultComponent: "a";
}
const AccordionMenuItem: AccordionMenuItemType = forwardRef(
(
{
children,
component: Component = "a",
active = false,
className,
...rest
},
ref
) => {
return (
<Component
ref={ref}
className={cl("navds-accordion-menu-item", className, {
"navds-accordion-menu-item--active": active,
})}
{...rest}
>
{children}
</Component>
);
}
);
export default AccordionMenuItem;
@@ -1,40 +0,0 @@
import React, { createContext, useCallback, useContext, useState } from "react";
interface Props {
children: JSX.Element | JSX.Element[];
}
export interface ActiveAnchorStore {
anchors: string[];
activeAnchor?: string;
registerAnchor: (anchor: string) => void;
unregisterAnchor: (anchor: string) => void;
setActiveAnchor: (anchor: string) => void;
}
export const ActiveAnchorContext = createContext({} as ActiveAnchorStore);
export const ActiveAnchorProvider = (props: Props) => {
const { children } = props;
const [anchors, setAnchors] = useState<string[]>([]);
const [activeAnchor, setActiveAnchor] = useState<string>();
return (
<ActiveAnchorContext.Provider
value={{
anchors,
activeAnchor,
registerAnchor: useCallback((anchor) => {
setAnchors((anchors) => [...anchors, anchor]);
}, []),
unregisterAnchor: useCallback((anchor) => {
setAnchors((anchors) => anchors.filter((a) => a !== anchor));
}, []),
setActiveAnchor: useCallback((anchor) => {
setActiveAnchor(anchor);
}, []),
}}
>
{children}
</ActiveAnchorContext.Provider>
);
};
export const useStore = () => useContext(ActiveAnchorContext);
@@ -1,52 +0,0 @@
import React, { forwardRef, HTMLAttributes, useState } from "react";
import cl from "classnames";
import { Expand } from "@navikt/ds-icons";
export interface AccordionMenuCollapsableProps
extends HTMLAttributes<HTMLButtonElement> {
title: string;
active?: boolean;
}
const Collapsable = forwardRef<
HTMLButtonElement,
AccordionMenuCollapsableProps
>(({ children, active, title, className, ...rest }, ref) => {
const [open, setOpen] = useState(active || false);
return (
<li
className={cl(
"navds-accordion-menu__item",
"navds-accordion-menu__dropdown"
)}
>
<button
ref={ref}
onClick={() => setOpen(!open)}
className={cl("navds-link", "navds-accordion-menu__button", className)}
{...rest}
>
{title}
<Expand
className={cl(
"navds-accordion__chevron",
`navds-accordion__chevron--${open ? "up" : "down"}`,
`navds-accordion-menu__chevron`
)}
/>
</button>
{open && (
<ul
className={cl(
"navds-accordion-menu__container",
"navds-accordion-menu__dropdown--content"
)}
>
{children}
</ul>
)}
</li>
);
});
export default Collapsable;
-62
View File
@@ -1,62 +0,0 @@
import React, { forwardRef, useEffect } from "react";
import { useStore } from "./ActiveAnchorStore";
import { OverridableComponent } from "../util";
import cl from "classnames";
export interface AccordionMenuItemProps {
props: {
children: React.ReactNode;
active?: boolean;
} & React.HTMLAttributes<HTMLLIElement>;
defaultComponent: "a";
}
const Item: OverridableComponent<AccordionMenuItemProps> = forwardRef(
(
{
children,
component: Component = "a",
active = false,
className,
...rest
},
ref
) => {
const anchor = rest.href && rest.href.split("#")[1];
const { activeAnchor, registerAnchor, unregisterAnchor } = useStore();
const isActive = active || (anchor && activeAnchor === anchor) || false;
useEffect(() => {
if (anchor) {
const target = document.getElementById(anchor);
if (target) {
registerAnchor(anchor);
return () => {
unregisterAnchor(anchor);
};
}
}
}, [anchor, registerAnchor, unregisterAnchor]);
return (
<li
className={cl(
"navds-accordion-menu__item",
isActive && "navds-accordion-menu__item--active"
)}
>
<Component
ref={ref}
className={cl("navds-link", "navds-accordion-menu__link", className, {
"navds-accordion-menu__link--active": isActive,
})}
{...rest}
>
{children}
</Component>
</li>
);
}
);
export default Item;
-68
View File
@@ -1,68 +0,0 @@
import React, {
forwardRef,
ForwardRefExoticComponent,
HTMLAttributes,
useEffect,
} from "react";
import { default as Item, AccordionMenuItemProps } from "./Item";
import { default as Collapsable } from "./Collapsable";
import { AccordionMenuCollapsableProps } from "./Collapsable";
import { Heading, OverridableComponent } from "../index";
import { ActiveAnchorProvider } from "./ActiveAnchorStore";
import Nav from "./Nav";
import cl from "classnames";
export interface LayoutWithSubComponents
extends ForwardRefExoticComponent<AccordionMenuProps> {
Collapsable: ForwardRefExoticComponent<AccordionMenuCollapsableProps>;
Item: OverridableComponent<AccordionMenuItemProps>;
}
export interface AccordionMenuProps extends HTMLAttributes<HTMLUListElement> {
title?: string;
smoothScrollBehavior?: boolean;
}
const AccordionMenu = forwardRef<HTMLUListElement, AccordionMenuProps>(
(
{ children, title, smoothScrollBehavior = true, className, ...rest },
ref
) => {
useEffect(() => {
if (smoothScrollBehavior) {
const htmlElement = document.documentElement;
htmlElement.classList.add("navds-accordion-menu__html");
return () => {
htmlElement.classList.remove("navds-accordion-menu__html");
};
}
}, [smoothScrollBehavior]);
return (
<ActiveAnchorProvider>
<Nav title={title}>
{title && (
<Heading
level={2}
size="medium"
className={cl("navds-accordion-menu__title", className)}
>
{title}
</Heading>
)}
<ul
ref={ref}
className={cl("navds-accordion-menu__container", className)}
{...rest}
>
{children}
</ul>
</Nav>
</ActiveAnchorProvider>
);
}
) as LayoutWithSubComponents;
AccordionMenu.Item = Item;
AccordionMenu.Collapsable = Collapsable;
export default AccordionMenu;
@@ -0,0 +1,16 @@
import React from "react";
/**
* @ignore
*/
const MenuItems = ({ children }) => (
<ul className="navds-accordion-menu__list">
{React.Children.toArray(children).map((child, i) => (
<li key={i} className="navds-accordion-menu__list-item">
{child}
</li>
))}
</ul>
);
export default MenuItems;
-54
View File
@@ -1,54 +0,0 @@
import React, { forwardRef, HTMLAttributes, useEffect } from "react";
import { useStore } from "./ActiveAnchorStore";
export interface AccordionMenuItemProps
extends HTMLAttributes<HTMLAnchorElement> {
title?: string;
}
const Nav = forwardRef<HTMLAnchorElement, AccordionMenuItemProps>(
({ children, title, className, ...rest }, ref) => {
const { anchors, activeAnchor, setActiveAnchor } = useStore();
useEffect(() => {
const scrollListener = () => {
const offset = 100;
const lastPassedAnchor = anchors
.map((anchor) => document.getElementById(anchor))
.map((element: HTMLElement) => ({
id: element.id,
top: element.getBoundingClientRect().top - offset,
scrolledToBottom:
window.innerHeight + window.pageYOffset >=
document.body.offsetHeight,
}))
.filter((element) => element.scrolledToBottom || element.top <= 0)
.sort((a, b) => (a.top < b.top ? -1 : 1))
.map((anchor) => anchor.id)
.pop();
// Set active anchor and related url hash
if (lastPassedAnchor && activeAnchor !== lastPassedAnchor) {
const { href, hash } = window.location;
const urlWithoutHash = href.replace(hash, "");
const urlWithAnchor = `${urlWithoutHash}#${lastPassedAnchor}`;
const title = document.title;
window.history.pushState(lastPassedAnchor, title, urlWithAnchor);
setActiveAnchor(lastPassedAnchor);
}
};
window.addEventListener("scroll", scrollListener);
return () => {
window.removeEventListener("scroll", scrollListener);
};
}, [anchors, activeAnchor, setActiveAnchor]);
return (
<nav className={"navds-accordion-menu__nav"} aria-label={title || "Meny"}>
{children}
</nav>
);
}
);
export default Nav;
+6 -5
View File
@@ -1,5 +1,6 @@
import { default as AccordionMenu, AccordionMenuProps } from "./Menu";
import { AccordionMenuItemProps } from "./Item";
export { AccordionMenu, AccordionMenuProps, AccordionMenuItemProps };
export default AccordionMenu;
export { default as AccordionMenu } from "./AccordionMenu";
export { default as AccordionMenuCollapsable } from "./AccordionMenuCollapsable";
export {
default as AccordionMenuItem,
AccordionMenuItemType,
} from "./AccordionMenuItem";
@@ -0,0 +1,160 @@
import * as React from "react";
import {
AccordionMenu,
AccordionMenuItem as Item,
AccordionMenuCollapsable as Collapsable,
} from "../index";
import { NavLink, HashRouter as Router } from "react-router-dom";
export default {
title: "ds-react/accordion-menu",
component: { AccordionMenu },
parameters: {
backgrounds: {
default: "light",
values: [
{
name: "light",
value: "#F1F1F1",
},
{
name: "white",
value: "#fff",
},
],
},
},
};
export const All = () => (
<div
style={{
background: "white",
maxWidth: 288,
}}
>
<AccordionMenu>
<Item href="#leo">Leo</Item>
<Collapsable title="Proin">
<Item href="#nulla" active>
Nulla
</Item>
<Item href="#luctus">Luctus</Item>
</Collapsable>
<Collapsable title="Accumsan">
<Item href="#justo" ariaCurrent="page">
Justo
</Item>
<Item href="#pariatur" active ariaCurrent="page">
Pariatur
</Item>
<Collapsable title="Proin">
<Item href="#nulla" active>
Nulla
</Item>
<Item href="#luctus">Luctus</Item>
</Collapsable>
</Collapsable>
<Item href="#justo" active>
Justo
</Item>
<Item href="#pariatur">Pariatur</Item>
</AccordionMenu>
</div>
);
export const ReactRouter = () => (
<AccordionMenu>
<Collapsable title="Nivå 1 – 3 rems høy">
<Item
component={NavLink}
activeClassName="navds-accordion-menu-item--active"
to="/1"
>
Nivå 2 innrykk 2 rems
</Item>
<Item
component={NavLink}
activeClassName="navds-accordion-menu-item--active"
to="/2"
>
Subtitle 3
</Item>
</Collapsable>
<Collapsable title="Skjema og søknad">
<Collapsable title="Satser">
<Item
component={NavLink}
activeClassName="navds-accordion-menu-item--active"
to="/3"
>
Nivå 3 – 3 rems innrykk Har du rett til dagpenger når du mottar annen
økonomisk støtte fra NAV?
</Item>
</Collapsable>
<Item
component={NavLink}
activeClassName="navds-accordion-menu-item--active"
to="/4"
>
Subtitle 2
</Item>
<Item
component={NavLink}
activeClassName="navds-accordion-menu-item--active"
to="/5"
>
Subtitle 3
</Item>
</Collapsable>
<Collapsable title="Sakbehandlingstider">
<Collapsable title="Subtitle 4 Lang tittel på andre nivå lorem ipsum">
<Item
component={NavLink}
activeClassName="navds-accordion-menu-item--active"
to="/6"
>
Subtitle 5
</Item>
</Collapsable>
</Collapsable>
<Item
component={NavLink}
activeClassName="navds-accordion-menu-item--active"
to="/7"
>
Internasjonalt
</Item>
<Collapsable title="Meld fra om endringer lang tittel på første nivå">
<Item
component={NavLink}
activeClassName="navds-accordion-menu-item--active"
to="/8"
>
Subtitle 6
</Item>
</Collapsable>
<Item
component={NavLink}
activeClassName="navds-accordion-menu-item--active"
to="/9"
>
Nivå 1
</Item>
</AccordionMenu>
);
ReactRouter.decorators = [
(Story) => (
<Router>
<div
style={{
background: "white",
maxWidth: 288,
}}
>
<Story />
</div>
</Router>
),
];
@@ -1,12 +0,0 @@
.navds-story-menu {
margin-top: 0;
list-style-type: none;
}
.navds-story-menu li:not(:last-child) {
margin-bottom: 1rem;
}
.navds-story-section {
margin-bottom: 1rem;
}
+10 -5
View File
@@ -1,3 +1,13 @@
export {
AccordionAnchorMenu,
AccordionAnchorMenuCollapsable,
AccordionAnchorMenuItem,
} from "./accordion-anchor-menu";
export {
AccordionMenu,
AccordionMenuCollapsable,
AccordionMenuItem,
} from "./accordion-menu";
export { Accordion, AccordionProps } from "./accordion/index";
export { Alert, AlertProps } from "./alert/index";
export { Button, ButtonProps } from "./button/index";
@@ -19,11 +29,6 @@ export {
ProductPageLayoutProps,
ProductPageLayoutSectionProps,
} from "./layouts/index";
export {
AccordionMenu,
AccordionMenuProps,
AccordionMenuItemProps,
} from "./accordion-menu";
export { Link, LinkProps } from "./link/index";
export { Modal, ModalProps } from "./modal/index";
export { Popover, PopoverProps } from "./popover/index";
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "0.2.4",
"version": "0.2.5",
"private": false,
"description": "NAV designsystem react components",
"author": "NAV Designsystem team",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tokens",
"version": "0.1.1",
"version": "0.1.2",
"private": false,
"description": "Auto-generated design-tokens for NAV design-systems framework",
"author": "NAV Designsystem team",
+6 -2
View File
@@ -25,8 +25,12 @@ module.exports = {
white: { value: white },
darkgray: { value: darkgray },
gray: {
40: { value: "#b7b1a9" },
60: { value: "#78706a" },
90: { value: "#262626" },
80: { value: "#4F4F4F" },
60: { value: "#6A6A6A" },
40: { value: "#A0A0A0" },
20: { value: "#C9C9C9" },
10: { value: "#F1F1F1" },
},
...Object.entries(baseColors).reduce(
(colors, [name, color]) => ({
+5 -1
View File
@@ -54,7 +54,10 @@
]
},
"stylelint": {
"extends": "stylelint-config-standard",
"extends": [
"stylelint-config-standard",
"stylelint-config-prettier"
],
"rules": {
"value-list-comma-newline-after": null,
"declaration-colon-newline-after": null,
@@ -135,6 +138,7 @@
"less-loader": "^7.0.2",
"mini-css-extract-plugin": "^1.3.0",
"prettier": "2.2.1",
"stylelint-config-prettier": "^8.0.2",
"prop-types": "^15.5.10",
"react": "^16.8.0 || ^17.0.0",
"react-docgen-typescript": "^1.21.0",
+5
View File
@@ -22802,6 +22802,11 @@ stylehacks@^4.0.0:
postcss "^7.0.0"
postcss-selector-parser "^3.0.0"
stylelint-config-prettier@^8.0.2:
version "8.0.2"
resolved "https://registry.yarnpkg.com/stylelint-config-prettier/-/stylelint-config-prettier-8.0.2.tgz#da9de33da4c56893cbe7e26df239a7374045e14e"
integrity sha512-TN1l93iVTXpF9NJstlvP7nOu9zY2k+mN0NSFQ/VEGz15ZIP9ohdDZTtCWHs5LjctAhSAzaILULGbgiM0ItId3A==
stylelint-config-recommended@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-3.0.0.tgz#e0e547434016c5539fe2650afd58049a2fd1d657"