mirror of
https://github.com/navikt/aksel.git
synced 2026-10-13 09:12:03 +00:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffcfe79c18 | ||
|
|
5d8d2ffba9 | ||
|
|
c785316086 | ||
|
|
7087f160e8 | ||
|
|
235e48a843 | ||
|
|
93fc5dc6ae | ||
|
|
5c49902ffb | ||
|
|
c9c148e050 | ||
|
|
adf9de3c63 | ||
|
|
26612e371d | ||
|
|
b194506943 | ||
|
|
79dbffff1d | ||
|
|
0359934bb9 |
No files matched your search
@@ -0,0 +1,8 @@
|
||||
.navds-accordion-anchor-menu {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.navds-accordion-anchor-menu__title {
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
@@ -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: -3px;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
@@ -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,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";
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-css",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.5",
|
||||
"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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-icons",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"private": false,
|
||||
"description": "NAVs egne ikoner i SVG og React format",
|
||||
"repository": {
|
||||
@@ -22,6 +22,7 @@
|
||||
"files": [
|
||||
"/cjs",
|
||||
"/esm",
|
||||
"/src",
|
||||
"/svg"
|
||||
],
|
||||
"scripts": {
|
||||
|
||||
@@ -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";
|
||||
+29
-27
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -20,6 +20,11 @@ export interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
|
||||
* @default "medium"
|
||||
*/
|
||||
size?: "medium" | "small";
|
||||
/**
|
||||
* Prevent the user from interacting with the button: it cannot be pressed or focused.
|
||||
* @default false
|
||||
*/
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-react",
|
||||
"version": "0.2.4",
|
||||
"version": "0.2.7",
|
||||
"private": false,
|
||||
"description": "NAV designsystem react components",
|
||||
"author": "NAV Designsystem team",
|
||||
@@ -28,11 +28,12 @@
|
||||
],
|
||||
"scripts": {
|
||||
"cjs-package": "copyfiles -f config/package.json cjs",
|
||||
"build": "tsc -p tsconfig.json && tsc -p tsconfig-esm.json && yarn cjs-package",
|
||||
"clean": "rm -rf cjs esm",
|
||||
"build": "yarn run clean && tsc && tsc -p tsconfig-esm.json && yarn cjs-package",
|
||||
"watch": "tsc --watch -p tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@navikt/ds-icons": "^0.2.0",
|
||||
"@navikt/ds-icons": "^0.2.1",
|
||||
"@popperjs/core": "^2.5.4",
|
||||
"classnames": "^2.2.6",
|
||||
"react-collapse": "^5.1.0",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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]) => ({
|
||||
|
||||
@@ -1,47 +1,34 @@
|
||||
import { NavdsPage } from "../../../src/pages";
|
||||
import { useRouter } from "next/router";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
AccordionMenu,
|
||||
AccordionMenuCollapsable,
|
||||
AccordionMenuItem,
|
||||
} from "@navikt/ds-react";
|
||||
|
||||
const MenuLink = (node) => {
|
||||
const { asPath } = useRouter();
|
||||
|
||||
/* TODO: Replace with accordion-menu when ready */
|
||||
const subLinks = (page: NavdsPage) => {
|
||||
return (
|
||||
<div key={page.title}>
|
||||
<span>{page.title}</span>
|
||||
<ul id={"collapse " + page.title}>{parsePages(page.children)}</ul>
|
||||
</div>
|
||||
<Link href={node.pathName} passHref>
|
||||
<AccordionMenuItem active={asPath === node.pathName}>
|
||||
{node.title}
|
||||
</AccordionMenuItem>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const parsePages = (pages) => {
|
||||
return pages?.map((page) => {
|
||||
if (page.pathName) {
|
||||
return (
|
||||
<li key={page.pathName}>
|
||||
<Link href={page.pathName}>
|
||||
{page.title ? page.title : parseTitle(page.pathName)}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
} else if (page.title && page.children) {
|
||||
return subLinks(page);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const parseTitle = (path: string) =>
|
||||
path
|
||||
.split("/")
|
||||
.pop()
|
||||
.replace(/^\w/, (c) => c.toUpperCase())
|
||||
.replace("-", " ") || "ERR: Default Title";
|
||||
|
||||
const Menu = ({ menu }) => {
|
||||
return (
|
||||
<nav>
|
||||
<ul>{parsePages(menu)}</ul>
|
||||
</nav>
|
||||
const mapToComponents = (node) =>
|
||||
node.children ? (
|
||||
<AccordionMenuCollapsable key={node.title} title={node.title}>
|
||||
{node.children.map(mapToComponents)}
|
||||
</AccordionMenuCollapsable>
|
||||
) : (
|
||||
<MenuLink key={node.title} {...node} />
|
||||
);
|
||||
};
|
||||
|
||||
const Menu = ({ menu }) => (
|
||||
<AccordionMenu>{menu.map(mapToComponents)}</AccordionMenu>
|
||||
);
|
||||
|
||||
export default Menu;
|
||||
@@ -11,7 +11,12 @@ interface SidebarProps {
|
||||
menu: any[];
|
||||
}
|
||||
|
||||
const Sidebar = ({ sidebar, small, onSidebarChange, menu }: SidebarProps) => {
|
||||
const Sidebar = ({
|
||||
sidebar,
|
||||
small,
|
||||
onSidebarChange,
|
||||
menu = [],
|
||||
}: SidebarProps) => {
|
||||
return (
|
||||
<>
|
||||
{sidebar && small && (
|
||||
|
||||
+5
-1
@@ -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",
|
||||
|
||||
@@ -19,6 +19,11 @@ import Panel from "nav-frontend-paneler";
|
||||
vi bygger i NAV være universelt utformet slik at de aller fleste kan klare seg
|
||||
selv.
|
||||
</Ingress>
|
||||
<Ingress>
|
||||
Utdypende informasjon om hvordan håndtere universell utforming av IT i NAV
|
||||
finner du på{" "}
|
||||
<a href="https://navikt.github.io/uu/">https://navikt.github.io/uu/</a>
|
||||
</Ingress>
|
||||
|
||||
## Nødvendig for noen, bra for alle
|
||||
|
||||
@@ -144,7 +149,9 @@ Dette er noen av de viktigste tingene du bør huske på for å unngå de vanligs
|
||||
|
||||
### For designere
|
||||
|
||||
1. Bruk aktuell versjon av [NAVs komponent- og illustrasjonsbibliotek i Sketch](/resources/new-project#for-designere) når du designer.
|
||||
[Flere tips for produktdesignere](https://navikt.github.io/uu/hvordan-faa-det-til/tips-etter-rolle/Produktdesigner/) og [tjenestedesignere](https://navikt.github.io/uu/hvordan-faa-det-til/tips-etter-rolle/Tjenestedesigner/) finner du på uu-sidene.
|
||||
|
||||
1. Bruk aktuell versjon av [NAVs komponent- og illustrasjonsbibliotek](/resources/new-project#for-designere) når du designer.
|
||||
2. Design for grupper med spesielle behov («ytterkantene»). Spør deg selv: Hadde bestemoren min klart å bruke tjenesten? [Om inklusiv design (University of Cambridge)](http://www.inclusivedesigntoolkit.com/whatis/whatis.html)
|
||||
3. Husk [god kontrast](/resources/colors/accessibility#krav-til-kontrast), både på tekst, interaktive elementer, grafer og illustrasjoner.
|
||||
4. Husk [beskrivende alt-tekster](/accessibility/alt-text) på bilder, illustrasjoner og grafer.
|
||||
@@ -152,21 +159,23 @@ Dette er noen av de viktigste tingene du bør huske på for å unngå de vanligs
|
||||
6. Video: Husk å ha en tekstversjon som har samme innhold som videoen på siden, samt tekste video og bruk nyeste versjon av QBrick. Snart kommer en lovendring om at videoer også må være synstolket. Det betyr at man skal tilby en audiobeskrivelse av det som vises i videoen som ikke blir tydelig av selve lydsporet.
|
||||
7. Språk og begreper: Skriv så forståelig som mulig og test teksten på reelle brukere. Bruk beskrivende overskrifter, deloverskrifter og punktlister der det er hensiktsmessig. Husk å ha en hovedoverskrift som beskriver hovedinnholdet på siden. Les mer om [tekst og struktur (Difi)]([https://uu.difi.no/krav-og-regelverk/losningsforslag-web/tekst-og-struktur) og våre [retningslinjer for språk](https://design.nav.no/resources/language)
|
||||
8. Lenker: Skriv lenketekster slik at det er tydelig hvor lenken går. Ikke åpne lenker i ny fane/vindu. Se [retningslinjer for gode lenker](https://design.nav.no/components/lenke/accessibility).
|
||||
9. Lag gode og [beskrivende feilmeldinger (Difi)](https://uu.difi.no/krav-og-regelverk/losningsforslag-web/feilhandtering) og pass på at meldingene er i tråd med Designsystemet.
|
||||
10. Test med reelle brukere, også med personer som bruker hjelpemidler. Bruk Sanselaben i Sannergata (rom 5006).
|
||||
9. Lag gode og [beskrivende feilmeldinger (DigDir)](https://uu.difi.no/krav-og-regelverk/losningsforslag-web/feilhandtering) og pass på at meldingene er i tråd med Designsystemet.
|
||||
10. Test med reelle brukere, også med personer som bruker hjelpemidler. Bruk Sanselaben i Fyrstikkaleen.
|
||||
|
||||
### For utviklere
|
||||
|
||||
[Flere tips for utviklere(https://navikt.github.io/uu/hvordan-faa-det-til/tips-etter-rolle/Utvikler/) på uu-sidene.
|
||||
|
||||
1. Bruk [komponentene våre](/components).
|
||||
2. Sjekk at koderekkefølgen gjenspeiler den visuelle rekkefølgen.
|
||||
3. Test at alt kan betjenes med tastatur og at fokusmarkering flyttes i forventet rekkefølge.
|
||||
4. Bruk de [semantiske egenskapene i HTML (MDN)](https://developer.mozilla.org/en-US/docs/Glossary/Semantics).
|
||||
5. Sjekk at du har med [hopp-lenkene (W3)](https://www.w3.org/TR/WCAG21/#bypass-blocks) og at de fungerer.
|
||||
6. Sjekk at du har med [riktig <title> (W3)](https://www.w3.org/TR/WCAG21/#page-titled). Formen skal være `[hovedinnhold/overskrift på siden] - www.nav.no`
|
||||
7. Pass på riktig [overskriftshierarki (Difi)](https://uu.difi.no/krav-og-regelverk/losningsforslag-web/tekst-og-struktur)
|
||||
7. Pass på riktig [overskriftshierarki (DigDir)](https://uu.difi.no/krav-og-regelverk/losningsforslag-web/tekst-og-struktur)
|
||||
8. Sjekk at HTMLen din [validerer (W3)](https://validator.w3.org).
|
||||
9. Bruk [WAI-Aria (W3)](https://www.w3.org/WAI/standards-guidelines/aria) der standard HTML ikke er tilstrekkelig, for eksempel på [feilmeldinger](https://www.w3.org/TR/WCAG21/#status-messages)
|
||||
10. Bruk automatiserte testverktøy (som f.eks. [WAVE](https://wave.webaim.org) eller [Sortsite](https://www.powermapper.com/products/sortsite/)) aktivt og ofte.
|
||||
11. Test med skjermleser på egen hånd. Med for eksempel [ChromeVox](https://www.chromevox.com), [VoiceOver på Mac](https://webaim.org/articles/voiceover/), eller [TalkBack på Android](https://support.google.com/accessibility/android/answer/6283677?hl=en) er dette enklere enn du tror.
|
||||
12. Hvis mulig, bruk [NAV-ally](https://www.npmjs.com/package/nav-ally) i utviklingsprosessen.
|
||||
13. Test med reelle brukere, også med personer som bruker hjelpemidler. Bruk Sanselaben i Sannergata (rom 5006).
|
||||
12. Hvis mulig, bruk [automatisert testing](https://navikt.github.io/uu/hvordan-faa-det-til/UU-testing/automatisert-testing/) i utviklingsprosessen. Husk at dette dekker maks 20-30 prosent av potensielle feil.
|
||||
13. Test med reelle brukere, også med personer som bruker hjelpemidler. Bruk Sanselaben i Fyrstikkaleen.
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user