@navikt/ds-react 3.4.1 → 4.0.0

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 (234) hide show
  1. package/_docs.json +5615 -3780
  2. package/cjs/date/hooks/index.js +4 -4
  3. package/cjs/date/index.js +6 -6
  4. package/cjs/dropdown/Dropdown.js +57 -0
  5. package/cjs/dropdown/Menu/Divider.js +47 -0
  6. package/cjs/dropdown/Menu/GroupedList/Heading.js +47 -0
  7. package/cjs/dropdown/Menu/GroupedList/Item.js +54 -0
  8. package/cjs/dropdown/Menu/GroupedList/index.js +51 -0
  9. package/cjs/dropdown/Menu/GroupedList/package.json +6 -0
  10. package/cjs/dropdown/Menu/List/Item.js +54 -0
  11. package/cjs/dropdown/Menu/List/index.js +49 -0
  12. package/cjs/dropdown/Menu/List/package.json +6 -0
  13. package/cjs/dropdown/Menu/index.js +64 -0
  14. package/cjs/dropdown/Menu/package.json +6 -0
  15. package/cjs/dropdown/Toggle.js +66 -0
  16. package/cjs/dropdown/index.js +23 -0
  17. package/cjs/dropdown/package.json +6 -0
  18. package/cjs/form/radio/RadioGroup.js +10 -0
  19. package/cjs/index.js +3 -0
  20. package/cjs/internal-header/InternalHeader.js +55 -0
  21. package/cjs/internal-header/InternalHeaderButton.js +47 -0
  22. package/cjs/internal-header/InternalHeaderTitle.js +48 -0
  23. package/cjs/internal-header/InternalHeaderUser.js +51 -0
  24. package/cjs/internal-header/InternalHeaderUserButton.js +54 -0
  25. package/cjs/internal-header/index.js +8 -0
  26. package/cjs/internal-header/package.json +6 -0
  27. package/cjs/read-more/ReadMore.js +17 -0
  28. package/cjs/timeline/AxisLabels.js +90 -0
  29. package/cjs/timeline/Pin.js +106 -0
  30. package/cjs/timeline/Timeline.js +162 -0
  31. package/cjs/timeline/TimelineRow.js +86 -0
  32. package/cjs/timeline/hooks/usePeriodContext.js +16 -0
  33. package/cjs/timeline/hooks/useRowContext.js +18 -0
  34. package/cjs/timeline/hooks/useTimelineContext.js +23 -0
  35. package/cjs/timeline/hooks/useTimelineRows.js +79 -0
  36. package/cjs/timeline/index.js +8 -0
  37. package/cjs/timeline/package.json +6 -0
  38. package/cjs/timeline/period/ClickablePeriod.js +120 -0
  39. package/cjs/timeline/period/NonClickablePeriod.js +18 -0
  40. package/cjs/timeline/period/index.js +46 -0
  41. package/cjs/timeline/period/package.json +6 -0
  42. package/cjs/timeline/utils/calc.js +19 -0
  43. package/cjs/timeline/utils/filter.js +18 -0
  44. package/cjs/timeline/utils/index.js +7 -0
  45. package/cjs/timeline/utils/package.json +6 -0
  46. package/cjs/timeline/utils/period.js +40 -0
  47. package/cjs/timeline/utils/sort.js +10 -0
  48. package/cjs/timeline/utils/timeline.js +79 -0
  49. package/cjs/timeline/utils/types.external.js +2 -0
  50. package/cjs/timeline/utils/types.internal.js +2 -0
  51. package/cjs/timeline/zoom/ZoomButton.js +68 -0
  52. package/cjs/timeline/zoom/index.js +50 -0
  53. package/cjs/timeline/zoom/package.json +6 -0
  54. package/esm/date/hooks/index.d.ts +3 -3
  55. package/esm/date/hooks/index.js +3 -3
  56. package/esm/date/hooks/index.js.map +1 -1
  57. package/esm/date/index.d.ts +3 -3
  58. package/esm/date/index.js +3 -3
  59. package/esm/date/index.js.map +1 -1
  60. package/esm/dropdown/Dropdown.d.ts +37 -0
  61. package/esm/dropdown/Dropdown.js +29 -0
  62. package/esm/dropdown/Dropdown.js.map +1 -0
  63. package/esm/dropdown/Menu/Divider.d.ts +4 -0
  64. package/esm/dropdown/Menu/Divider.js +19 -0
  65. package/esm/dropdown/Menu/Divider.js.map +1 -0
  66. package/esm/dropdown/Menu/GroupedList/Heading.d.ts +10 -0
  67. package/esm/dropdown/Menu/GroupedList/Heading.js +19 -0
  68. package/esm/dropdown/Menu/GroupedList/Heading.js.map +1 -0
  69. package/esm/dropdown/Menu/GroupedList/Item.d.ts +11 -0
  70. package/esm/dropdown/Menu/GroupedList/Item.js +26 -0
  71. package/esm/dropdown/Menu/GroupedList/Item.js.map +1 -0
  72. package/esm/dropdown/Menu/GroupedList/index.d.ts +15 -0
  73. package/esm/dropdown/Menu/GroupedList/index.js +23 -0
  74. package/esm/dropdown/Menu/GroupedList/index.js.map +1 -0
  75. package/esm/dropdown/Menu/List/Item.d.ts +11 -0
  76. package/esm/dropdown/Menu/List/Item.js +26 -0
  77. package/esm/dropdown/Menu/List/Item.js.map +1 -0
  78. package/esm/dropdown/Menu/List/index.d.ts +13 -0
  79. package/esm/dropdown/Menu/List/index.js +21 -0
  80. package/esm/dropdown/Menu/List/index.js.map +1 -0
  81. package/esm/dropdown/Menu/index.d.ts +27 -0
  82. package/esm/dropdown/Menu/index.js +36 -0
  83. package/esm/dropdown/Menu/index.js.map +1 -0
  84. package/esm/dropdown/Toggle.d.ts +10 -0
  85. package/esm/dropdown/Toggle.js +38 -0
  86. package/esm/dropdown/Toggle.js.map +1 -0
  87. package/esm/dropdown/index.d.ts +2 -0
  88. package/esm/dropdown/index.js +3 -0
  89. package/esm/dropdown/index.js.map +1 -0
  90. package/esm/form/radio/RadioGroup.d.ts +10 -0
  91. package/esm/form/radio/RadioGroup.js +10 -0
  92. package/esm/form/radio/RadioGroup.js.map +1 -1
  93. package/esm/index.d.ts +3 -0
  94. package/esm/index.js +3 -0
  95. package/esm/index.js.map +1 -1
  96. package/esm/internal-header/InternalHeader.d.ts +16 -0
  97. package/esm/internal-header/InternalHeader.js +27 -0
  98. package/esm/internal-header/InternalHeader.js.map +1 -0
  99. package/esm/internal-header/InternalHeaderButton.d.ts +11 -0
  100. package/esm/internal-header/InternalHeaderButton.js +19 -0
  101. package/esm/internal-header/InternalHeaderButton.js.map +1 -0
  102. package/esm/internal-header/InternalHeaderTitle.d.ts +11 -0
  103. package/esm/internal-header/InternalHeaderTitle.js +20 -0
  104. package/esm/internal-header/InternalHeaderTitle.js.map +1 -0
  105. package/esm/internal-header/InternalHeaderUser.d.ts +14 -0
  106. package/esm/internal-header/InternalHeaderUser.js +23 -0
  107. package/esm/internal-header/InternalHeaderUser.js.map +1 -0
  108. package/esm/internal-header/InternalHeaderUserButton.d.ts +15 -0
  109. package/esm/internal-header/InternalHeaderUserButton.js +26 -0
  110. package/esm/internal-header/InternalHeaderUserButton.js.map +1 -0
  111. package/esm/internal-header/index.d.ts +5 -0
  112. package/esm/internal-header/index.js +2 -0
  113. package/esm/internal-header/index.js.map +1 -0
  114. package/esm/read-more/ReadMore.d.ts +17 -0
  115. package/esm/read-more/ReadMore.js +17 -0
  116. package/esm/read-more/ReadMore.js.map +1 -1
  117. package/esm/timeline/AxisLabels.d.ts +6 -0
  118. package/esm/timeline/AxisLabels.js +81 -0
  119. package/esm/timeline/AxisLabels.js.map +1 -0
  120. package/esm/timeline/Pin.d.ts +17 -0
  121. package/esm/timeline/Pin.js +81 -0
  122. package/esm/timeline/Pin.js.map +1 -0
  123. package/esm/timeline/Timeline.d.ts +45 -0
  124. package/esm/timeline/Timeline.js +134 -0
  125. package/esm/timeline/Timeline.js.map +1 -0
  126. package/esm/timeline/TimelineRow.d.ts +22 -0
  127. package/esm/timeline/TimelineRow.js +58 -0
  128. package/esm/timeline/TimelineRow.js.map +1 -0
  129. package/esm/timeline/hooks/usePeriodContext.d.ts +9 -0
  130. package/esm/timeline/hooks/usePeriodContext.js +13 -0
  131. package/esm/timeline/hooks/usePeriodContext.js.map +1 -0
  132. package/esm/timeline/hooks/useRowContext.d.ts +11 -0
  133. package/esm/timeline/hooks/useRowContext.js +15 -0
  134. package/esm/timeline/hooks/useRowContext.js.map +1 -0
  135. package/esm/timeline/hooks/useTimelineContext.d.ts +15 -0
  136. package/esm/timeline/hooks/useTimelineContext.js +20 -0
  137. package/esm/timeline/hooks/useTimelineContext.js.map +1 -0
  138. package/esm/timeline/hooks/useTimelineRows.d.ts +4 -0
  139. package/esm/timeline/hooks/useTimelineRows.js +74 -0
  140. package/esm/timeline/hooks/useTimelineRows.js.map +1 -0
  141. package/esm/timeline/index.d.ts +6 -0
  142. package/esm/timeline/index.js +2 -0
  143. package/esm/timeline/index.js.map +1 -0
  144. package/esm/timeline/period/ClickablePeriod.d.ts +9 -0
  145. package/esm/timeline/period/ClickablePeriod.js +93 -0
  146. package/esm/timeline/period/ClickablePeriod.js.map +1 -0
  147. package/esm/timeline/period/NonClickablePeriod.d.ts +7 -0
  148. package/esm/timeline/period/NonClickablePeriod.js +14 -0
  149. package/esm/timeline/period/NonClickablePeriod.js.map +1 -0
  150. package/esm/timeline/period/index.d.ts +57 -0
  151. package/esm/timeline/period/index.js +18 -0
  152. package/esm/timeline/period/index.js.map +1 -0
  153. package/esm/timeline/utils/calc.d.ts +5 -0
  154. package/esm/timeline/utils/calc.js +15 -0
  155. package/esm/timeline/utils/calc.js.map +1 -0
  156. package/esm/timeline/utils/filter.d.ts +10 -0
  157. package/esm/timeline/utils/filter.js +11 -0
  158. package/esm/timeline/utils/filter.js.map +1 -0
  159. package/esm/timeline/utils/index.d.ts +1 -0
  160. package/esm/timeline/utils/index.js +2 -0
  161. package/esm/timeline/utils/index.js.map +1 -0
  162. package/esm/timeline/utils/period.d.ts +2 -0
  163. package/esm/timeline/utils/period.js +33 -0
  164. package/esm/timeline/utils/period.js.map +1 -0
  165. package/esm/timeline/utils/sort.d.ts +4 -0
  166. package/esm/timeline/utils/sort.js +5 -0
  167. package/esm/timeline/utils/sort.js.map +1 -0
  168. package/esm/timeline/utils/timeline.d.ts +12 -0
  169. package/esm/timeline/utils/timeline.js +73 -0
  170. package/esm/timeline/utils/timeline.js.map +1 -0
  171. package/esm/timeline/utils/types.external.d.ts +53 -0
  172. package/esm/timeline/utils/types.external.js +2 -0
  173. package/esm/timeline/utils/types.external.js.map +1 -0
  174. package/esm/timeline/utils/types.internal.d.ts +61 -0
  175. package/esm/timeline/utils/types.internal.js +2 -0
  176. package/esm/timeline/utils/types.internal.js.map +1 -0
  177. package/esm/timeline/zoom/ZoomButton.d.ts +19 -0
  178. package/esm/timeline/zoom/ZoomButton.js +43 -0
  179. package/esm/timeline/zoom/ZoomButton.js.map +1 -0
  180. package/esm/timeline/zoom/index.d.ts +11 -0
  181. package/esm/timeline/zoom/index.js +22 -0
  182. package/esm/timeline/zoom/index.js.map +1 -0
  183. package/package.json +2 -2
  184. package/src/date/datepicker/datepicker.stories.tsx +19 -21
  185. package/src/date/datepicker/datepicker.test.tsx +5 -5
  186. package/src/date/hooks/index.ts +3 -3
  187. package/src/date/hooks/useRangeDatepicker.test.tsx +6 -6
  188. package/src/date/index.ts +5 -5
  189. package/src/date/monthpicker/monthpicker.stories.tsx +5 -5
  190. package/src/date/utils/__tests__/get-dates.test.ts +0 -1
  191. package/src/dropdown/Dropdown.tsx +80 -0
  192. package/src/dropdown/Menu/Divider.tsx +18 -0
  193. package/src/dropdown/Menu/GroupedList/Heading.tsx +31 -0
  194. package/src/dropdown/Menu/GroupedList/Item.tsx +45 -0
  195. package/src/dropdown/Menu/GroupedList/index.tsx +33 -0
  196. package/src/dropdown/Menu/List/Item.tsx +44 -0
  197. package/src/dropdown/Menu/List/index.tsx +27 -0
  198. package/src/dropdown/Menu/index.tsx +85 -0
  199. package/src/dropdown/Toggle.tsx +52 -0
  200. package/src/dropdown/dropdown.stories.tsx +91 -0
  201. package/src/dropdown/index.ts +2 -0
  202. package/src/form/radio/RadioGroup.tsx +10 -0
  203. package/src/index.ts +3 -0
  204. package/src/internal-header/InternalHeader.tsx +44 -0
  205. package/src/internal-header/InternalHeaderButton.tsx +28 -0
  206. package/src/internal-header/InternalHeaderTitle.tsx +35 -0
  207. package/src/internal-header/InternalHeaderUser.tsx +39 -0
  208. package/src/internal-header/InternalHeaderUserButton.tsx +43 -0
  209. package/src/internal-header/header.stories.tsx +225 -0
  210. package/src/internal-header/index.ts +8 -0
  211. package/src/read-more/ReadMore.tsx +17 -0
  212. package/src/timeline/AxisLabels.tsx +143 -0
  213. package/src/timeline/Pin.tsx +169 -0
  214. package/src/timeline/Timeline.tsx +219 -0
  215. package/src/timeline/TimelineRow.tsx +122 -0
  216. package/src/timeline/hooks/usePeriodContext.tsx +22 -0
  217. package/src/timeline/hooks/useRowContext.tsx +26 -0
  218. package/src/timeline/hooks/useTimelineContext.tsx +37 -0
  219. package/src/timeline/hooks/useTimelineRows.ts +161 -0
  220. package/src/timeline/index.ts +6 -0
  221. package/src/timeline/period/ClickablePeriod.tsx +193 -0
  222. package/src/timeline/period/NonClickablePeriod.tsx +46 -0
  223. package/src/timeline/period/index.tsx +130 -0
  224. package/src/timeline/timeline.stories.tsx +444 -0
  225. package/src/timeline/utils/calc.ts +26 -0
  226. package/src/timeline/utils/filter.ts +32 -0
  227. package/src/timeline/utils/index.ts +6 -0
  228. package/src/timeline/utils/period.ts +48 -0
  229. package/src/timeline/utils/sort.ts +11 -0
  230. package/src/timeline/utils/timeline.ts +83 -0
  231. package/src/timeline/utils/types.external.ts +67 -0
  232. package/src/timeline/utils/types.internal.ts +76 -0
  233. package/src/timeline/zoom/ZoomButton.tsx +83 -0
  234. package/src/timeline/zoom/index.tsx +30 -0
@@ -0,0 +1,37 @@
1
+ import { createContext, useContext } from "react";
2
+
3
+ interface TimelineContextProps {
4
+ startDate: Date;
5
+ endDate: Date;
6
+ direction: "left" | "right";
7
+ setStart: (d: Date) => void;
8
+ setEndInclusive: (d: Date) => void;
9
+ activeRow: number | null;
10
+ setActiveRow: (i: string) => void;
11
+ initiate: (i: number) => void;
12
+ addFocusable: (ref: HTMLButtonElement | null, id: number) => void;
13
+ }
14
+
15
+ export const TimelineContext = createContext<TimelineContextProps>({
16
+ startDate: new Date(),
17
+ endDate: new Date(),
18
+ direction: "left",
19
+ setStart: () => null,
20
+ setEndInclusive: () => null,
21
+ activeRow: 0,
22
+ setActiveRow: () => null,
23
+ initiate: () => null,
24
+ addFocusable: () => null,
25
+ });
26
+
27
+ export const useTimelineContext = () => {
28
+ const context = useContext(TimelineContext);
29
+
30
+ if (!context) {
31
+ console.warn(
32
+ "useTimelineContext must be used with TimelineContext (<Timeline />)"
33
+ );
34
+ }
35
+
36
+ return context;
37
+ };
@@ -0,0 +1,161 @@
1
+ import { useMemo } from "react";
2
+
3
+ import { addDays, endOfDay, isAfter, startOfDay, subDays } from "date-fns";
4
+
5
+ import { lastPeriod } from "../utils/sort";
6
+ import { Period } from "../utils/types.external";
7
+ import {
8
+ InternalSimpleTimeline,
9
+ PositionedPeriod,
10
+ } from "../utils/types.internal";
11
+
12
+ import { horizontalPositionAndWidth } from "../utils/calc";
13
+ import { invisiblePeriods, withinADay } from "../utils/filter";
14
+
15
+ const spatialPeriod = (
16
+ period: Period,
17
+ timelineStart: Date,
18
+ timelineEndInclusive: Date,
19
+ direction: "left" | "right" = "left",
20
+ i: number,
21
+ periods: PositionedPeriod[],
22
+ rowIndex: number
23
+ ): PositionedPeriod => {
24
+ const start = period.start;
25
+ const endInclusive = period.end;
26
+
27
+ const rightOverlap =
28
+ i < periods.length - 1 && !isAfter(periods[i + 1].start, endInclusive);
29
+
30
+ const { horizontalPosition, width } = horizontalPositionAndWidth(
31
+ startOfDay(start),
32
+ endOfDay(
33
+ rightOverlap ? startOfDay(subDays(periods[i + 1].start, 1)) : endInclusive
34
+ ),
35
+ timelineStart,
36
+ timelineEndInclusive
37
+ );
38
+
39
+ return {
40
+ id: `r-${rowIndex}-p-${i}`,
41
+ start: start,
42
+ endInclusive: endInclusive,
43
+ horizontalPosition: horizontalPosition,
44
+ direction: direction,
45
+ width: width,
46
+ end: endInclusive,
47
+ status: period.status,
48
+ onSelectPeriod: period.onSelectPeriod,
49
+ icon: period.icon,
50
+ children: period.children,
51
+ isActive: period.isActive,
52
+ statusLabel: period.statusLabel,
53
+ };
54
+ };
55
+
56
+ const adjustedEdges = (
57
+ period: PositionedPeriod,
58
+ i: number,
59
+ allPeriods: PositionedPeriod[]
60
+ ): PositionedPeriod => {
61
+ const left =
62
+ i > 0 && withinADay(period.start, allPeriods[i - 1].endInclusive);
63
+ const right =
64
+ i < allPeriods.length - 1 &&
65
+ withinADay(allPeriods[i + 1].start, period.endInclusive);
66
+
67
+ return left && right
68
+ ? { ...period, cropped: "both" }
69
+ : left
70
+ ? { ...period, cropped: "left" }
71
+ : right
72
+ ? { ...period, cropped: "right" }
73
+ : period;
74
+ };
75
+
76
+ const trimmedPeriods = (period: PositionedPeriod) => {
77
+ let { horizontalPosition, width, cropped } = period;
78
+ if (horizontalPosition + width > 100) {
79
+ width = 100 - horizontalPosition;
80
+ cropped = cropped === "left" || cropped === "both" ? "both" : "right";
81
+ }
82
+ if (horizontalPosition < 0 && horizontalPosition + width > 0) {
83
+ width = horizontalPosition + width;
84
+ horizontalPosition = 0;
85
+ cropped = cropped === "right" || cropped === "both" ? "both" : "left";
86
+ }
87
+
88
+ return {
89
+ ...period,
90
+ width: width,
91
+ horizontalPosition: horizontalPosition,
92
+ cropped: cropped,
93
+ };
94
+ };
95
+
96
+ export const useTimelineRows = (
97
+ rows: any,
98
+ startDate: Date,
99
+ endDate: Date,
100
+ direction: "left" | "right"
101
+ ): InternalSimpleTimeline[] =>
102
+ useMemo(
103
+ () =>
104
+ rows.map((periods: InternalSimpleTimeline, i: number) => {
105
+ const rowIndex = i;
106
+ const timelinePeriods = periods.periods
107
+ .sort((a: Period, b: Period) => a.start.valueOf() - b.start.valueOf())
108
+ .map((period: Period & { restProps?: any; ref?: any }, i) => ({
109
+ ...spatialPeriod(
110
+ period,
111
+ startDate,
112
+ endDate,
113
+ direction,
114
+ i,
115
+ periods.periods,
116
+ rowIndex
117
+ ),
118
+ restProps: period?.restProps,
119
+ ref: period?.ref,
120
+ }))
121
+ .sort(lastPeriod)
122
+ .map(adjustedEdges)
123
+ .map(trimmedPeriods)
124
+ .filter(invisiblePeriods);
125
+ return {
126
+ id: `tl-row-${rowIndex}`,
127
+ label: periods.label,
128
+ headingTag: periods.headingTag || "h3",
129
+ icon: periods.icon,
130
+ periods:
131
+ direction === "left" ? timelinePeriods : timelinePeriods.reverse(),
132
+ restProps: periods?.restProps,
133
+ ref: periods?.ref,
134
+ };
135
+ }),
136
+ [rows, startDate, endDate, direction]
137
+ );
138
+
139
+ const earliestDate = (earliest: Date, period: Period) =>
140
+ period.start < earliest ? period.start : earliest;
141
+
142
+ const earliestFomDate = (rader: Period[][]) =>
143
+ rader.flat().reduce(earliestDate, new Date());
144
+
145
+ export const useEarliestDate = ({ startDate, rows }: any) =>
146
+ useMemo(
147
+ () => (startDate ? startDate : earliestFomDate(rows)),
148
+ [startDate, rows]
149
+ );
150
+
151
+ const latestDate = (latest: Date, period: Period) =>
152
+ period.end > latest ? period.end : latest;
153
+
154
+ const latestTomDate = (rows: Period[][]) =>
155
+ rows.flat().reduce(latestDate, new Date(0));
156
+
157
+ export const useLatestDate = ({ endDate, rows }: any) =>
158
+ useMemo(
159
+ () => (endDate ? endDate : addDays(latestTomDate(rows), 1)),
160
+ [endDate, rows]
161
+ );
@@ -0,0 +1,6 @@
1
+ export { default as Timeline } from "./Timeline";
2
+ export type { TimelineProps } from "./Timeline";
3
+ export type { TimelineRowProps } from "./TimelineRow";
4
+ export type { TimelinePinProps } from "./Pin";
5
+ export type { TimelinePeriodProps } from "./period";
6
+ export type { TimelineZoomButtonProps } from "./zoom/ZoomButton";
@@ -0,0 +1,193 @@
1
+ import {
2
+ autoUpdate,
3
+ arrow as flArrow,
4
+ flip,
5
+ offset,
6
+ safePolygon,
7
+ shift,
8
+ useDismiss,
9
+ useFloating,
10
+ useFocus,
11
+ useHover,
12
+ useInteractions,
13
+ } from "@floating-ui/react";
14
+ import { useEventListener, mergeRefs } from "../../util";
15
+ import cl from "clsx";
16
+ import React, { useCallback, useMemo, useRef, useState } from "react";
17
+ import { usePeriodContext } from "../hooks/usePeriodContext";
18
+ import { useRowContext } from "../hooks/useRowContext";
19
+ import { useTimelineContext } from "../hooks/useTimelineContext";
20
+ import { ariaLabel, getConditionalClasses } from "../utils/period";
21
+ import { PeriodProps } from "./index";
22
+
23
+ interface TimelineClickablePeriodProps extends PeriodProps {
24
+ onSelectPeriod?: (
25
+ e: React.MouseEvent<Element, MouseEvent> | React.KeyboardEvent<Element>
26
+ ) => void;
27
+ isActive?: boolean;
28
+ periodRef: React.ForwardedRef<HTMLButtonElement>;
29
+ }
30
+
31
+ const ClickablePeriod = React.memo(
32
+ ({
33
+ onSelectPeriod,
34
+ start,
35
+ end,
36
+ status,
37
+ cropped,
38
+ direction,
39
+ left,
40
+ width,
41
+ icon,
42
+ children,
43
+ isActive,
44
+ statusLabel,
45
+ restProps,
46
+ periodRef,
47
+ }: TimelineClickablePeriodProps) => {
48
+ const [open, setOpen] = useState(false);
49
+ const { index } = useRowContext();
50
+ const { firstFocus } = usePeriodContext();
51
+ const { initiate, addFocusable } = useTimelineContext();
52
+ const arrowRef = useRef<HTMLDivElement | null>(null);
53
+
54
+ const {
55
+ context,
56
+ placement,
57
+ middlewareData: { arrow: { x: arrowX, y: arrowY } = {} },
58
+ refs,
59
+ floatingStyles,
60
+ } = useFloating({
61
+ placement: "top",
62
+ open: open,
63
+ onOpenChange: setOpen,
64
+ middleware: [
65
+ offset(16),
66
+ shift(),
67
+ flip({ padding: 5, fallbackPlacements: ["bottom", "top"] }),
68
+ flArrow({ element: arrowRef, padding: 5 }),
69
+ ],
70
+ whileElementsMounted: autoUpdate,
71
+ });
72
+
73
+ const { getFloatingProps, getReferenceProps } = useInteractions([
74
+ useHover(context, {
75
+ handleClose: safePolygon(),
76
+ restMs: 25,
77
+ delay: { open: 1000 },
78
+ }),
79
+ useFocus(context),
80
+ useDismiss(context),
81
+ ]);
82
+
83
+ const mergedRef = useMemo(
84
+ () => mergeRefs([refs.setReference, periodRef]),
85
+ [periodRef, refs.setReference]
86
+ );
87
+
88
+ useEventListener(
89
+ "focusin",
90
+ useCallback(
91
+ (e: FocusEvent) => {
92
+ if (
93
+ ![refs.domReference.current, refs?.floating?.current].some(
94
+ (element) => element?.contains(e.target as Node)
95
+ )
96
+ ) {
97
+ open && setOpen(false);
98
+ }
99
+ },
100
+ [open, refs.domReference, refs?.floating]
101
+ )
102
+ );
103
+
104
+ const staticSide = {
105
+ top: "bottom",
106
+ right: "left",
107
+ bottom: "top",
108
+ left: "right",
109
+ }[placement.split("-")[0]];
110
+
111
+ return (
112
+ <>
113
+ <button
114
+ {...restProps}
115
+ type="button"
116
+ ref={(r) => {
117
+ firstFocus && addFocusable(r, index);
118
+ mergedRef(r);
119
+ }}
120
+ aria-label={ariaLabel(start, end, status, statusLabel)}
121
+ className={cl(
122
+ "navds-timeline__period--clickable",
123
+ getConditionalClasses(cropped, direction, status),
124
+ restProps?.className,
125
+ {
126
+ "navds-timeline__period--selected": isActive,
127
+ }
128
+ )}
129
+ aria-expanded={children ? open : undefined}
130
+ aria-current={isActive || undefined}
131
+ {...getReferenceProps({
132
+ onFocus: () => {
133
+ initiate(index);
134
+ },
135
+ onKeyDown: (e) => {
136
+ restProps?.onKeydown?.(e);
137
+ if (e.key === "Enter") {
138
+ setOpen((prev) => !prev);
139
+ }
140
+ if (e.key === " ") {
141
+ onSelectPeriod?.(e);
142
+ setOpen(false);
143
+ }
144
+ },
145
+ style: {
146
+ width: `${width}%`,
147
+ [direction]: `${left}%`,
148
+ },
149
+ onClick: (e) => {
150
+ restProps?.onClick?.(e);
151
+ if (e.detail === 0) {
152
+ return;
153
+ }
154
+ onSelectPeriod?.(e);
155
+ },
156
+ })}
157
+ >
158
+ <span className="navds-timeline__period--inner">{icon}</span>
159
+ </button>
160
+ {children && (
161
+ <div
162
+ className="navds-timeline__popover"
163
+ data-placement={placement}
164
+ aria-hidden={!open}
165
+ ref={refs.setFloating}
166
+ {...getFloatingProps({
167
+ tabIndex: -1,
168
+ })}
169
+ style={{
170
+ ...floatingStyles,
171
+ display: open ? undefined : "none",
172
+ }}
173
+ >
174
+ <div className="navds-timeline__popover-content">{children}</div>
175
+ <div
176
+ ref={(node) => {
177
+ arrowRef.current = node;
178
+ }}
179
+ style={{
180
+ ...(arrowX != null ? { left: arrowX } : {}),
181
+ ...(arrowY != null ? { top: arrowY } : {}),
182
+ ...(staticSide ? { [staticSide]: "-0.5rem" } : {}),
183
+ }}
184
+ className="navds-timeline__popover-arrow"
185
+ />
186
+ </div>
187
+ )}
188
+ </>
189
+ );
190
+ }
191
+ );
192
+
193
+ export default ClickablePeriod;
@@ -0,0 +1,46 @@
1
+ import React from "react";
2
+ import { ariaLabel, getConditionalClasses } from "../utils/period";
3
+ import { PeriodProps } from "./index";
4
+ import cl from "clsx";
5
+
6
+ interface TimelineNonClickablePeriodProps extends PeriodProps {
7
+ periodRef?: React.ForwardedRef<HTMLDivElement>;
8
+ }
9
+
10
+ const NonClickablePeriod = ({
11
+ start,
12
+ end,
13
+ status,
14
+ cropped,
15
+ direction,
16
+ left,
17
+ width,
18
+ icon,
19
+ statusLabel,
20
+ restProps,
21
+ periodRef,
22
+ }: TimelineNonClickablePeriodProps) => {
23
+ return (
24
+ <div
25
+ ref={periodRef}
26
+ {...restProps}
27
+ className={cl(
28
+ getConditionalClasses(cropped, direction, status),
29
+ restProps?.classname
30
+ )}
31
+ style={{
32
+ width: `${width}%`,
33
+ [direction]: `${left}%`,
34
+ }}
35
+ >
36
+ <span className="navds-timeline__period--inner">
37
+ {icon}
38
+ <span className="sr-only">
39
+ {ariaLabel(start, end, status, statusLabel)}
40
+ </span>
41
+ </span>
42
+ </div>
43
+ );
44
+ };
45
+
46
+ export default NonClickablePeriod;
@@ -0,0 +1,130 @@
1
+ import React, { forwardRef } from "react";
2
+ import { usePeriodContext } from "../hooks/usePeriodContext";
3
+ import { useRowContext } from "../hooks/useRowContext";
4
+ import { TimelineComponentTypes } from "../utils/types.internal";
5
+ import ClickablePeriod from "./ClickablePeriod";
6
+ import NonClickablePeriod from "./NonClickablePeriod";
7
+
8
+ export interface TimelinePeriodProps
9
+ extends React.HTMLAttributes<HTMLDivElement> {
10
+ /**
11
+ * Period start date.
12
+ */
13
+ start: Date;
14
+ /**
15
+ * Period end date.
16
+ */
17
+ end: Date;
18
+ /**
19
+ * Icon for easier visual identification.
20
+ */
21
+ icon?: React.ReactNode;
22
+ /**
23
+ * Period status.
24
+ * @default "neutral"
25
+ */
26
+ status?: "success" | "warning" | "danger" | "info" | "neutral";
27
+ /**
28
+ * Status label for screen-readers
29
+ * e.g "Sykemeldt", "foreldrepermisjon"
30
+ */
31
+ statusLabel?: string;
32
+ /**
33
+ * Callback when selecting a period.
34
+ */
35
+ onSelectPeriod?: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
36
+ /**
37
+ * Content displayed in Popover on click.
38
+ */
39
+ children?: React.ReactNode;
40
+ /**
41
+ * Visual active inidcation on period
42
+ * @note Make sure only one period is active at a time
43
+ */
44
+ isActive?: boolean;
45
+ }
46
+
47
+ export interface PeriodProps {
48
+ start: Date;
49
+ end: Date;
50
+ status: string;
51
+ cropped: string;
52
+ direction: string;
53
+ width: Number;
54
+ left: Number;
55
+ icon?: React.ReactNode;
56
+ children?: React.ReactNode;
57
+ statusLabel?: string;
58
+ restProps?: any;
59
+ }
60
+
61
+ export interface PeriodType
62
+ extends React.ForwardRefExoticComponent<
63
+ TimelinePeriodProps &
64
+ React.RefAttributes<HTMLDivElement | HTMLButtonElement>
65
+ > {
66
+ componentType: TimelineComponentTypes;
67
+ }
68
+
69
+ export const Period = forwardRef<HTMLDivElement, TimelinePeriodProps>(
70
+ ({ icon }, ref) => {
71
+ const { periods } = useRowContext();
72
+ const { periodId, restProps } = usePeriodContext();
73
+
74
+ const period = periods.find((p) => p.id === periodId);
75
+
76
+ if (!period) {
77
+ return <></>;
78
+ }
79
+ const {
80
+ start,
81
+ endInclusive,
82
+ width,
83
+ horizontalPosition,
84
+ status = "neutral",
85
+ onSelectPeriod,
86
+ cropped,
87
+ direction,
88
+ children,
89
+ isActive,
90
+ statusLabel,
91
+ } = period;
92
+
93
+ return onSelectPeriod || children ? (
94
+ <ClickablePeriod
95
+ periodRef={ref as React.ForwardedRef<HTMLButtonElement>}
96
+ start={start}
97
+ end={endInclusive}
98
+ status={status}
99
+ onSelectPeriod={onSelectPeriod}
100
+ cropped={cropped || ""}
101
+ direction={direction}
102
+ width={width}
103
+ left={horizontalPosition}
104
+ icon={icon}
105
+ children={children}
106
+ isActive={isActive}
107
+ statusLabel={statusLabel}
108
+ restProps={restProps}
109
+ />
110
+ ) : (
111
+ <NonClickablePeriod
112
+ periodRef={ref}
113
+ start={start}
114
+ end={endInclusive}
115
+ status={status}
116
+ cropped={cropped || ""}
117
+ direction={direction}
118
+ width={width}
119
+ left={horizontalPosition}
120
+ icon={icon}
121
+ statusLabel={statusLabel}
122
+ restProps={restProps}
123
+ />
124
+ );
125
+ }
126
+ ) as PeriodType;
127
+
128
+ Period.componentType = "period";
129
+
130
+ export default Period;