@navikt/ds-react 0.14.14 → 0.15.0-rc.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/cjs/accordion/AccordionHeader.js +3 -3
  2. package/cjs/button/Button.js +3 -3
  3. package/cjs/form/checkbox/Checkbox.js +12 -1
  4. package/cjs/form/checkbox/useCheckbox.js +1 -1
  5. package/cjs/help-text/HelpText.js +4 -4
  6. package/cjs/index.js +2 -1
  7. package/cjs/{accordion-menu/AccordionMenu.js → menu/Menu.js} +7 -7
  8. package/cjs/{accordion-menu/AccordionMenuCollapsable.js → menu/MenuCollapse.js} +18 -8
  9. package/cjs/{accordion-menu/AccordionMenuItem.js → menu/MenuItem.js} +2 -2
  10. package/cjs/menu/MenuItems.js +23 -0
  11. package/cjs/menu/index.js +8 -0
  12. package/cjs/menu/package.json +6 -0
  13. package/cjs/modal/Modal.js +6 -2
  14. package/cjs/page-header/PageHeader.js +1 -1
  15. package/cjs/pagination/Pagination.js +51 -0
  16. package/cjs/pagination/index.js +19 -0
  17. package/cjs/pagination/package.json +6 -0
  18. package/cjs/table/ColumnHeader.js +58 -0
  19. package/cjs/table/Table.js +15 -2
  20. package/esm/accordion/AccordionHeader.js +3 -3
  21. package/esm/accordion/AccordionHeader.js.map +1 -1
  22. package/esm/button/Button.d.ts +1 -1
  23. package/esm/button/Button.js +3 -3
  24. package/esm/button/Button.js.map +1 -1
  25. package/esm/form/checkbox/Checkbox.d.ts +5 -0
  26. package/esm/form/checkbox/Checkbox.js +12 -1
  27. package/esm/form/checkbox/Checkbox.js.map +1 -1
  28. package/esm/form/checkbox/useCheckbox.d.ts +0 -1
  29. package/esm/form/checkbox/useCheckbox.js +1 -1
  30. package/esm/form/checkbox/useCheckbox.js.map +1 -1
  31. package/esm/help-text/HelpText.js +4 -4
  32. package/esm/help-text/HelpText.js.map +1 -1
  33. package/esm/index.d.ts +2 -1
  34. package/esm/index.js +2 -1
  35. package/esm/index.js.map +1 -1
  36. package/esm/menu/Menu.d.ts +12 -0
  37. package/esm/{accordion-menu/AccordionMenu.js → menu/Menu.js} +8 -8
  38. package/esm/menu/Menu.js.map +1 -0
  39. package/esm/menu/MenuCollapse.d.ts +12 -0
  40. package/esm/menu/MenuCollapse.js +37 -0
  41. package/esm/menu/MenuCollapse.js.map +1 -0
  42. package/esm/menu/MenuItem.d.ts +13 -0
  43. package/esm/{accordion-menu/AccordionMenuItem.js → menu/MenuItem.js} +3 -3
  44. package/esm/menu/MenuItem.js.map +1 -0
  45. package/esm/{accordion-menu → menu}/MenuItems.d.ts +2 -1
  46. package/esm/menu/MenuItems.js +19 -0
  47. package/esm/menu/MenuItems.js.map +1 -0
  48. package/esm/menu/index.d.ts +3 -0
  49. package/esm/menu/index.js +2 -0
  50. package/esm/menu/index.js.map +1 -0
  51. package/esm/modal/Modal.d.ts +3 -0
  52. package/esm/modal/Modal.js +6 -2
  53. package/esm/modal/Modal.js.map +1 -1
  54. package/esm/page-header/PageHeader.js +1 -1
  55. package/esm/page-header/PageHeader.js.map +1 -1
  56. package/esm/pagination/Pagination.d.ts +43 -0
  57. package/esm/pagination/Pagination.js +45 -0
  58. package/esm/pagination/Pagination.js.map +1 -0
  59. package/esm/pagination/index.d.ts +2 -0
  60. package/esm/pagination/index.js +3 -0
  61. package/esm/pagination/index.js.map +1 -0
  62. package/esm/table/ColumnHeader.d.ts +16 -0
  63. package/esm/table/ColumnHeader.js +35 -0
  64. package/esm/table/ColumnHeader.js.map +1 -0
  65. package/esm/table/Table.d.ts +16 -0
  66. package/esm/table/Table.js +15 -2
  67. package/esm/table/Table.js.map +1 -1
  68. package/esm/typography/Heading.d.ts +1 -1
  69. package/package.json +5 -3
  70. package/src/accordion/AccordionHeader.tsx +4 -3
  71. package/src/accordion/accordion.stories.tsx +90 -0
  72. package/src/alert/{stories/alert.stories.tsx → alert.stories.tsx} +2 -2
  73. package/src/button/Button.tsx +7 -3
  74. package/src/button/{stories/button.stories.tsx → button.stories.tsx} +12 -1
  75. package/src/form/checkbox/Checkbox.tsx +18 -1
  76. package/src/form/checkbox/stories/checkbox.stories.tsx +38 -1
  77. package/src/form/checkbox/useCheckbox.ts +0 -1
  78. package/src/help-text/HelpText.tsx +4 -5
  79. package/src/index.ts +2 -1
  80. package/src/menu/Menu.tsx +36 -0
  81. package/src/menu/MenuCollapse.tsx +80 -0
  82. package/src/{accordion-menu/AccordionMenuItem.tsx → menu/MenuItem.tsx} +6 -6
  83. package/src/menu/MenuItems.tsx +21 -0
  84. package/src/menu/index.ts +3 -0
  85. package/src/menu/stories/menu.stories.mdx +93 -0
  86. package/src/menu/stories/menu.stories.tsx +139 -0
  87. package/src/modal/Modal.tsx +11 -0
  88. package/src/page-header/PageHeader.tsx +1 -5
  89. package/src/pagination/Pagination.tsx +143 -0
  90. package/src/pagination/index.ts +2 -0
  91. package/src/pagination/steps.test.ts +120 -0
  92. package/src/pagination/stories/pagination.stories.tsx +18 -0
  93. package/src/table/ColumnHeader.tsx +70 -0
  94. package/src/table/Table.tsx +30 -2
  95. package/src/table/stories/people.json +822 -0
  96. package/src/table/stories/table-async.stories.tsx +166 -0
  97. package/src/table/stories/table-hot.stories.tsx +377 -0
  98. package/src/table/stories/table.stories.tsx +35 -2
  99. package/src/typography/Heading.tsx +1 -1
  100. package/src/typography/{stories/typography.stories.tsx → typography.stories.tsx} +4 -5
  101. package/cjs/accordion-menu/MenuItems.js +0 -8
  102. package/cjs/accordion-menu/index.js +0 -8
  103. package/cjs/accordion-menu/package.json +0 -6
  104. package/esm/accordion-menu/AccordionMenu.d.ts +0 -12
  105. package/esm/accordion-menu/AccordionMenu.js.map +0 -1
  106. package/esm/accordion-menu/AccordionMenuCollapsable.d.ts +0 -9
  107. package/esm/accordion-menu/AccordionMenuCollapsable.js +0 -28
  108. package/esm/accordion-menu/AccordionMenuCollapsable.js.map +0 -1
  109. package/esm/accordion-menu/AccordionMenuItem.d.ts +0 -13
  110. package/esm/accordion-menu/AccordionMenuItem.js.map +0 -1
  111. package/esm/accordion-menu/MenuItems.js +0 -4
  112. package/esm/accordion-menu/MenuItems.js.map +0 -1
  113. package/esm/accordion-menu/index.d.ts +0 -3
  114. package/esm/accordion-menu/index.js +0 -2
  115. package/esm/accordion-menu/index.js.map +0 -1
  116. package/src/accordion/stories/accordion.stories.mdx +0 -72
  117. package/src/accordion/stories/accordion.stories.tsx +0 -92
  118. package/src/accordion-menu/AccordionMenu.tsx +0 -39
  119. package/src/accordion-menu/AccordionMenuCollapsable.tsx +0 -45
  120. package/src/accordion-menu/MenuItems.tsx +0 -13
  121. package/src/accordion-menu/index.ts +0 -3
  122. package/src/accordion-menu/stories/accordion-menu.stories.mdx +0 -66
  123. package/src/accordion-menu/stories/accordion-menu.stories.tsx +0 -141
  124. package/src/alert/stories/alert.stories.mdx +0 -96
  125. package/src/button/stories/button.stories.mdx +0 -76
  126. package/src/typography/stories/index.css +0 -3
  127. package/src/typography/stories/typography.stories.mdx +0 -83
@@ -0,0 +1,139 @@
1
+ import * as React from "react";
2
+ import cl from "classnames";
3
+ import { Menu } from "../index";
4
+ import { NavLink, HashRouter as Router } from "react-router-dom";
5
+ import { MenuItemProps } from "../MenuItem";
6
+
7
+ export default {
8
+ title: "ds-react/menu",
9
+ component: { Menu },
10
+ };
11
+
12
+ export const All = () => (
13
+ <div
14
+ style={{
15
+ background: "white",
16
+ maxWidth: 288,
17
+ }}
18
+ >
19
+ <h2>Only Menu.Items</h2>
20
+ <Menu>
21
+ <Menu.Item href="#leo">Leo</Menu.Item>
22
+
23
+ <Menu.Item href="#justo">Justo</Menu.Item>
24
+ <Menu.Item href="#pariatur" active aria-current="page">
25
+ Pariatur
26
+ </Menu.Item>
27
+ <Menu.Item href="#nulla">Nulla</Menu.Item>
28
+ <Menu.Item href="#luctus">Luctus</Menu.Item>
29
+ <Menu.Item href="#justo">Justo</Menu.Item>
30
+ <Menu.Item href="#pariatur">Pariatur</Menu.Item>
31
+ </Menu>
32
+
33
+ <h2>With Collapse</h2>
34
+ <Menu>
35
+ <Menu.Item href="#leo">Leo</Menu.Item>
36
+ <Menu.Collapse title="Proin">
37
+ <Menu.Item href="#nulla" active>
38
+ Nulla
39
+ </Menu.Item>
40
+ <Menu.Item href="#luctus">Luctus</Menu.Item>
41
+ </Menu.Collapse>
42
+ <Menu.Collapse title="Accumsan">
43
+ <Menu.Item href="#justo" aria-current="page">
44
+ Justo
45
+ </Menu.Item>
46
+ <Menu.Item href="#pariatur" active aria-current="page">
47
+ Pariatur
48
+ </Menu.Item>
49
+ <Menu.Collapse title="Proin">
50
+ <Menu.Item href="#nulla" active>
51
+ Nulla
52
+ </Menu.Item>
53
+ <Menu.Collapse title="Proin">
54
+ <Menu.Item href="#nulla" active>
55
+ Nulla
56
+ </Menu.Item>
57
+ <Menu.Item href="#luctus">Luctus</Menu.Item>
58
+ </Menu.Collapse>
59
+ <Menu.Item href="#luctus">Luctus</Menu.Item>
60
+ </Menu.Collapse>
61
+ </Menu.Collapse>
62
+ <Menu.Item href="#justo" active>
63
+ Justo
64
+ </Menu.Item>
65
+ <Menu.Item href="#pariatur">Pariatur</Menu.Item>
66
+ </Menu>
67
+ </div>
68
+ );
69
+
70
+ export const ReactRouter = () => {
71
+ const MenuItemLink = (props: MenuItemProps & { to: string }) => {
72
+ const { to } = props;
73
+
74
+ const CustomLink = React.useMemo(
75
+ () => (props: MenuItemProps) => (
76
+ <NavLink
77
+ {...props}
78
+ to={to}
79
+ className={({ isActive }) =>
80
+ cl(props.className, {
81
+ "navds-menu-item--active": isActive,
82
+ })
83
+ }
84
+ />
85
+ ),
86
+ [to]
87
+ );
88
+
89
+ return (
90
+ <Menu.Item {...props} as={CustomLink}>
91
+ Nivå 2 innrykk 2 rems
92
+ </Menu.Item>
93
+ );
94
+ };
95
+
96
+ return (
97
+ <Menu>
98
+ <Menu.Collapse title="Nivå 1 - 3 rems høy">
99
+ <MenuItemLink to="/1">Nivå 2 innrykk 2 rems</MenuItemLink>
100
+ <MenuItemLink to="/2">Subtitle 3</MenuItemLink>
101
+ </Menu.Collapse>
102
+ <Menu.Collapse title="Skjema og søknad">
103
+ <Menu.Collapse title="Satser">
104
+ <MenuItemLink to="/3">
105
+ Nivå 3 - 3 rems innrykk Har du rett til dagpenger når du mottar
106
+ annen økonomisk støtte fra NAV?
107
+ </MenuItemLink>
108
+ </Menu.Collapse>
109
+ <MenuItemLink to="/4">Subtitle 2</MenuItemLink>
110
+ <MenuItemLink to="/5">Subtitle 3</MenuItemLink>
111
+ </Menu.Collapse>
112
+ <Menu.Collapse title="Sakbehandlingstider">
113
+ <Menu.Collapse title="Subtitle 4 Lang tittel på andre nivå lorem ipsum">
114
+ <MenuItemLink to="/6">Subtitle 5</MenuItemLink>
115
+ </Menu.Collapse>
116
+ </Menu.Collapse>
117
+ <MenuItemLink to="/7">Internasjonalt</MenuItemLink>
118
+ <Menu.Collapse title="Meld fra om endringer lang tittel på første nivå">
119
+ <MenuItemLink to="/8">Subtitle 6</MenuItemLink>
120
+ </Menu.Collapse>
121
+ <MenuItemLink to="/9">Nivå 1</MenuItemLink>
122
+ </Menu>
123
+ );
124
+ };
125
+
126
+ ReactRouter.decorators = [
127
+ (Story) => (
128
+ <Router>
129
+ <div
130
+ style={{
131
+ background: "white",
132
+ maxWidth: 288,
133
+ }}
134
+ >
135
+ <Story />
136
+ </div>
137
+ </Router>
138
+ ),
139
+ ];
@@ -33,6 +33,9 @@ export interface ModalProps {
33
33
  * @default true
34
34
  */
35
35
  closeButton?: boolean;
36
+ "aria-labelledby"?: string;
37
+ "aria-describedby"?: string;
38
+ "aria-modal"?: boolean;
36
39
  }
37
40
 
38
41
  interface ModalComponent
@@ -56,6 +59,9 @@ const Modal = forwardRef<ReactModal, ModalProps>(
56
59
  className,
57
60
  shouldCloseOnOverlayClick = true,
58
61
  closeButton = true,
62
+ "aria-describedby": ariaDescribedBy,
63
+ "aria-labelledby": ariaLabelledBy,
64
+ "aria-modal": ariaModal,
59
65
  ...rest
60
66
  },
61
67
  ref
@@ -81,6 +87,11 @@ const Modal = forwardRef<ReactModal, ModalProps>(
81
87
  overlayClassName="navds-modal__overlay"
82
88
  shouldCloseOnOverlayClick={shouldCloseOnOverlayClick}
83
89
  onRequestClose={(e) => onModalCloseRequest(e)}
90
+ aria={{
91
+ describedby: ariaDescribedBy,
92
+ labelledby: ariaLabelledBy,
93
+ modal: ariaModal,
94
+ }}
84
95
  >
85
96
  {children}
86
97
  {closeButton && (
@@ -55,11 +55,7 @@ const PageHeader = forwardRef<HTMLDivElement, PageHeaderProps>(
55
55
  <div className="navds-page-header__illustration">{illustration}</div>
56
56
  )}
57
57
  <div className="navds-page-header__wrapper">
58
- <Heading
59
- className="navds-page-header__title"
60
- size="2xlarge"
61
- level="1"
62
- >
58
+ <Heading className="navds-page-header__title" size="xlarge" level="1">
63
59
  {children}
64
60
  </Heading>
65
61
  {description && (
@@ -0,0 +1,143 @@
1
+ import React from "react";
2
+ import cl from "classnames";
3
+ import { Back, Next } from "@navikt/ds-icons";
4
+ import { BodyShort } from "..";
5
+
6
+ interface PaginationProps extends React.HTMLAttributes<HTMLElement> {
7
+ /**
8
+ * Current page
9
+ */
10
+ page: number;
11
+ /**
12
+ * Number of always visible pages before and after the current page.
13
+ * @default 1
14
+ */
15
+ siblingCount?: number;
16
+ /**
17
+ * Number of always visible pages at the beginning and end.
18
+ * @default 1
19
+ */
20
+ boundaryCount?: number;
21
+ /**
22
+ * Callback when current page changes
23
+ */
24
+ onPageChange: (page: number) => void;
25
+ /**
26
+ * Total number of pages
27
+ */
28
+ count: number;
29
+ /**
30
+ * Changes padding, height and font-size
31
+ * @default "medium"
32
+ */
33
+ size?: "medium" | "small";
34
+ /**
35
+ * Display text alongside "previous" and "next" icons
36
+ * @default false
37
+ */
38
+ prevNextTexts?: boolean;
39
+ }
40
+
41
+ export const getSteps = ({
42
+ page,
43
+ count,
44
+ boundaryCount = 1,
45
+ siblingCount = 1,
46
+ }) => {
47
+ const range = (start: number, end: number) =>
48
+ Array.from({ length: end - start + 1 }, (_, i) => start + i);
49
+
50
+ if (count <= (boundaryCount + siblingCount) * 2 + 3) return range(1, count);
51
+
52
+ const startPages = range(1, boundaryCount);
53
+ const endPages = range(count - boundaryCount + 1, count);
54
+
55
+ const siblingsStart = Math.max(
56
+ Math.min(page - siblingCount, count - boundaryCount - siblingCount * 2 - 1),
57
+ boundaryCount + 2
58
+ );
59
+ const siblingsEnd = siblingsStart + siblingCount * 2;
60
+
61
+ return [
62
+ ...startPages,
63
+ siblingsStart - (startPages[startPages.length - 1] ?? 0) === 2
64
+ ? siblingsStart - 1
65
+ : "ellipsis",
66
+ ...range(siblingsStart, siblingsEnd),
67
+ (endPages[0] ?? count + 1) - siblingsEnd === 2
68
+ ? siblingsEnd + 1
69
+ : "ellipsis",
70
+ ...endPages,
71
+ ];
72
+ };
73
+ const Pagination = ({
74
+ page,
75
+ onPageChange,
76
+ count,
77
+ siblingCount,
78
+ boundaryCount,
79
+ className,
80
+ size = "medium",
81
+ prevNextTexts = false,
82
+ }: PaginationProps) => {
83
+ return (
84
+ <nav
85
+ className={cl("navds-pagination", `navds-pagination--${size}`, className)}
86
+ >
87
+ <ul className="navds-pagination__list">
88
+ <li>
89
+ <button
90
+ className="navds-pagination__previous"
91
+ disabled={page === 1}
92
+ onClick={() => onPageChange(page - 1)}
93
+ >
94
+ <Back
95
+ className="navds-pagination__previous-icon"
96
+ title={prevNextTexts ? undefined : "Tilbake"}
97
+ role={prevNextTexts ? "presentation" : undefined}
98
+ />
99
+ {prevNextTexts && <BodyShort size={size}>Tilbake</BodyShort>}
100
+ </button>
101
+ </li>
102
+ {getSteps({ page, count, siblingCount, boundaryCount }).map(
103
+ (step, i) => {
104
+ const n = Number(step);
105
+ return isNaN(n) ? (
106
+ <li className="navds-pagination__ellipsis" key={`${step}${i}`}>
107
+ <BodyShort size={size}>...</BodyShort>
108
+ </li>
109
+ ) : (
110
+ <li key={step}>
111
+ <BodyShort
112
+ size={size}
113
+ as="button"
114
+ className="navds-pagination__item"
115
+ onClick={() => onPageChange(n)}
116
+ aria-current={page === n ? true : undefined}
117
+ >
118
+ {n}
119
+ </BodyShort>
120
+ </li>
121
+ );
122
+ }
123
+ )}
124
+ <li>
125
+ <button
126
+ className="navds-pagination__next"
127
+ disabled={page === count}
128
+ onClick={() => onPageChange(page + 1)}
129
+ >
130
+ {prevNextTexts && <BodyShort size={size}>Neste</BodyShort>}
131
+ <Next
132
+ className="navds-pagination__next-icon"
133
+ title={prevNextTexts ? undefined : "Neste"}
134
+ role={prevNextTexts ? "presentation" : undefined}
135
+ />
136
+ </button>
137
+ </li>
138
+ </ul>
139
+ </nav>
140
+ );
141
+ };
142
+
143
+ export default Pagination;
@@ -0,0 +1,2 @@
1
+ export { default as Pagination } from "./Pagination";
2
+ export * from "./Pagination";
@@ -0,0 +1,120 @@
1
+ import faker from "faker";
2
+ import { getSteps } from "./Pagination";
3
+
4
+ describe("getSteps", () => {
5
+ it("lists all pages when count is <= 7", () => {
6
+ const count = faker.datatype.number({ min: 1, max: 7 });
7
+ expect(getSteps({ page: 1, count })).toEqual(
8
+ Array.from({ length: count }, (_, i) => i + 1)
9
+ );
10
+ });
11
+
12
+ it("has an end ellipsis when count >= 8", () => {
13
+ const count = faker.datatype.number({ min: 8 });
14
+ const page = faker.datatype.number({ min: 1, max: 4 });
15
+ expect(
16
+ getSteps({
17
+ page,
18
+ count,
19
+ })
20
+ ).toEqual([1, 2, 3, 4, 5, "ellipsis", count]);
21
+ });
22
+
23
+ it("has a start ellipsis when count - page >= 3", () => {
24
+ const count = faker.datatype.number({ min: 8 });
25
+ const page = faker.datatype.number({ min: count - 3, max: count });
26
+ expect(
27
+ getSteps({
28
+ page,
29
+ count,
30
+ })
31
+ ).toEqual([
32
+ 1,
33
+ "ellipsis",
34
+ count - 4,
35
+ count - 3,
36
+ count - 2,
37
+ count - 1,
38
+ count,
39
+ ]);
40
+ });
41
+
42
+ it("has start & end ellipsis when count is high", () => {
43
+ const count = faker.datatype.number({ min: 9 });
44
+ const page = faker.datatype.number({ min: 5, max: count - 4 });
45
+ expect(
46
+ getSteps({
47
+ page,
48
+ count,
49
+ })
50
+ ).toEqual([1, "ellipsis", page - 1, page, page + 1, "ellipsis", count]);
51
+ });
52
+
53
+ it("can have a reduced siblingCount", () => {
54
+ const count = faker.datatype.number({ min: 7 });
55
+ const page = faker.datatype.number({ min: 4, max: count - 3 });
56
+ expect(
57
+ getSteps({
58
+ page,
59
+ count,
60
+ siblingCount: 0,
61
+ })
62
+ ).toEqual([1, "ellipsis", page, "ellipsis", count]);
63
+ });
64
+
65
+ it("can have an increased siblingCount", () => {
66
+ const count = faker.datatype.number({ min: 11 });
67
+ const page = faker.datatype.number({ min: 6, max: count - 5 });
68
+ expect(
69
+ getSteps({
70
+ page,
71
+ count,
72
+ siblingCount: 2,
73
+ })
74
+ ).toEqual([
75
+ 1,
76
+ "ellipsis",
77
+ page - 2,
78
+ page - 1,
79
+ page,
80
+ page + 1,
81
+ page + 2,
82
+ "ellipsis",
83
+ count,
84
+ ]);
85
+ });
86
+
87
+ it("can have an reduced boundaryCount", () => {
88
+ const count = faker.datatype.number({ min: 7 });
89
+ const page = faker.datatype.number({ min: 4, max: count - 3 });
90
+ expect(
91
+ getSteps({
92
+ page,
93
+ count,
94
+ boundaryCount: 0,
95
+ })
96
+ ).toEqual(["ellipsis", page - 1, page, page + 1, "ellipsis"]);
97
+ });
98
+
99
+ it("can have an increased boundaryCount", () => {
100
+ const count = faker.datatype.number({ min: 11 });
101
+ const page = faker.datatype.number({ min: 6, max: count - 5 });
102
+ expect(
103
+ getSteps({
104
+ page,
105
+ count,
106
+ boundaryCount: 2,
107
+ })
108
+ ).toEqual([
109
+ 1,
110
+ 2,
111
+ "ellipsis",
112
+ page - 1,
113
+ page,
114
+ page + 1,
115
+ "ellipsis",
116
+ count - 1,
117
+ count,
118
+ ]);
119
+ });
120
+ });
@@ -0,0 +1,18 @@
1
+ import React, { useState } from "react";
2
+ import Pagination from "../Pagination";
3
+
4
+ export default {
5
+ title: "ds-react/pagination",
6
+ component: Pagination,
7
+ };
8
+
9
+ export const All = (props) => {
10
+ const [page, setPage] = useState(props.page);
11
+ return <Pagination {...props} page={page} onPageChange={setPage} />;
12
+ };
13
+ All.args = {
14
+ page: 1,
15
+ count: 8,
16
+ siblingCount: 1,
17
+ boundaryCount: 1,
18
+ };
@@ -0,0 +1,70 @@
1
+ import React, { forwardRef, useContext } from "react";
2
+ import { TableContext } from "..";
3
+ import { Down, Up } from "@navikt/ds-icons";
4
+ import HeaderCell from "./HeaderCell";
5
+
6
+ interface ColumnHeaderProps extends React.HTMLAttributes<HTMLTableCellElement> {
7
+ /**
8
+ * Column is sortable
9
+ * @default false
10
+ */
11
+ sortable?: boolean;
12
+ /**
13
+ * Key to sort by
14
+ */
15
+ sortKey?: string;
16
+ }
17
+
18
+ export interface ColumnHeaderType
19
+ extends React.ForwardRefExoticComponent<
20
+ ColumnHeaderProps & React.RefAttributes<HTMLTableCellElement>
21
+ > {}
22
+
23
+ const ColumnHeader: ColumnHeaderType = forwardRef(
24
+ ({ className, children, sortable = false, sortKey, ...rest }, ref) => {
25
+ const context = useContext(TableContext);
26
+
27
+ if (sortable && !sortKey) {
28
+ console.warn("ColumnHeader with `sortable=true` must have a sortKey.");
29
+ }
30
+
31
+ return (
32
+ <HeaderCell
33
+ scope="col"
34
+ ref={ref}
35
+ className={className}
36
+ aria-sort={
37
+ sortable
38
+ ? context?.sort?.orderBy === sortKey
39
+ ? context?.sort?.direction
40
+ : "none"
41
+ : undefined
42
+ }
43
+ {...rest}
44
+ >
45
+ {sortable ? (
46
+ <button
47
+ className="navds-table__sort-button"
48
+ onClick={
49
+ sortable && sortKey
50
+ ? () => context?.onSortChange?.(sortKey)
51
+ : undefined
52
+ }
53
+ >
54
+ {children}
55
+ {context?.sort?.orderBy === sortKey &&
56
+ context?.sort?.direction === "descending" ? (
57
+ <Down aria-label="sorter synkende" />
58
+ ) : (
59
+ <Up aria-label="sorter stigende" />
60
+ )}
61
+ </button>
62
+ ) : (
63
+ children
64
+ )}
65
+ </HeaderCell>
66
+ );
67
+ }
68
+ );
69
+
70
+ export default ColumnHeader;
@@ -3,9 +3,15 @@ import cl from "classnames";
3
3
  import Header, { HeaderType } from "./Header";
4
4
  import Body, { BodyType } from "./Body";
5
5
  import Row, { RowType } from "./Row";
6
+ import ColumnHeader, { ColumnHeaderType } from "./ColumnHeader";
6
7
  import HeaderCell, { HeaderCellType } from "./HeaderCell";
7
8
  import DataCell, { DataCellType } from "./DataCell";
8
9
 
10
+ export interface SortState {
11
+ orderBy: string;
12
+ direction: "ascending" | "descending";
13
+ }
14
+
9
15
  export interface TableProps extends React.HTMLAttributes<HTMLTableElement> {
10
16
  /**
11
17
  * Changes padding
@@ -17,6 +23,14 @@ export interface TableProps extends React.HTMLAttributes<HTMLTableElement> {
17
23
  * @default false
18
24
  */
19
25
  zebraStripes?: boolean;
26
+ /**
27
+ * Sort state
28
+ */
29
+ sort?: SortState;
30
+ /**
31
+ * Callback whens ort state changes
32
+ */
33
+ onSortChange?: (sortKey?: string) => void;
20
34
  }
21
35
 
22
36
  export interface TableType
@@ -28,17 +42,30 @@ export interface TableType
28
42
  Row: RowType;
29
43
  DataCell: DataCellType;
30
44
  HeaderCell: HeaderCellType;
45
+ ColumnHeader: ColumnHeaderType;
31
46
  }
32
47
 
33
48
  export interface TableContextProps {
34
49
  size: "medium" | "small";
50
+ onSortChange?: (sortKey: string) => void;
51
+ sort?: SortState;
35
52
  }
36
53
 
37
54
  export const TableContext = createContext<TableContextProps | null>(null);
38
55
 
39
56
  const Table = forwardRef(
40
- ({ className, zebraStripes = false, size = "medium", ...rest }, ref) => (
41
- <TableContext.Provider value={{ size }}>
57
+ (
58
+ {
59
+ className,
60
+ zebraStripes = false,
61
+ size = "medium",
62
+ onSortChange,
63
+ sort,
64
+ ...rest
65
+ },
66
+ ref
67
+ ) => (
68
+ <TableContext.Provider value={{ size, onSortChange, sort }}>
42
69
  <table
43
70
  {...rest}
44
71
  ref={ref}
@@ -53,6 +80,7 @@ const Table = forwardRef(
53
80
  Table.Header = Header;
54
81
  Table.Body = Body;
55
82
  Table.Row = Row;
83
+ Table.ColumnHeader = ColumnHeader;
56
84
  Table.HeaderCell = HeaderCell;
57
85
  Table.DataCell = DataCell;
58
86