mirror of
https://github.com/navikt/aksel.git
synced 2026-10-15 18:22:02 +00:00
Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
064bf66c0f | ||
|
|
a8b66dfec9 | ||
|
|
8f99813590 | ||
|
|
21b078a9a4 | ||
|
|
e89d3b27b2 | ||
|
|
ddd2fd2e08 | ||
|
|
a7c326aa43 | ||
|
|
0b89387bae | ||
|
|
8a58d75efb | ||
|
|
bf18ffcb0c | ||
|
|
ad3c88d542 | ||
|
|
eb90447bb7 | ||
|
|
cb429868c1 | ||
|
|
e09f478a09 | ||
|
|
00259c629b | ||
|
|
6124812ba8 | ||
|
|
5f6bd160d6 | ||
|
|
b876ffb607 | ||
|
|
6886cf7a67 |
No files matched your search
@@ -1,3 +1,4 @@
|
||||
@charset "UTF-8";
|
||||
@import "baseline/baseline.css";
|
||||
@import "typography.css";
|
||||
@import "grid.css";
|
||||
@@ -13,8 +14,10 @@
|
||||
@import "loader.css";
|
||||
@import "menu.css";
|
||||
@import "modal.css";
|
||||
@import "pagination.css";
|
||||
@import "popover.css";
|
||||
@import "tag.css";
|
||||
@import "toggle-group.css";
|
||||
@import "panel.css";
|
||||
@import "link-panel.css";
|
||||
@import "speech-bubble.css";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-css",
|
||||
"version": "0.15.3",
|
||||
"version": "0.15.7",
|
||||
"private": false,
|
||||
"description": "Css for NAV Designsystem components",
|
||||
"author": "NAV Designsystem team",
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
:root {
|
||||
--navds-pagination-color-background-hover: var(
|
||||
--navds-semantic-color-canvas-background
|
||||
);
|
||||
--navds-pagination-color-background-active: var(
|
||||
--navds-semantic-color-interaction-primary-selected
|
||||
);
|
||||
--navds-pagination-color-text-active: var(
|
||||
--navds-semantic-color-text-inverted
|
||||
);
|
||||
--navds-pagination-color-background-selected: var(
|
||||
--navds-semantic-color-interaction-primary
|
||||
);
|
||||
--navds-pagination-color-text-selected: var(
|
||||
--navds-semantic-color-text-inverted
|
||||
);
|
||||
}
|
||||
|
||||
.navds-pagination {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.navds-pagination__list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
gap: var(--navds-spacing-3);
|
||||
}
|
||||
|
||||
.navds-pagination__ellipsis {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--navds-spacing-8);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.navds-pagination--small .navds-pagination__ellipsis {
|
||||
width: var(--navds-spacing-6);
|
||||
}
|
||||
|
||||
.navds-pagination__item {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
background: none;
|
||||
border-radius: 2px;
|
||||
padding: var(--navds-spacing-1) var(--navds-spacing-3);
|
||||
}
|
||||
|
||||
.navds-pagination--small .navds-pagination__item {
|
||||
padding: 2px var(--navds-spacing-2);
|
||||
}
|
||||
|
||||
.navds-pagination__item:hover {
|
||||
background-color: var(--navds-pagination-color-background-hover);
|
||||
}
|
||||
|
||||
.navds-pagination__item:active {
|
||||
background-color: var(--navds-pagination-color-background-active);
|
||||
color: var(--navds-pagination-color-text-active);
|
||||
}
|
||||
|
||||
.navds-pagination__item:focus {
|
||||
box-shadow: inset 0 0 0 2px var(--navds-semantic-color-focus),
|
||||
inset 0 0 0 3px white;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.navds-pagination__item[aria-current="true"] {
|
||||
background-color: var(--navds-pagination-color-background-selected);
|
||||
color: var(--navds-pagination-color-text-selected);
|
||||
font-weight: var(--navds-font-weight-bold);
|
||||
}
|
||||
|
||||
.navds-pagination__prev-next {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
text-decoration: underline;
|
||||
color: var(--navds-semantic-color-link);
|
||||
align-items: center;
|
||||
gap: var(--navds-spacing-1);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.navds-pagination__prev-next:focus {
|
||||
outline: none;
|
||||
box-shadow: inset 0 0 0 2px var(--navds-semantic-color-focus);
|
||||
}
|
||||
|
||||
.navds-pagination__prev-next:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.navds-pagination__prev-next-icon {
|
||||
margin: 7px;
|
||||
}
|
||||
|
||||
.navds-pagination--small .navds-pagination__prev-next-icon {
|
||||
margin: var(--navds-spacing-1);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.navds-pagination__next-text {
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
|
||||
.navds-pagination__prev-text {
|
||||
padding-right: 0.75rem;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
:root {
|
||||
--navds-toggle-group-color-background: var(
|
||||
--navds-semantic-color-component-background-light
|
||||
);
|
||||
--navds-toggle-group-color-text: var(--navds-semantic-color-text);
|
||||
--navds-toggle-group-color-border: var(--navds-semantic-color-divider);
|
||||
--navds-toggle-group-color-background-hover: var(
|
||||
--navds-semantic-color-interaction-primary-hover-subtle
|
||||
);
|
||||
--navds-toggle-group-color-text-hover: var(
|
||||
--navds-semantic-color-interaction-primary
|
||||
);
|
||||
--navds-toggle-group-color-background-pressed: var(
|
||||
--navds-semantic-color-interaction-primary-selected
|
||||
);
|
||||
--navds-toggle-group-color-text-pressed: var(
|
||||
--navds-semantic-color-text-inverted
|
||||
);
|
||||
}
|
||||
|
||||
.navds-toggle-group {
|
||||
border-radius: 7px;
|
||||
background-color: var(--navds-toggle-group-color-background);
|
||||
box-shadow: inset 0 0 0 1px var(--navds-toggle-group-color-border);
|
||||
padding: calc(var(--navds-spacing-1) + 1px);
|
||||
gap: var(--navds-spacing-1);
|
||||
display: inline-grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: 1fr;
|
||||
}
|
||||
|
||||
.navds-toggle-group__button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--navds-spacing-3);
|
||||
min-height: 48px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
background-color: var(--navds-toggle-group-color-background);
|
||||
color: var(--navds-toggle-group-color-text);
|
||||
border-radius: 2px;
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
.navds-toggle-group__button:hover {
|
||||
background-color: var(--navds-toggle-group-color-background-hover);
|
||||
color: var(--navds-toggle-group-color-text-hover);
|
||||
}
|
||||
|
||||
.navds-toggle-group__button:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 1px var(--navds-toggle-group-color-background),
|
||||
0 0 0 4px var(--navds-semantic-color-focus);
|
||||
}
|
||||
|
||||
.navds-toggle-group__button:focus:hover[aria-pressed="false"] {
|
||||
box-shadow: 0 0 0 1px var(--navds-toggle-group-color-background-hover),
|
||||
0 0 0 4px var(--navds-semantic-color-focus);
|
||||
}
|
||||
|
||||
.navds-toggle-group__button-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--navds-spacing-2);
|
||||
}
|
||||
|
||||
.navds-toggle-group__button-inner > * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.navds-toggle-group__button[aria-pressed="true"] {
|
||||
background-color: var(--navds-toggle-group-color-background-pressed);
|
||||
color: var(--navds-toggle-group-color-text-pressed);
|
||||
}
|
||||
|
||||
.navds-toggle-group--small > .navds-toggle-group__button {
|
||||
padding: 0.375rem;
|
||||
min-height: 32px;
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
.navds-toggle-group__button > .navds-toggle-group__button-inner > svg {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.navds-toggle-group--small
|
||||
> .navds-toggle-group__button
|
||||
> .navds-toggle-group__button-inner
|
||||
> svg {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.navds-toggle-group__label {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-react",
|
||||
"version": "0.16.5",
|
||||
"version": "0.16.12",
|
||||
"private": false,
|
||||
"description": "NAV designsystem react components",
|
||||
"author": "NAV Designsystem team",
|
||||
@@ -37,6 +37,7 @@
|
||||
"@material-ui/core": "^4.12.3",
|
||||
"@navikt/ds-icons": "^0.8.4",
|
||||
"@popperjs/core": "^2.10.1",
|
||||
"@radix-ui/react-toggle-group": "^0.1.4",
|
||||
"classnames": "^2.2.6",
|
||||
"react-collapse": "^5.1.0",
|
||||
"react-merge-refs": "^1.1.0",
|
||||
|
||||
@@ -36,13 +36,13 @@ export interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
const Icon = ({ variant, ...props }) => {
|
||||
switch (variant) {
|
||||
case "error":
|
||||
return <ErrorFilled {...props} />;
|
||||
return <ErrorFilled title="Feil" {...props} />;
|
||||
case "warning":
|
||||
return <WarningFilled {...props} />;
|
||||
return <WarningFilled title="Advarsel" {...props} />;
|
||||
case "info":
|
||||
return <InformationFilled {...props} />;
|
||||
return <InformationFilled title="Informasjon" {...props} />;
|
||||
case "success":
|
||||
return <SuccessFilled {...props} />;
|
||||
return <SuccessFilled title="Suksess" {...props} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -76,11 +76,7 @@ const Alert = forwardRef<HTMLDivElement, AlertProps>(
|
||||
{ "navds-alert--full-width": fullWidth, "navds-alert--inline": inline }
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
title={`${variant}-ikon`}
|
||||
variant={variant}
|
||||
className="navds-alert__icon"
|
||||
/>
|
||||
<Icon variant={variant} className="navds-alert__icon" />
|
||||
<BodyLong as="div" size={size} className="navds-alert__wrapper">
|
||||
{children}
|
||||
</BodyLong>
|
||||
|
||||
@@ -5,6 +5,9 @@ import { Success } from "@navikt/ds-icons";
|
||||
export default {
|
||||
title: "ds-react/button",
|
||||
component: Button,
|
||||
parameters: {
|
||||
chromatic: { delay: 300 },
|
||||
},
|
||||
};
|
||||
|
||||
const Section = ({ children }) => (
|
||||
|
||||
@@ -10,11 +10,13 @@ export * from "./link-panel";
|
||||
export * from "./loader";
|
||||
export * from "./menu";
|
||||
export * from "./modal";
|
||||
export * from "./pagination";
|
||||
export * from "./panel";
|
||||
export * from "./popover";
|
||||
export * from "./speech-bubble";
|
||||
export * from "./step-indicator";
|
||||
export * from "./tag";
|
||||
export * from "./toggle-group";
|
||||
export * from "./table";
|
||||
export * from "./typography";
|
||||
export * from "./util";
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
import React from "react";
|
||||
import cl from "classnames";
|
||||
import { Back, Next } from "@navikt/ds-icons";
|
||||
import { BodyShort } from "..";
|
||||
|
||||
interface PaginationProps extends React.HTMLAttributes<HTMLElement> {
|
||||
/**
|
||||
* Current page
|
||||
*/
|
||||
page: number;
|
||||
/**
|
||||
* Number of always visible pages before and after the current page.
|
||||
* @default 1
|
||||
*/
|
||||
siblingCount?: number;
|
||||
/**
|
||||
* Number of always visible pages at the beginning and end.
|
||||
* @default 1
|
||||
*/
|
||||
boundaryCount?: number;
|
||||
/**
|
||||
* Callback when current page changes
|
||||
*/
|
||||
onPageChange: (page: number) => void;
|
||||
/**
|
||||
* Total number of pages
|
||||
*/
|
||||
count: number;
|
||||
/**
|
||||
* Changes padding, height and font-size
|
||||
* @default "medium"
|
||||
*/
|
||||
size?: "medium" | "small";
|
||||
/**
|
||||
* Display text alongside "previous" and "next" icons
|
||||
* @default false
|
||||
*/
|
||||
prevNextTexts?: boolean;
|
||||
}
|
||||
|
||||
export const getSteps = ({
|
||||
page,
|
||||
count,
|
||||
boundaryCount = 1,
|
||||
siblingCount = 1,
|
||||
}) => {
|
||||
const range = (start: number, end: number) =>
|
||||
Array.from({ length: end - start + 1 }, (_, i) => start + i);
|
||||
|
||||
if (count <= (boundaryCount + siblingCount) * 2 + 3) return range(1, count);
|
||||
|
||||
const startPages = range(1, boundaryCount);
|
||||
const endPages = range(count - boundaryCount + 1, count);
|
||||
|
||||
const siblingsStart = Math.max(
|
||||
Math.min(page - siblingCount, count - boundaryCount - siblingCount * 2 - 1),
|
||||
boundaryCount + 2
|
||||
);
|
||||
const siblingsEnd = siblingsStart + siblingCount * 2;
|
||||
|
||||
return [
|
||||
...startPages,
|
||||
siblingsStart - (startPages[startPages.length - 1] ?? 0) === 2
|
||||
? siblingsStart - 1
|
||||
: "ellipsis",
|
||||
...range(siblingsStart, siblingsEnd),
|
||||
(endPages[0] ?? count + 1) - siblingsEnd === 2
|
||||
? siblingsEnd + 1
|
||||
: "ellipsis",
|
||||
...endPages,
|
||||
];
|
||||
};
|
||||
|
||||
const Pagination = ({
|
||||
page,
|
||||
onPageChange,
|
||||
count,
|
||||
boundaryCount = 1,
|
||||
siblingCount = 1,
|
||||
className,
|
||||
size = "medium",
|
||||
prevNextTexts = false,
|
||||
}: PaginationProps) => {
|
||||
if (page < 1) {
|
||||
console.error("page cannot be less than 1");
|
||||
return null;
|
||||
}
|
||||
if (count < 1) {
|
||||
console.error("count cannot be less than 1");
|
||||
return null;
|
||||
}
|
||||
if (boundaryCount < 0) {
|
||||
console.error("boundaryCount cannot be less than 0");
|
||||
return null;
|
||||
}
|
||||
if (siblingCount < 0) {
|
||||
console.error("siblingCount cannot be less than 0");
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={cl("navds-pagination", `navds-pagination--${size}`, className)}
|
||||
>
|
||||
{prevNextTexts && (
|
||||
<button
|
||||
className="navds-pagination__prev-next"
|
||||
disabled={page === 1}
|
||||
onClick={() => onPageChange(page - 1)}
|
||||
>
|
||||
<Back
|
||||
className="navds-pagination__prev-next-icon"
|
||||
role="presentation"
|
||||
/>
|
||||
<BodyShort size={size} className="navds-pagination__prev-text">
|
||||
Tilbake
|
||||
</BodyShort>
|
||||
</button>
|
||||
)}
|
||||
<ul className="navds-pagination__list">
|
||||
{!prevNextTexts && (
|
||||
<li>
|
||||
<button
|
||||
className="navds-pagination__prev-next"
|
||||
disabled={page === 1}
|
||||
onClick={() => onPageChange(page - 1)}
|
||||
>
|
||||
<Back
|
||||
className="navds-pagination__prev-next-icon"
|
||||
title="Tilbake"
|
||||
/>
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
{getSteps({ page, count, siblingCount, boundaryCount }).map(
|
||||
(step, i) => {
|
||||
const n = Number(step);
|
||||
return isNaN(n) ? (
|
||||
<li className="navds-pagination__ellipsis" key={`${step}${i}`}>
|
||||
<BodyShort size={size}>...</BodyShort>
|
||||
</li>
|
||||
) : (
|
||||
<li key={step}>
|
||||
<BodyShort
|
||||
size={size}
|
||||
as="button"
|
||||
className="navds-pagination__item"
|
||||
onClick={() => onPageChange(n)}
|
||||
aria-current={page === n ? true : undefined}
|
||||
>
|
||||
{n}
|
||||
</BodyShort>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
)}
|
||||
{!prevNextTexts && (
|
||||
<li>
|
||||
<button
|
||||
className="navds-pagination__prev-next"
|
||||
disabled={page === count}
|
||||
onClick={() => onPageChange(page + 1)}
|
||||
>
|
||||
<Next
|
||||
className="navds-pagination__prev-next-icon"
|
||||
title="Neste"
|
||||
/>
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
{prevNextTexts && (
|
||||
<button
|
||||
className="navds-pagination__prev-next"
|
||||
disabled={page === count}
|
||||
onClick={() => onPageChange(page + 1)}
|
||||
>
|
||||
<BodyShort size={size} className="navds-pagination__next-text">
|
||||
Neste
|
||||
</BodyShort>
|
||||
<Next
|
||||
className="navds-pagination__prev-next-icon"
|
||||
role="presentation"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
export default Pagination;
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as Pagination } from "./Pagination";
|
||||
export * from "./Pagination";
|
||||
@@ -0,0 +1,37 @@
|
||||
import React, { useState } from "react";
|
||||
import Pagination from "./Pagination";
|
||||
|
||||
export default {
|
||||
title: "ds-react/pagination",
|
||||
component: Pagination,
|
||||
};
|
||||
|
||||
export const All = (props) => {
|
||||
const [page, setPage] = useState(props.page);
|
||||
return (
|
||||
<div>
|
||||
<h2>Pagination</h2>
|
||||
<Pagination {...props} page={page} onPageChange={setPage} />
|
||||
|
||||
<h2>Small</h2>
|
||||
<Pagination size="small" {...props} page={page} onPageChange={setPage} />
|
||||
|
||||
<h2>prevNextTexts</h2>
|
||||
<Pagination prevNextTexts {...props} page={page} onPageChange={setPage} />
|
||||
<h3>prevNextTexts small</h3>
|
||||
<Pagination
|
||||
prevNextTexts
|
||||
size="small"
|
||||
{...props}
|
||||
page={page}
|
||||
onPageChange={setPage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
All.args = {
|
||||
page: 1,
|
||||
count: 8,
|
||||
siblingCount: 1,
|
||||
boundaryCount: 1,
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
import faker from "faker";
|
||||
import { getSteps } from "./Pagination";
|
||||
|
||||
describe("getSteps", () => {
|
||||
it("lists all pages when count is <= 7", () => {
|
||||
const count = faker.datatype.number({ min: 1, max: 7 });
|
||||
expect(getSteps({ page: 1, count })).toEqual(
|
||||
Array.from({ length: count }, (_, i) => i + 1)
|
||||
);
|
||||
});
|
||||
|
||||
it("has an end ellipsis when count >= 8", () => {
|
||||
const count = faker.datatype.number({ min: 8 });
|
||||
const page = faker.datatype.number({ min: 1, max: 4 });
|
||||
expect(
|
||||
getSteps({
|
||||
page,
|
||||
count,
|
||||
})
|
||||
).toEqual([1, 2, 3, 4, 5, "ellipsis", count]);
|
||||
});
|
||||
|
||||
it("has a start ellipsis when count - page >= 3", () => {
|
||||
const count = faker.datatype.number({ min: 8 });
|
||||
const page = faker.datatype.number({ min: count - 3, max: count });
|
||||
expect(
|
||||
getSteps({
|
||||
page,
|
||||
count,
|
||||
})
|
||||
).toEqual([
|
||||
1,
|
||||
"ellipsis",
|
||||
count - 4,
|
||||
count - 3,
|
||||
count - 2,
|
||||
count - 1,
|
||||
count,
|
||||
]);
|
||||
});
|
||||
|
||||
it("has start & end ellipsis when count is high", () => {
|
||||
const count = faker.datatype.number({ min: 9 });
|
||||
const page = faker.datatype.number({ min: 5, max: count - 4 });
|
||||
expect(
|
||||
getSteps({
|
||||
page,
|
||||
count,
|
||||
})
|
||||
).toEqual([1, "ellipsis", page - 1, page, page + 1, "ellipsis", count]);
|
||||
});
|
||||
|
||||
it("can have a reduced siblingCount", () => {
|
||||
const count = faker.datatype.number({ min: 7 });
|
||||
const page = faker.datatype.number({ min: 4, max: count - 3 });
|
||||
expect(
|
||||
getSteps({
|
||||
page,
|
||||
count,
|
||||
siblingCount: 0,
|
||||
})
|
||||
).toEqual([1, "ellipsis", page, "ellipsis", count]);
|
||||
});
|
||||
|
||||
it("can have an increased siblingCount", () => {
|
||||
const count = faker.datatype.number({ min: 11 });
|
||||
const page = faker.datatype.number({ min: 6, max: count - 5 });
|
||||
expect(
|
||||
getSteps({
|
||||
page,
|
||||
count,
|
||||
siblingCount: 2,
|
||||
})
|
||||
).toEqual([
|
||||
1,
|
||||
"ellipsis",
|
||||
page - 2,
|
||||
page - 1,
|
||||
page,
|
||||
page + 1,
|
||||
page + 2,
|
||||
"ellipsis",
|
||||
count,
|
||||
]);
|
||||
});
|
||||
|
||||
it("can have an reduced boundaryCount", () => {
|
||||
const count = faker.datatype.number({ min: 7 });
|
||||
const page = faker.datatype.number({ min: 4, max: count - 3 });
|
||||
expect(
|
||||
getSteps({
|
||||
page,
|
||||
count,
|
||||
boundaryCount: 0,
|
||||
})
|
||||
).toEqual(["ellipsis", page - 1, page, page + 1, "ellipsis"]);
|
||||
});
|
||||
|
||||
it("can have an increased boundaryCount", () => {
|
||||
const count = faker.datatype.number({ min: 11 });
|
||||
const page = faker.datatype.number({ min: 6, max: count - 5 });
|
||||
expect(
|
||||
getSteps({
|
||||
page,
|
||||
count,
|
||||
boundaryCount: 2,
|
||||
})
|
||||
).toEqual([
|
||||
1,
|
||||
2,
|
||||
"ellipsis",
|
||||
page - 1,
|
||||
page,
|
||||
page + 1,
|
||||
"ellipsis",
|
||||
count - 1,
|
||||
count,
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,7 @@ import cl from "classnames";
|
||||
import { BodyShort } from "..";
|
||||
import { TableContext } from ".";
|
||||
|
||||
interface DataCellProps extends React.HTMLAttributes<HTMLTableCellElement> {}
|
||||
interface DataCellProps extends React.TdHTMLAttributes<HTMLTableCellElement> {}
|
||||
|
||||
export interface DataCellType
|
||||
extends React.ForwardRefExoticComponent<
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { forwardRef, useContext } from "react";
|
||||
import cl from "classnames";
|
||||
import { Label, TableContext } from "..";
|
||||
|
||||
interface HeaderCellProps extends React.HTMLAttributes<HTMLTableCellElement> {
|
||||
interface HeaderCellProps extends React.ThHTMLAttributes<HTMLTableCellElement> {
|
||||
scope?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ import Row, { RowType } from "./Row";
|
||||
import HeaderCell, { HeaderCellType } from "./HeaderCell";
|
||||
import DataCell, { DataCellType } from "./DataCell";
|
||||
|
||||
export interface TableProps extends React.HTMLAttributes<HTMLTableElement> {
|
||||
export interface TableProps
|
||||
extends React.TableHTMLAttributes<HTMLTableElement> {
|
||||
/**
|
||||
* Changes padding
|
||||
* @default "medium"
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import React, { useState } from "react";
|
||||
import { Attachment, Hamburger, Star, System } from "@navikt/ds-icons";
|
||||
import { ToggleGroup } from "../index";
|
||||
import { Meta } from "@storybook/react/types-6-0";
|
||||
export default {
|
||||
title: "ds-react/toggle-group",
|
||||
component: ToggleGroup,
|
||||
parameters: {
|
||||
chromatic: { disable: true },
|
||||
},
|
||||
} as Meta;
|
||||
|
||||
const Items = (icon?: boolean, both?: boolean) => (
|
||||
<>
|
||||
<ToggleGroup.Item value="first">
|
||||
{both ? (
|
||||
<>
|
||||
<Hamburger /> First
|
||||
</>
|
||||
) : (
|
||||
<>{icon ? <Hamburger /> : "First"}</>
|
||||
)}
|
||||
</ToggleGroup.Item>
|
||||
<ToggleGroup.Item value="second">
|
||||
{both ? (
|
||||
<>
|
||||
<Star /> Second more txt
|
||||
</>
|
||||
) : (
|
||||
<>{icon ? <Star /> : "Second more txt"}</>
|
||||
)}
|
||||
</ToggleGroup.Item>
|
||||
<ToggleGroup.Item value="third">
|
||||
{both ? (
|
||||
<>
|
||||
<Attachment /> Thrid
|
||||
</>
|
||||
) : (
|
||||
<>{icon ? <Attachment /> : "Third"}</>
|
||||
)}
|
||||
</ToggleGroup.Item>
|
||||
<ToggleGroup.Item value="fourth">
|
||||
{both ? (
|
||||
<>
|
||||
<System /> Fourth
|
||||
</>
|
||||
) : (
|
||||
<>{icon ? <System /> : "Fourth"}</>
|
||||
)}
|
||||
</ToggleGroup.Item>
|
||||
</>
|
||||
);
|
||||
|
||||
export const All = () => {
|
||||
const [activeValue, setActiveValue] = useState("first");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>ToggleGroup</h2>
|
||||
<h3>{activeValue}</h3>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
|
||||
<ToggleGroup value={activeValue} onChange={setActiveValue}>
|
||||
{Items()}
|
||||
</ToggleGroup>
|
||||
<ToggleGroup value={activeValue} onChange={setActiveValue}>
|
||||
{Items(true)}
|
||||
</ToggleGroup>
|
||||
<ToggleGroup value={activeValue} onChange={setActiveValue}>
|
||||
{Items(true, true)}
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
<h2>ToggleGroup Small</h2>
|
||||
<h3>{activeValue}</h3>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
|
||||
<ToggleGroup size="small" value={activeValue} onChange={setActiveValue}>
|
||||
{Items()}
|
||||
</ToggleGroup>
|
||||
<ToggleGroup size="small" value={activeValue} onChange={setActiveValue}>
|
||||
{Items(true)}
|
||||
</ToggleGroup>
|
||||
<ToggleGroup size="small" value={activeValue} onChange={setActiveValue}>
|
||||
{Items(true, true)}
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
<h2>ToggleGroup label</h2>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
|
||||
<ToggleGroup
|
||||
label="Label msg"
|
||||
value={activeValue}
|
||||
onChange={setActiveValue}
|
||||
>
|
||||
{Items()}
|
||||
</ToggleGroup>
|
||||
<ToggleGroup
|
||||
label="Label msg"
|
||||
value={activeValue}
|
||||
onChange={setActiveValue}
|
||||
aria-describedby="demo-id"
|
||||
>
|
||||
{Items()}
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
|
||||
<h2>Uncontrolled</h2>
|
||||
<ToggleGroup onChange={setActiveValue} defaultValue="second">
|
||||
{Items()}
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,136 @@
|
||||
import cl from "classnames";
|
||||
import React, {
|
||||
createContext,
|
||||
forwardRef,
|
||||
HTMLAttributes,
|
||||
useState,
|
||||
} from "react";
|
||||
import ToggleItem, { ToggleItemType } from "./ToggleItem";
|
||||
import * as RadixToggleGroup from "@radix-ui/react-toggle-group";
|
||||
import { Label, useId } from "..";
|
||||
|
||||
export interface ToggleGroupProps
|
||||
extends Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "dir"> {
|
||||
/**
|
||||
* Toggles.Item elements
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Changes padding and font-size
|
||||
* @default "medium"
|
||||
*/
|
||||
size?: "medium" | "small";
|
||||
/**
|
||||
* Controlled selected value
|
||||
*/
|
||||
value?: string;
|
||||
/**
|
||||
* If not controlled, a default-value needs to be set
|
||||
*/
|
||||
defaultValue?: string;
|
||||
/**
|
||||
* Returns elements that wants to be active
|
||||
*/
|
||||
onChange: (value: string) => void;
|
||||
/**
|
||||
* Label describing ToggleGroup
|
||||
*/
|
||||
label?: React.ReactNode;
|
||||
}
|
||||
|
||||
interface ToggleGroupComponent
|
||||
extends React.ForwardRefExoticComponent<
|
||||
ToggleGroupProps & React.RefAttributes<HTMLDivElement>
|
||||
> {
|
||||
Item: ToggleItemType;
|
||||
}
|
||||
|
||||
interface ToggleContextProps {
|
||||
size: "medium" | "small";
|
||||
}
|
||||
|
||||
export const ToggleGroupContext = createContext<ToggleContextProps | null>(
|
||||
null
|
||||
);
|
||||
|
||||
const ToggleGroup = forwardRef<HTMLDivElement, ToggleGroupProps>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
children,
|
||||
onChange,
|
||||
size = "medium",
|
||||
label,
|
||||
value,
|
||||
defaultValue,
|
||||
id,
|
||||
"aria-describedby": desc,
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const [groupValue, setGroupValue] = useState(defaultValue);
|
||||
const labelId = `toggle-group-label-${useId()}`;
|
||||
|
||||
const handleValueChange = (v: string) => {
|
||||
if (v !== "") {
|
||||
setGroupValue(v);
|
||||
onChange?.(v);
|
||||
}
|
||||
};
|
||||
|
||||
if (!value && !defaultValue) {
|
||||
console.error("ToggleGroup without value/defaultvalue is not allowed");
|
||||
}
|
||||
|
||||
const describeBy = cl({
|
||||
[desc ?? ""]: !!desc,
|
||||
[labelId]: !!label,
|
||||
});
|
||||
|
||||
if (!value && !defaultValue) {
|
||||
console.error("ToggleGroup needs either a value or defaultValue");
|
||||
}
|
||||
|
||||
return (
|
||||
<ToggleGroupContext.Provider
|
||||
value={{
|
||||
size,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
{label && (
|
||||
<Label
|
||||
size={size}
|
||||
className="navds-toggle-group__label"
|
||||
id={labelId}
|
||||
>
|
||||
{label}
|
||||
</Label>
|
||||
)}
|
||||
<RadixToggleGroup.Root
|
||||
{...rest}
|
||||
onValueChange={handleValueChange}
|
||||
value={value ?? groupValue}
|
||||
defaultValue={defaultValue}
|
||||
ref={ref}
|
||||
className={cl(
|
||||
"navds-toggle-group",
|
||||
className,
|
||||
`navds-toggle-group--${size}`
|
||||
)}
|
||||
{...(describeBy && { "aria-describedby": describeBy })}
|
||||
role="toolbar"
|
||||
type="single"
|
||||
>
|
||||
{children}
|
||||
</RadixToggleGroup.Root>
|
||||
</div>
|
||||
</ToggleGroupContext.Provider>
|
||||
);
|
||||
}
|
||||
) as ToggleGroupComponent;
|
||||
|
||||
ToggleGroup.Item = ToggleItem;
|
||||
|
||||
export default ToggleGroup;
|
||||
@@ -0,0 +1,45 @@
|
||||
import React, { forwardRef, useContext } from "react";
|
||||
import cl from "classnames";
|
||||
import { Label } from "..";
|
||||
import * as RadixToggleGroup from "@radix-ui/react-toggle-group";
|
||||
import { ToggleGroupContext } from "./ToggleGroup";
|
||||
|
||||
export interface ToggleItemProps
|
||||
extends React.HTMLAttributes<HTMLButtonElement> {
|
||||
/**
|
||||
* Content
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Value for state-handling
|
||||
*/
|
||||
value: string;
|
||||
}
|
||||
|
||||
export type ToggleItemType = React.ForwardRefExoticComponent<
|
||||
ToggleItemProps & React.RefAttributes<HTMLButtonElement>
|
||||
>;
|
||||
|
||||
const ToggleItem = forwardRef<HTMLButtonElement, ToggleItemProps>(
|
||||
({ className, children, ...rest }, ref) => {
|
||||
const context = useContext(ToggleGroupContext);
|
||||
|
||||
return (
|
||||
<RadixToggleGroup.Item
|
||||
{...rest}
|
||||
ref={ref}
|
||||
className={cl("navds-toggle-group__button", className)}
|
||||
>
|
||||
<Label
|
||||
as="span"
|
||||
className="navds-toggle-group__button-inner"
|
||||
size={context?.size}
|
||||
>
|
||||
{children}
|
||||
</Label>
|
||||
</RadixToggleGroup.Item>
|
||||
);
|
||||
}
|
||||
) as ToggleItemType;
|
||||
|
||||
export default ToggleItem;
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as ToggleGroup } from "./ToggleGroup";
|
||||
export { ToggleGroupProps } from "./ToggleGroup";
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-react-internal",
|
||||
"version": "0.11.5",
|
||||
"version": "0.11.12",
|
||||
"private": false,
|
||||
"description": "NAV internal designsystem react components",
|
||||
"author": "NAV Designsystem team",
|
||||
@@ -34,7 +34,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@navikt/ds-icons": "^0.8.4",
|
||||
"@navikt/ds-react": "^0.16.5",
|
||||
"@navikt/ds-react": "^0.16.12",
|
||||
"@popperjs/core": "^2.10.1",
|
||||
"classnames": "^2.3.1",
|
||||
"copy-to-clipboard": "^3.3.1",
|
||||
|
||||
@@ -1107,6 +1107,13 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.13.10":
|
||||
version "7.17.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.0.tgz#b8d142fc0f7664fb3d9b5833fd40dcbab89276c0"
|
||||
integrity sha512-etcO/ohMNaNA2UBdaXBBSX/3aEzFMRrVfaPv8Ptc0k+cWpWW0QFiGZ2XnVqQZI1Cf734LbPGmqBKWESfW4x/dQ==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/template@^7.12.7", "@babel/template@^7.16.0", "@babel/template@^7.3.3":
|
||||
version "7.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6"
|
||||
@@ -2547,6 +2554,122 @@
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.0.tgz#6734f8ebc106a0860dff7f92bf90df193f0935d7"
|
||||
integrity sha512-zrsUxjLOKAzdewIDRWy9nsV1GQsKBCWaGwsZQlCgr6/q+vjyZhFgqedLfFBuI9anTPEUT4APq9Mu0SZBTzIcGQ==
|
||||
|
||||
"@radix-ui/primitive@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-0.1.0.tgz#6206b97d379994f0d1929809db035733b337e543"
|
||||
integrity sha512-tqxZKybwN5Fa3VzZry4G6mXAAb9aAqKmPtnVbZpL0vsBwvOHTBwsjHVPXylocYLwEtBY9SCe665bYnNB515uoA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-collection@0.1.3":
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-0.1.3.tgz#7b584f5db40ce165883b87c383d3bd16c0000d69"
|
||||
integrity sha512-tMBY65l87tj77fMX44EBjm5p8clR6swkcNFr0/dDVdEPC0Vf3fwkv62dezCnZyrRBpkOgZPDOp2kO73hYlCfXw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "0.1.0"
|
||||
"@radix-ui/react-context" "0.1.1"
|
||||
"@radix-ui/react-primitive" "0.1.3"
|
||||
"@radix-ui/react-slot" "0.1.2"
|
||||
|
||||
"@radix-ui/react-compose-refs@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-0.1.0.tgz#cff6e780a0f73778b976acff2c2a5b6551caab95"
|
||||
integrity sha512-eyclbh+b77k+69Dk72q3694OHrn9B3QsoIRx7ywX341U9RK1ThgQjMFZoPtmZNQTksXHLNEiefR8hGVeFyInGg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-context@0.1.1":
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-0.1.1.tgz#06996829ea124d9a1bc1dbe3e51f33588fab0875"
|
||||
integrity sha512-PkyVX1JsLBioeu0jB9WvRpDBBLtLZohVDT3BB5CTSJqActma8S8030P57mWZb4baZifMvN7KKWPAA40UmWKkQg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-id@0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-0.1.4.tgz#4cd6126e6ac8a43ebe6d52948a068b797cc9ad71"
|
||||
integrity sha512-/hq5m/D0ZfJWOS7TLF+G0l08KDRs87LBE46JkAvgKkg1fW4jkucx9At9D9vauIPSbdNmww5kXEp566hMlA8eXA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-layout-effect" "0.1.0"
|
||||
|
||||
"@radix-ui/react-primitive@0.1.3":
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-0.1.3.tgz#585c35ef2ec06bab0ea9e0fc5c916e556661b881"
|
||||
integrity sha512-fcyADaaAx2jdqEDLsTs6aX50S3L1c9K9CC6XMpJpuXFJCU4n9PGTFDZRtY2gAoXXoRCPIBsklCopSmGb6SsDjQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-slot" "0.1.2"
|
||||
|
||||
"@radix-ui/react-roving-focus@0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-0.1.4.tgz#693ca3eaab153feabe37f9d50987d6d2911cf819"
|
||||
integrity sha512-zaixcAxRcWQliUSx6l9rdfJhvcbuY7Tb4Emb7H4DWCTx1kenXH8+n9mwa8gaSIJLLSSSMzBpQATlpFw9xv/bJQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "0.1.0"
|
||||
"@radix-ui/react-collection" "0.1.3"
|
||||
"@radix-ui/react-compose-refs" "0.1.0"
|
||||
"@radix-ui/react-context" "0.1.1"
|
||||
"@radix-ui/react-id" "0.1.4"
|
||||
"@radix-ui/react-primitive" "0.1.3"
|
||||
"@radix-ui/react-use-callback-ref" "0.1.0"
|
||||
"@radix-ui/react-use-controllable-state" "0.1.0"
|
||||
|
||||
"@radix-ui/react-slot@0.1.2":
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-0.1.2.tgz#e6f7ad9caa8ce81cc8d532c854c56f9b8b6307c8"
|
||||
integrity sha512-ADkqfL+agEzEguU3yS26jfB50hRrwf7U4VTwAOZEmi/g+ITcBWe12yM46ueS/UCIMI9Py+gFUaAdxgxafFvY2Q==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "0.1.0"
|
||||
|
||||
"@radix-ui/react-toggle-group@^0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-toggle-group/-/react-toggle-group-0.1.4.tgz#e8204e1c458f0673acb58eb82677c35b867c04f8"
|
||||
integrity sha512-pzs3Cc5I9JKnBdCBCB4xfhbQpWNKY9dYwKVxcYLzGmaUsVPUiAq2AYr/Ht3LebhIxWYHkggjX8EOTeO6ExMr8Q==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "0.1.0"
|
||||
"@radix-ui/react-context" "0.1.1"
|
||||
"@radix-ui/react-primitive" "0.1.3"
|
||||
"@radix-ui/react-roving-focus" "0.1.4"
|
||||
"@radix-ui/react-toggle" "0.1.3"
|
||||
"@radix-ui/react-use-controllable-state" "0.1.0"
|
||||
|
||||
"@radix-ui/react-toggle@0.1.3":
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-toggle/-/react-toggle-0.1.3.tgz#ca471d1b49bdf0c9b821f4c4e39feca70b7cde6a"
|
||||
integrity sha512-ig2LsivpVOxbPoBpNy/6dgB5WNm0mkPMaBBgwo7yufDhlUExdp+bRCqkcWkN9EV42SEbd4etNEIrLAqppfgq3g==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "0.1.0"
|
||||
"@radix-ui/react-primitive" "0.1.3"
|
||||
"@radix-ui/react-use-controllable-state" "0.1.0"
|
||||
|
||||
"@radix-ui/react-use-callback-ref@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-0.1.0.tgz#934b6e123330f5b3a6b116460e6662cbc663493f"
|
||||
integrity sha512-Va041McOFFl+aV+sejvl0BS2aeHx86ND9X/rVFmEFQKTXCp6xgUK0NGUAGcgBlIjnJSbMYPGEk1xKSSlVcN2Aw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-use-controllable-state@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-0.1.0.tgz#4fced164acfc69a4e34fb9d193afdab973a55de1"
|
||||
integrity sha512-zv7CX/PgsRl46a52Tl45TwqwVJdmqnlQEQhaYMz/yBOD2sx2gCkCFSoF/z9mpnYWmS6DTLNTg5lIps3fV6EnXg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-callback-ref" "0.1.0"
|
||||
|
||||
"@radix-ui/react-use-layout-effect@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-0.1.0.tgz#ebf71bd6d2825de8f1fbb984abf2293823f0f223"
|
||||
integrity sha512-+wdeS51Y+E1q1Wmd+1xSSbesZkpVj4jsg0BojCbopWvgq5iBvixw5vgemscdh58ep98BwUbsFYnrywFhV9yrVg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@samverschueren/stream-to-observable@^0.3.0":
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz#a21117b19ee9be70c379ec1877537ef2e1c63301"
|
||||
|
||||
Reference in new issue
Block a user