@vygruppen/spor-react 1.3.4 → 2.0.1

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 (196) hide show
  1. package/.turbo/turbo-build.log +12 -10
  2. package/CHANGELOG.md +31 -0
  3. package/dist/index.d.ts +6720 -27
  4. package/dist/index.js +14143 -140
  5. package/dist/index.mjs +13822 -27
  6. package/package.json +19 -31
  7. package/src/accordion/Accordion.test.tsx +20 -0
  8. package/src/accordion/Accordion.tsx +62 -0
  9. package/src/accordion/AccordionContext.tsx +27 -0
  10. package/src/accordion/Expandable.tsx +157 -0
  11. package/src/accordion/index.tsx +2 -0
  12. package/src/alert/AlertIcon.tsx +75 -0
  13. package/src/alert/BaseAlert.test.tsx +37 -0
  14. package/src/alert/BaseAlert.tsx +21 -0
  15. package/src/alert/ClosableAlert.test.tsx +37 -0
  16. package/src/alert/ClosableAlert.tsx +75 -0
  17. package/src/alert/ExpandableAlert.test.tsx +84 -0
  18. package/src/alert/ExpandableAlert.tsx +84 -0
  19. package/src/alert/StaticAlert.tsx +25 -0
  20. package/src/alert/index.tsx +3 -0
  21. package/src/button/Button.test.tsx +23 -0
  22. package/src/button/Button.tsx +162 -0
  23. package/src/button/ButtonGroup.tsx +43 -0
  24. package/src/button/CloseButton.tsx +63 -0
  25. package/src/button/FloatingActionButton.tsx +113 -0
  26. package/src/button/IconButton.tsx +63 -0
  27. package/src/button/index.tsx +5 -0
  28. package/src/card/Card.tsx +59 -0
  29. package/src/card/index.tsx +1 -0
  30. package/src/datepicker/Calendar.tsx +32 -0
  31. package/src/datepicker/CalendarCell.tsx +74 -0
  32. package/src/datepicker/CalendarGrid.tsx +76 -0
  33. package/src/datepicker/CalendarHeader.tsx +153 -0
  34. package/src/datepicker/CalendarNavigationButton.tsx +26 -0
  35. package/src/datepicker/CalendarTriggerButton.tsx +36 -0
  36. package/src/datepicker/DateField.tsx +51 -0
  37. package/src/datepicker/DatePicker.tsx +153 -0
  38. package/src/datepicker/DateRangePicker.tsx +171 -0
  39. package/src/datepicker/DateTimeSegment.tsx +56 -0
  40. package/src/datepicker/RangeCalendar.tsx +35 -0
  41. package/src/datepicker/StyledField.tsx +31 -0
  42. package/src/datepicker/TimeField.tsx +46 -0
  43. package/src/datepicker/TimePicker.test.tsx +74 -0
  44. package/src/datepicker/TimePicker.tsx +196 -0
  45. package/src/datepicker/index.tsx +4 -0
  46. package/src/datepicker/utils.ts +33 -0
  47. package/src/i18n/index.tsx +38 -0
  48. package/src/image/index.tsx +2 -0
  49. package/src/index.tsx +25 -26
  50. package/src/input/CardSelect.tsx +165 -0
  51. package/src/input/Checkbox.tsx +24 -0
  52. package/src/input/CheckboxGroup.tsx +43 -0
  53. package/src/input/ChoiceChip.tsx +102 -0
  54. package/src/input/Dialog.tsx +29 -0
  55. package/src/input/FormControl.tsx +11 -0
  56. package/src/input/FormErrorMessage.tsx +91 -0
  57. package/src/input/FormLabel.tsx +11 -0
  58. package/src/input/InfoSelect.tsx +209 -0
  59. package/src/input/Input.tsx +59 -0
  60. package/src/input/InputElement.tsx +45 -0
  61. package/src/input/ListBox.tsx +123 -0
  62. package/src/input/NativeSelect.tsx +38 -0
  63. package/src/input/PasswordInput.tsx +70 -0
  64. package/src/input/Popover.tsx +70 -0
  65. package/src/input/Radio.tsx +34 -0
  66. package/src/input/RadioGroup.tsx +47 -0
  67. package/src/input/SearchInput.tsx +89 -0
  68. package/src/input/Switch.tsx +40 -0
  69. package/src/input/Textarea.tsx +98 -0
  70. package/src/input/index.tsx +20 -0
  71. package/src/layout/Divider.tsx +26 -0
  72. package/src/layout/Stack.tsx +42 -0
  73. package/src/layout/index.tsx +28 -0
  74. package/src/linjetag/InfoTag.tsx +54 -0
  75. package/src/linjetag/LineIcon.tsx +44 -0
  76. package/src/linjetag/TravelTag.tsx +121 -0
  77. package/src/linjetag/icons.tsx +80 -0
  78. package/src/linjetag/index.tsx +3 -0
  79. package/src/linjetag/types.d.ts +24 -0
  80. package/src/link/TextLink.tsx +45 -0
  81. package/src/link/index.tsx +1 -0
  82. package/src/loader/ClientOnly.tsx +29 -0
  83. package/src/loader/ColorInlineLoader.tsx +27 -0
  84. package/src/loader/ColorSpinner.tsx +44 -0
  85. package/src/loader/ContentLoader.tsx +27 -0
  86. package/src/loader/DarkFullScreenLoader.tsx +23 -0
  87. package/src/loader/DarkInlineLoader.tsx +25 -0
  88. package/src/loader/DarkSpinner.tsx +43 -0
  89. package/src/loader/LightFullScreenLoader.tsx +23 -0
  90. package/src/loader/LightInlineLoader.tsx +25 -0
  91. package/src/loader/LightSpinner.tsx +41 -0
  92. package/src/loader/Lottie.tsx +10 -0
  93. package/src/loader/ProgressBar.tsx +128 -0
  94. package/src/loader/ProgressLoader.tsx +140 -0
  95. package/src/loader/Skeleton.tsx +16 -0
  96. package/src/loader/SkeletonCircle.tsx +13 -0
  97. package/src/loader/SkeletonText.tsx +10 -0
  98. package/src/loader/index.tsx +14 -0
  99. package/src/loader/useHydrated.tsx +34 -0
  100. package/src/loader/useRotatingLabel.tsx +22 -0
  101. package/src/logo/VyLogo.tsx +101 -0
  102. package/src/logo/index.tsx +1 -0
  103. package/src/media-controller/JumpButton.tsx +69 -0
  104. package/src/media-controller/PlayPauseButton.tsx +67 -0
  105. package/src/media-controller/SkipButton.tsx +66 -0
  106. package/src/media-controller/icons.tsx +80 -0
  107. package/src/media-controller/index.test.tsx +59 -0
  108. package/src/media-controller/index.tsx +3 -0
  109. package/src/modal/Drawer.tsx +122 -0
  110. package/src/modal/Modal.tsx +15 -0
  111. package/src/modal/ModalHeader.tsx +31 -0
  112. package/src/modal/SimpleDrawer.tsx +44 -0
  113. package/src/modal/index.tsx +4 -0
  114. package/src/popover/PopoverWizardBody.tsx +91 -0
  115. package/src/popover/SimplePopover.tsx +75 -0
  116. package/src/popover/WizardPopover.tsx +61 -0
  117. package/src/popover/index.tsx +23 -0
  118. package/src/provider/SporProvider.tsx +67 -0
  119. package/src/provider/index.tsx +1 -0
  120. package/src/stepper/Stepper.tsx +115 -0
  121. package/src/stepper/StepperContext.tsx +55 -0
  122. package/src/stepper/StepperStep.tsx +48 -0
  123. package/src/stepper/index.tsx +2 -0
  124. package/src/tab/Tabs.tsx +20 -0
  125. package/src/tab/index.tsx +9 -0
  126. package/src/table/Table.tsx +58 -0
  127. package/src/table/index.tsx +19 -0
  128. package/src/theme/components/accordion.ts +143 -0
  129. package/src/theme/components/alert.ts +59 -0
  130. package/src/theme/components/badge.ts +109 -0
  131. package/src/theme/components/button.ts +217 -0
  132. package/src/theme/components/card-select.ts +158 -0
  133. package/src/theme/components/card.ts +174 -0
  134. package/src/theme/components/checkbox.ts +90 -0
  135. package/src/theme/components/choice-chip.ts +79 -0
  136. package/src/theme/components/close-button.ts +56 -0
  137. package/src/theme/components/code.ts +17 -0
  138. package/src/theme/components/datepicker.ts +194 -0
  139. package/src/theme/components/drawer.ts +92 -0
  140. package/src/theme/components/fab.ts +111 -0
  141. package/src/theme/components/form-label.ts +17 -0
  142. package/src/theme/components/form.ts +27 -0
  143. package/src/theme/components/index.ts +34 -0
  144. package/src/theme/components/info-select.ts +91 -0
  145. package/src/theme/components/info-tag.ts +49 -0
  146. package/src/theme/components/input.ts +97 -0
  147. package/src/theme/components/line-icon.ts +121 -0
  148. package/src/theme/components/link.ts +155 -0
  149. package/src/theme/components/listbox.ts +52 -0
  150. package/src/theme/components/media-controller-button.ts +134 -0
  151. package/src/theme/components/modal.ts +93 -0
  152. package/src/theme/components/popover.ts +63 -0
  153. package/src/theme/components/radio.ts +64 -0
  154. package/src/theme/components/select.ts +52 -0
  155. package/src/theme/components/skeleton.ts +40 -0
  156. package/src/theme/components/stepper.ts +230 -0
  157. package/src/theme/components/switch.ts +227 -0
  158. package/src/theme/components/table.ts +163 -0
  159. package/src/theme/components/tabs.ts +282 -0
  160. package/src/theme/components/textarea.ts +14 -0
  161. package/src/theme/components/toast.ts +28 -0
  162. package/src/theme/components/travel-tag.ts +267 -0
  163. package/src/theme/font-faces.ts +66 -0
  164. package/src/theme/foundations/borders.ts +11 -0
  165. package/src/theme/foundations/breakpoints.ts +9 -0
  166. package/src/theme/foundations/colors.ts +10 -0
  167. package/src/theme/foundations/config.ts +5 -0
  168. package/src/theme/foundations/fontSizes.ts +29 -0
  169. package/src/theme/foundations/fontWeights.ts +5 -0
  170. package/src/theme/foundations/fonts.ts +7 -0
  171. package/src/theme/foundations/index.ts +14 -0
  172. package/src/theme/foundations/lineHeights.ts +5 -0
  173. package/src/theme/foundations/radii.ts +12 -0
  174. package/src/theme/foundations/shadows.ts +8 -0
  175. package/src/theme/foundations/sizes.ts +34 -0
  176. package/src/theme/foundations/spacing.ts +30 -0
  177. package/src/theme/foundations/textStyles.ts +60 -0
  178. package/src/theme/foundations/zIndices.ts +17 -0
  179. package/src/theme/index.ts +14 -0
  180. package/src/theme/utils/box-shadow-utils.ts +44 -0
  181. package/src/theme/utils/focus-utils.ts +16 -0
  182. package/src/toast/ActionToast.test.tsx +22 -0
  183. package/src/toast/ActionToast.tsx +28 -0
  184. package/src/toast/BaseToast.test.tsx +27 -0
  185. package/src/toast/BaseToast.tsx +75 -0
  186. package/src/toast/ClosableToast.test.tsx +17 -0
  187. package/src/toast/ClosableToast.tsx +40 -0
  188. package/src/toast/index.tsx +1 -0
  189. package/src/toast/useToast.tsx +99 -0
  190. package/src/typography/Badge.tsx +68 -0
  191. package/src/typography/Code.tsx +32 -0
  192. package/src/typography/Heading.tsx +32 -0
  193. package/src/typography/Text.tsx +26 -0
  194. package/src/typography/index.tsx +4 -0
  195. package/src/util/externals.tsx +23 -0
  196. package/src/util/index.tsx +1 -0
@@ -0,0 +1,14 @@
1
+ export * from "./ColorInlineLoader";
2
+ export * from "./ColorSpinner";
3
+ export * from "./ContentLoader";
4
+ export * from "./DarkFullScreenLoader";
5
+ export * from "./DarkInlineLoader";
6
+ export * from "./DarkSpinner";
7
+ export * from "./LightFullScreenLoader";
8
+ export * from "./LightInlineLoader";
9
+ export * from "./LightSpinner";
10
+ export * from "./ProgressBar";
11
+ export * from "./ProgressLoader";
12
+ export * from "./Skeleton";
13
+ export * from "./SkeletonCircle";
14
+ export * from "./SkeletonText";
@@ -0,0 +1,34 @@
1
+ import { useEffect, useState } from "react";
2
+
3
+ // Lifted from remix-utils
4
+ // https://github.com/sergiodxa/remix-utils/blob/main/src/react/use-hydrated.ts
5
+
6
+ let hydrating = true;
7
+
8
+ /**
9
+ * Return a boolean indicating if the JS has been hydrated already.
10
+ * When doing Server-Side Rendering, the result will always be false.
11
+ * When doing Client-Side Rendering, the result will always be false on the
12
+ * first render and true from then on. Even if a new component renders it will
13
+ * always start with true.
14
+ *
15
+ * Example: Disable a button that needs JS to work.
16
+ * ```tsx
17
+ * let hydrated = useHydrated();
18
+ * return (
19
+ * <button type="button" disabled={!hydrated} onClick={doSomethingCustom}>
20
+ * Click me
21
+ * </button>
22
+ * );
23
+ * ```
24
+ */
25
+ export function useHydrated() {
26
+ let [hydrated, setHydrated] = useState(() => !hydrating);
27
+
28
+ useEffect(function hydrate() {
29
+ hydrating = false;
30
+ setHydrated(true);
31
+ }, []);
32
+
33
+ return hydrated;
34
+ }
@@ -0,0 +1,22 @@
1
+ import { useInterval } from "@chakra-ui/react";
2
+ import { useMemo, useState } from "react";
3
+
4
+ type UseRotatingLabelArgs = {
5
+ label?: string | string[];
6
+ delay: number;
7
+ };
8
+ /** Returns a label from a set of labels */
9
+ export const useRotatingLabel = ({ label, delay }: UseRotatingLabelArgs) => {
10
+ const loadingTextArray = useMemo(
11
+ () => (Array.isArray(label) ? label : [label]),
12
+ [label]
13
+ );
14
+ const [currentLoadingTextIndex, setCurrentLoadingTextIndex] = useState(0);
15
+
16
+ useInterval(() => {
17
+ setCurrentLoadingTextIndex(
18
+ (prevIndex) => (prevIndex + 1) % loadingTextArray.length
19
+ );
20
+ }, delay);
21
+ return loadingTextArray[currentLoadingTextIndex];
22
+ };
@@ -0,0 +1,101 @@
1
+ import { Box, BoxProps } from "@chakra-ui/react";
2
+ import React, { useId } from "react";
3
+
4
+ export type VyLogoProps = {
5
+ /** The color of the logo
6
+ *
7
+ * Use `"light"` when the logo is used on a light background.
8
+ * Use `"dark"` when the logo is used on a dark background.
9
+ */
10
+ colorScheme: "light" | "dark";
11
+ } & BoxProps;
12
+ export const VyLogo = ({ colorScheme, ...boxProps }: VyLogoProps) => {
13
+ // These colors should not be tokenized, as they are logo specific.
14
+ const mainColor = colorScheme === "light" ? "#1d211c" : "#ffffff";
15
+ const accentColor = colorScheme === "light" ? "#138c6e" : "#ffffff";
16
+ const id = useId();
17
+ return (
18
+ <Box as="svg" viewBox="0 0 107 54" {...boxProps}>
19
+ <title>Vy logo</title>
20
+ <path
21
+ fillRule="evenodd"
22
+ clipRule="evenodd"
23
+ d="M79.97 33.44a.04.04 0 0 0 0-.08 5.76 5.76 0 0 1-2.32-.85c-1.56-1-2.79-2.9-3.83-6.07L68.14 7.16c-1.18-4.04-3.42-5.85-5.48-6.6a8.36 8.36 0 0 0-2.88-.52h-1.44a.04.04 0 0 0 0 .08c.57.09 1.18.24 1.8.5 1.92.8 3.92 2.63 5.06 6.54l5.61 19.07c1.06 3.3 2.31 5.27 3.92 6.3 1.01.64 2.17.9 3.5.9h1.74Z"
24
+ fill={mainColor}
25
+ />
26
+ <mask
27
+ id={`${id}-a`}
28
+ style={{ maskType: "alpha" }}
29
+ maskUnits="userSpaceOnUse"
30
+ x="0"
31
+ y="0"
32
+ width="94"
33
+ height="54"
34
+ >
35
+ <path
36
+ fillRule="evenodd"
37
+ clipRule="evenodd"
38
+ d="M0 .03h93.26v53.65H0V.03Z"
39
+ fill={mainColor}
40
+ />
41
+ </mask>
42
+ <g
43
+ mask={`url(#${id}-a)`}
44
+ fillRule="evenodd"
45
+ clipRule="evenodd"
46
+ fill={mainColor}
47
+ >
48
+ <path d="M84.57 33.44a.04.04 0 0 0 .01-.08c-2.34-.3-3.85-3.59-4.68-6.38-.88-2.93-4.04-13.63-5.92-19.82C72.08.94 66.2.05 63.54.04a.04.04 0 0 0-.01.08c2.49.34 6.02 1.85 7.55 7.04 1.34 4.55 5.6 19.03 5.76 19.51 1.02 3.03 2.22 4.85 3.73 5.83a6.3 6.3 0 0 0 3.54.94h.46ZM23.28 53.68h-.72c-2.12 0-4.68-1.08-6.09-6.04L3.8 4.37C3.04 1.77 1.93.47.03.15A.04.04 0 0 1 .04.07h1.9c2.54 0 3.92 1.27 4.8 4.3 0 0 11.72 39.78 12.79 43.54.78 2.78 1.7 4.67 3.13 5.43.23.13.42.2.62.26a.04.04 0 0 1 0 .08ZM18.99 5.99C17.77 1.79 15.87.04 12.37.04h-1.71a.04.04 0 0 0 0 .08c2.73.39 4.32 2.19 5.39 5.87 0 0 10.49 35.72 11.85 40.4l1.44-4.87L18.99 6Z" />
49
+ <path d="M24.26 53.68h1.24c1.57 0 2.69-.41 3.52-1.1 1.37-1.1 1.99-2.93 2.56-4.86.09-.29 11.2-37.95 11.59-39.42 1.32-4.97 4.27-7.13 7.43-7.9a12.2 12.2 0 0 1 1.58-.28.04.04 0 0 0 0-.08h-1.4c-4.15 0-8.8 1.65-10.56 8.26-.63 2.38-11.5 39.13-11.58 39.42-.57 1.93-1.23 3.96-2.59 5.07-.56.45-1.06.7-1.8.81a.04.04 0 0 0 0 .08ZM81.3 27.76l6.53-21.78C88.89 2.3 90.49.5 93.23.11a.04.04 0 0 0-.01-.08H91.5c-3.5 0-5.4 1.76-6.62 5.95l-5.05 16.97s1.33 4.46 1.46 4.8Z" />
50
+ </g>
51
+ <path
52
+ fillRule="evenodd"
53
+ clipRule="evenodd"
54
+ d="M98.49.07h-1.82c-2.98 0-4.6 1.49-5.63 5.06l-6.52 21.79a18.22 18.22 0 0 1-1.67 3.96c.57.78 1.17 1.26 1.76 1.38 1.13-.96 2.06-2.75 2.89-5.46l6.49-21.67c.9-3.1 2.23-4.63 4.5-4.98a.04.04 0 0 0 0-.08Z"
55
+ fill={accentColor}
56
+ />
57
+ <path
58
+ fillRule="evenodd"
59
+ clipRule="evenodd"
60
+ d="M85.25 34.53h-2.93L78.58 46.9c-1.82 6.1 1.05 6.73 2.15 6.73h2.31a.04.04 0 0 0 .01-.09c-1.25-.3-2.96-1.6-1.45-6.64l3.65-12.37ZM102.35.11c.02 0 .03-.02.03-.04a.04.04 0 0 0-.04-.04h-.4c-2.54 0-3.92 1.27-4.8 4.3 0 0-5 16.82-6.57 22.03-1.57 5.2-2.65 6.6-4.78 6.97l-.11.03a.04.04 0 0 0 0 .08h1.45c3.72 0 5.1-2.48 6.41-6.84l7.5-25.07c.19-.6.7-1.23 1.31-1.42Z"
61
+ fill={mainColor}
62
+ />
63
+ <mask
64
+ id={`${id}-b`}
65
+ style={{ maskType: "alpha" }}
66
+ maskUnits="userSpaceOnUse"
67
+ x="29"
68
+ y="0"
69
+ width="78"
70
+ height="54"
71
+ >
72
+ <path
73
+ fillRule="evenodd"
74
+ clipRule="evenodd"
75
+ d="M29.55.04H106v53.64H29.55V.04Z"
76
+ fill={mainColor}
77
+ />
78
+ </mask>
79
+ <g mask={`url(#${id}-b)`} fillRule="evenodd" clipRule="evenodd">
80
+ <path
81
+ d="m88.2 34.45-3.96 13.46c-1.54 5.18.9 5.72 1.83 5.72h2.26a.04.04 0 0 0 0-.09c-1.05-.27-2.44-1.4-1.18-5.63l4.43-15.02c-.7.71-1.88 1.37-3.38 1.56ZM93.71 53.63c.02 0 .04-.02.04-.05a.04.04 0 0 0-.03-.04c-.9-.25-2.02-1.24-.96-4.77 0 0 12.4-42.2 13.08-44.6a3.1 3.1 0 0 0-2.47-4.1c-.02 0-.06-.02-.07.01-.01.04.03.06.04.06.3.16.63.52.45 1.14L89.84 48.77c-1.32 4.4.76 4.86 1.55 4.86h2.32ZM74.1 33.44a.04.04 0 0 0 0-.08 5.77 5.77 0 0 1-2.3-.83c-1.64-1.04-2.9-3.06-3.98-6.5-.1-.29-5.37-18.25-5.55-18.87-1.12-3.8-2.91-5.63-4.68-6.46a7.06 7.06 0 0 0-3.04-.66H53a.04.04 0 0 0-.02.08c.3.05 4.32.17 6.35 7.04 2.03 6.86 4.46 15.07 5.28 17.97 1.77 6.26 4.07 8.3 7.75 8.3h1.73Z"
82
+ fill={mainColor}
83
+ />
84
+ <path
85
+ d="M55.43 2.4c-1.48 1.27-2.7 3.16-3.44 5.9-.07.29-11.08 37.74-11.32 38.55-.93 3.12-1.7 6.21-5.2 6.75a.04.04 0 0 0 0 .08h.7l.6-.01c3.56-.14 5.33-1.66 6.85-6.82L54.94 8.3c.44-1.44.96-3.08 1.7-4.52-.15-.25-.7-.96-1.2-1.38Z"
86
+ fill={mainColor}
87
+ />
88
+ <path
89
+ d="M53.78 1.44a4.64 4.64 0 0 0-4.17.93A10.79 10.79 0 0 0 46.19 8L34.53 47.72c-.58 1.93-1.2 3.75-2.56 4.87-.62.5-1.4.86-2.39 1.01a.04.04 0 0 0 0 .08h1.8c1.57 0 2.69-.41 3.53-1.1 1.36-1.1 1.98-2.93 2.56-4.86L49.05 8.3c.73-2.74 1.95-4.63 3.43-5.9.43-.36.88-.68 1.35-.95l-.05-.01Z"
90
+ fill={accentColor}
91
+ />
92
+ </g>
93
+ <path
94
+ fillRule="evenodd"
95
+ clipRule="evenodd"
96
+ d="M26.55 50.33a9.09 9.09 0 0 1-1.24-2.7c-.44-1.54-12.46-42.5-12.46-42.5C11.82 1.56 10.2.07 7.22.07H5.4a.04.04 0 0 0 0 .08C7.67.5 9 2.04 9.9 5.13l12.46 42.5c.65 2.28 1.53 3.74 2.5 4.65.7-.4 1.29-1.05 1.69-1.95Z"
97
+ fill={mainColor}
98
+ />
99
+ </Box>
100
+ );
101
+ };
@@ -0,0 +1 @@
1
+ export * from "./VyLogo";
@@ -0,0 +1,69 @@
1
+ import { BoxProps, Center, useMultiStyleConfig } from "@chakra-ui/react";
2
+ import React from "react";
3
+ import { createTexts, useTranslation } from "..";
4
+ import { JumpBackwardIcon, JumpForwardIcon } from "./icons";
5
+
6
+ type JumpButtonProps = BoxProps & {
7
+ onClick: () => void;
8
+ "aria-label"?: string;
9
+ isDisabled?: boolean;
10
+ direction: "backward" | "forward";
11
+ size: "sm" | "lg";
12
+ };
13
+
14
+ /**
15
+ * A jump button.
16
+ *
17
+ * Intended to jump 15 seconds forward or backward in a video, podcast, audiobook or similar.
18
+ *
19
+ * Specify what direction you want to skip with the `direction` prop.
20
+ *
21
+ * ```tsx
22
+ * <JumpButton direction="forward" onClick={onGoForward} />
23
+ * ```
24
+ */
25
+ export const JumpButton = ({
26
+ direction,
27
+ isDisabled,
28
+ size = "sm",
29
+ ...props
30
+ }: JumpButtonProps) => {
31
+ const { t } = useTranslation();
32
+ const styles = useMultiStyleConfig("MediaControllerButton", {
33
+ variant: "jumpSkip",
34
+ size,
35
+ });
36
+
37
+ return (
38
+ <Center
39
+ as="button"
40
+ sx={styles.container}
41
+ aria-label={
42
+ direction === "forward" ? t(texts.forward) : t(texts.backward)
43
+ }
44
+ disabled={isDisabled}
45
+ {...props}
46
+ >
47
+ {direction === "forward" ? (
48
+ <JumpForwardIcon sx={styles.icon} />
49
+ ) : (
50
+ <JumpBackwardIcon sx={styles.icon} />
51
+ )}
52
+ </Center>
53
+ );
54
+ };
55
+
56
+ const texts = createTexts({
57
+ forward: {
58
+ nb: "15 sekunder frem",
59
+ nn: "15 sekunder fram",
60
+ sv: "15 sekunder framåt",
61
+ en: "15 seconds forward",
62
+ },
63
+ backward: {
64
+ nb: "15 sekunder tilbake",
65
+ nn: "15 sekunder tilbake",
66
+ sv: "15 sekunder tillbaka",
67
+ en: "15 seconds backward",
68
+ },
69
+ });
@@ -0,0 +1,67 @@
1
+ import { BoxProps, Center, useMultiStyleConfig } from "@chakra-ui/react";
2
+ import React from "react";
3
+ import { createTexts, useTranslation } from "..";
4
+ import { PauseIcon, PlayIcon } from "./icons";
5
+
6
+ type PlayPauseButtonProps = BoxProps & {
7
+ onClick: () => void;
8
+ "aria-label"?: string;
9
+ isDisabled?: boolean;
10
+ isPlaying: boolean;
11
+ size: "sm" | "lg";
12
+ };
13
+
14
+ /**
15
+ * A playback button.
16
+ *
17
+ * Intended to start or pause playback of a video, podcast, audiobook or similar.
18
+ *
19
+ * Specify the current playing state with `isPlaying`.
20
+ *
21
+ * ```tsx
22
+ * <PlayPauseButton isPlaying={isPlaying} onClick={onPlaybackClick} />
23
+ * ```
24
+ */
25
+ export const PlayPauseButton = ({
26
+ size = "lg",
27
+ isPlaying,
28
+ isDisabled,
29
+ ...props
30
+ }: PlayPauseButtonProps) => {
31
+ const { t } = useTranslation();
32
+ const styles = useMultiStyleConfig("MediaControllerButton", {
33
+ variant: "play",
34
+ size,
35
+ });
36
+
37
+ return (
38
+ <Center
39
+ as="button"
40
+ sx={styles.container}
41
+ aria-label={isPlaying ? t(texts.pause) : t(texts.play)}
42
+ disabled={isDisabled}
43
+ {...props}
44
+ >
45
+ {isPlaying ? (
46
+ <PauseIcon sx={styles.icon} />
47
+ ) : (
48
+ <PlayIcon sx={styles.icon} />
49
+ )}
50
+ </Center>
51
+ );
52
+ };
53
+
54
+ const texts = createTexts({
55
+ pause: {
56
+ nb: "Pause",
57
+ nn: "Pause",
58
+ sv: "Paus",
59
+ en: "Pause",
60
+ },
61
+ play: {
62
+ nb: "Spill av",
63
+ nn: "Spill av",
64
+ sv: "Spel av",
65
+ en: "Play",
66
+ },
67
+ });
@@ -0,0 +1,66 @@
1
+ import { BoxProps, Center, useMultiStyleConfig } from "@chakra-ui/react";
2
+ import React from "react";
3
+ import { createTexts, useTranslation } from "..";
4
+ import { SkipNextIcon, SkipPreviousIcon } from "./icons";
5
+
6
+ type SkipButtonProps = BoxProps & {
7
+ onClick: () => void;
8
+ "aria-label"?: string;
9
+ isDisabled?: boolean;
10
+ direction: "backward" | "forward";
11
+ size: "sm" | "lg";
12
+ };
13
+ /**
14
+ * A skip button.
15
+ *
16
+ * Intended to skip to the next section, chapter og similar.
17
+ *
18
+ * Specify what direction you want to skip with the `direction` prop.
19
+ *
20
+ * ```tsx
21
+ * <SkipButton direction="forward" onClick={onNextChapter} />
22
+ * ```
23
+ */
24
+ export const SkipButton = ({
25
+ direction,
26
+ isDisabled,
27
+ size = "sm",
28
+ ...props
29
+ }: SkipButtonProps) => {
30
+ const { t } = useTranslation();
31
+ const styles = useMultiStyleConfig("MediaControllerButton", {
32
+ variant: "jumpSkip",
33
+ size,
34
+ });
35
+
36
+ return (
37
+ <Center
38
+ as="button"
39
+ sx={styles.container}
40
+ aria-label={direction === "forward" ? t(texts.next) : t(texts.previous)}
41
+ disabled={isDisabled}
42
+ {...props}
43
+ >
44
+ {direction === "forward" ? (
45
+ <SkipNextIcon sx={styles.icon} />
46
+ ) : (
47
+ <SkipPreviousIcon sx={styles.icon} />
48
+ )}
49
+ </Center>
50
+ );
51
+ };
52
+
53
+ const texts = createTexts({
54
+ next: {
55
+ nb: "Neste",
56
+ nn: "Neste",
57
+ sv: "Nästa",
58
+ en: "Next",
59
+ },
60
+ previous: {
61
+ nb: "Forrige",
62
+ nn: "Forrige",
63
+ sv: "Föregående",
64
+ en: "Previous",
65
+ },
66
+ });
@@ -0,0 +1,80 @@
1
+ import { Box, BoxProps } from "@chakra-ui/react";
2
+ import * as React from "react";
3
+
4
+ export const JumpForwardIcon = (props: BoxProps) => (
5
+ <Box as="svg" {...props} viewBox="0 0 32 30" fill="none">
6
+ <path
7
+ fillRule="evenodd"
8
+ clipRule="evenodd"
9
+ d="M22.388 7.5C20.567 5.967 18.251 5 15.75 5 9.951 5 5.25 9.701 5.25 15.5S9.951 26 15.75 26s10.5-4.701 10.5-10.5a1 1 0 1 1 2 0c0 6.904-5.596 12.5-12.5 12.5s-12.5-5.596-12.5-12.5S8.846 3 15.75 3c3.206 0 6.11 1.31 8.304 3.3l.206-1.441a1 1 0 0 1 1.98.282L25.617 9.5H21.25a1 1 0 1 1 0-2h1.138Z"
10
+ fill="currentColor"
11
+ />
12
+ <path
13
+ fillRule="evenodd"
14
+ clipRule="evenodd"
15
+ d="M19.465 14.568c-.4-.232-.863-.348-1.392-.348-.655 0-1.195.172-1.62.516l.18-2.172h2.989c.256 0 .436-.016.54-.048.111-.032.168-.112.168-.24v-1.02h-.012c-.04.04-.117.064-.229.072a7.65 7.65 0 0 1-.515.012H15.42l-.36 4.464 1.273.12c.175-.16.384-.284.623-.372a2.11 2.11 0 0 1 .768-.144c.48 0 .856.136 1.128.408.28.264.42.624.42 1.08 0 .456-.155.832-.468 1.128-.311.296-.703.444-1.175.444a3.04 3.04 0 0 1-.936-.132 3.802 3.802 0 0 1-.756-.348c-.289-.176-.48-.34-.577-.492l-.636.888a.233.233 0 0 0-.047.12c0 .08.043.16.131.24.089.072.225.168.409.288.712.448 1.547.672 2.508.672.576 0 1.091-.124 1.547-.372a2.743 2.743 0 0 0 1.08-1.056 2.97 2.97 0 0 0 .384-1.5c0-.472-.111-.9-.335-1.284a2.314 2.314 0 0 0-.937-.924ZM13.338 19.224a2.954 2.954 0 0 1-.036-.552V11.34h-1.116a2.07 2.07 0 0 1-.576.768c-.248.208-.576.312-.984.312h-.144c-.136 0-.224-.016-.264-.048v.792c0 .128.052.208.156.24.112.032.296.048.552.048h.912v5.364c0 .248.028.424.084.528.056.104.172.156.348.156h1.248v-.012a.478.478 0 0 1-.18-.264Z"
16
+ fill="currentColor"
17
+ />
18
+ </Box>
19
+ );
20
+
21
+ export const JumpBackwardIcon = (props: BoxProps) => (
22
+ <Box as="svg" {...props} viewBox="0 0 32 30" fill="none">
23
+ <path
24
+ fillRule="evenodd"
25
+ clipRule="evenodd"
26
+ d="M9.612 7.5C11.433 5.967 13.749 5 16.25 5c5.799 0 10.5 4.701 10.5 10.5S22.049 26 16.25 26s-10.5-4.701-10.5-10.5a1 1 0 1 0-2 0c0 6.904 5.596 12.5 12.5 12.5s12.5-5.596 12.5-12.5S23.154 3 16.25 3c-3.206 0-6.11 1.31-8.304 3.3L7.74 4.86a1 1 0 0 0-1.98.282L6.383 9.5h4.367a1 1 0 1 0 0-2H9.612Z"
27
+ fill="currentColor"
28
+ />
29
+ <path
30
+ fillRule="evenodd"
31
+ clipRule="evenodd"
32
+ d="M19.965 14.568c-.4-.232-.863-.348-1.392-.348-.655 0-1.195.172-1.62.516l.18-2.172h2.989c.256 0 .436-.016.54-.048.111-.032.168-.112.168-.24v-1.02h-.012c-.04.04-.117.064-.229.072a7.65 7.65 0 0 1-.515.012H15.92l-.36 4.464 1.273.12c.175-.16.384-.284.623-.372a2.11 2.11 0 0 1 .768-.144c.48 0 .856.136 1.128.408.28.264.42.624.42 1.08 0 .456-.155.832-.468 1.128-.311.296-.703.444-1.175.444a3.04 3.04 0 0 1-.936-.132 3.802 3.802 0 0 1-.756-.348c-.288-.176-.48-.34-.576-.492l-.636.888a.233.233 0 0 0-.049.12c0 .08.045.16.133.24.088.072.223.168.407.288.713.448 1.548.672 2.508.672.576 0 1.092-.124 1.548-.372a2.743 2.743 0 0 0 1.08-1.056 2.97 2.97 0 0 0 .384-1.5c0-.472-.111-.9-.335-1.284a2.314 2.314 0 0 0-.937-.924ZM13.838 19.224a2.954 2.954 0 0 1-.036-.552V11.34h-1.116a2.07 2.07 0 0 1-.576.768c-.248.208-.576.312-.984.312h-.144c-.136 0-.224-.016-.264-.048v.792c0 .128.052.208.156.24.112.032.296.048.552.048h.912v5.364c0 .248.028.424.084.528.056.104.172.156.348.156h1.248v-.012a.478.478 0 0 1-.18-.264Z"
33
+ fill="currentColor"
34
+ />
35
+ </Box>
36
+ );
37
+
38
+ export const PlayIcon = (props: BoxProps) => (
39
+ <Box as="svg" {...props} fill="none" viewBox="0 0 60 60">
40
+ <path
41
+ fillRule="evenodd"
42
+ clipRule="evenodd"
43
+ d="M30 60c16.569 0 30-13.431 30-30C60 13.431 46.569 0 30 0 13.431 0 0 13.431 0 30c0 16.569 13.431 30 30 30Zm-4.25-45.325c-1.97-1.533-4.84-.128-4.84 2.368v25.914c0 2.496 2.87 3.9 4.84 2.368l16.66-12.957a3 3 0 0 0 0-4.736L25.75 14.675Z"
44
+ fill="currentColor"
45
+ />
46
+ </Box>
47
+ );
48
+
49
+ export const PauseIcon = (props: BoxProps) => (
50
+ <Box as="svg" {...props} viewBox="0 0 60 60" fill="none">
51
+ <path
52
+ fillRule="evenodd"
53
+ clipRule="evenodd"
54
+ d="M60 30c0 16.569-13.431 30-30 30C13.431 60 0 46.569 0 30 0 13.431 13.431 0 30 0c16.569 0 30 13.431 30 30Zm-25.5-9.75a2.25 2.25 0 0 1 4.5 0v19.5a2.25 2.25 0 0 1-4.5 0v-19.5ZM23.25 18A2.25 2.25 0 0 0 21 20.25v19.5a2.25 2.25 0 0 0 4.5 0v-19.5A2.25 2.25 0 0 0 23.25 18Z"
55
+ fill="currentColor"
56
+ />
57
+ </Box>
58
+ );
59
+
60
+ export const SkipNextIcon = (props: BoxProps) => (
61
+ <Box as="svg" {...props} viewBox="0 0 30 30" fill="none">
62
+ <path
63
+ fillRule="evenodd"
64
+ clipRule="evenodd"
65
+ d="M6.453 4.18A1.695 1.695 0 0 0 4 5.696v18.608c0 1.26 1.326 2.08 2.453 1.516L24 17.047V25a1 1 0 1 0 2 0V5a1 1 0 1 0-2 0v7.952L6.453 4.18ZM23.623 15 6 23.81V6.19l17.623 8.81Z"
66
+ fill="currentColor"
67
+ />
68
+ </Box>
69
+ );
70
+
71
+ export const SkipPreviousIcon = (props: BoxProps) => (
72
+ <Box as="svg" {...props} viewBox="0 0 30 30" fill="none">
73
+ <path
74
+ fillRule="evenodd"
75
+ clipRule="evenodd"
76
+ d="M23.547 4.18A1.695 1.695 0 0 1 26 5.696v18.608c0 1.26-1.326 2.08-2.453 1.516L6 17.047V25a1 1 0 1 1-2 0V5a1 1 0 1 1 2 0v7.952L23.547 4.18ZM6.377 15 24 23.81V6.19l-17.623 8.81Z"
77
+ fill="currentColor"
78
+ />
79
+ </Box>
80
+ );
@@ -0,0 +1,59 @@
1
+ import { render } from "@testing-library/react";
2
+ import React from "react";
3
+ import { vi } from "vitest";
4
+ import { axe } from "vitest-axe";
5
+ import { JumpButton, PlayPauseButton, SkipButton } from ".";
6
+
7
+ describe("<PlayPauseButton />", () => {
8
+ it("works like a button", async () => {
9
+ const handleClick = vi.fn();
10
+ const { getByRole } = render(
11
+ <PlayPauseButton size="sm" isPlaying={true} onClick={handleClick} />
12
+ );
13
+ getByRole("button").click();
14
+ expect(handleClick).toHaveBeenCalled();
15
+ });
16
+
17
+ it("is accessible", async () => {
18
+ const { container } = render(
19
+ <PlayPauseButton size="sm" isPlaying={true} onClick={() => {}} />
20
+ );
21
+ expect(await axe(container)).toHaveNoViolations();
22
+ });
23
+ });
24
+
25
+ describe("<SkipButtonButton />", () => {
26
+ it("works like a button", async () => {
27
+ const handleClick = vi.fn();
28
+ const { getByRole } = render(
29
+ <SkipButton size="sm" direction="forward" onClick={handleClick} />
30
+ );
31
+ getByRole("button").click();
32
+ expect(handleClick).toHaveBeenCalled();
33
+ });
34
+
35
+ it("is accessible", async () => {
36
+ const { container } = render(
37
+ <SkipButton size="sm" direction="forward" onClick={() => {}} />
38
+ );
39
+ expect(await axe(container)).toHaveNoViolations();
40
+ });
41
+ });
42
+
43
+ describe("<JumpButtonButton />", () => {
44
+ it("works like a button", async () => {
45
+ const handleClick = vi.fn();
46
+ const { getByRole } = render(
47
+ <JumpButton size="sm" direction="forward" onClick={handleClick} />
48
+ );
49
+ getByRole("button").click();
50
+ expect(handleClick).toHaveBeenCalled();
51
+ });
52
+
53
+ it("is accessible", async () => {
54
+ const { container } = render(
55
+ <JumpButton size="sm" direction="forward" onClick={() => {}} />
56
+ );
57
+ expect(await axe(container)).toHaveNoViolations();
58
+ });
59
+ });
@@ -0,0 +1,3 @@
1
+ export * from "./JumpButton";
2
+ export * from "./PlayPauseButton";
3
+ export * from "./SkipButton";
@@ -0,0 +1,122 @@
1
+ import {
2
+ Box,
3
+ BoxProps,
4
+ Center,
5
+ Drawer as ChakraDrawer,
6
+ DrawerContent as ChakraDrawerContent,
7
+ DrawerContentProps as ChakraDrawerContentProps,
8
+ DrawerProps as ChakraDrawerProps,
9
+ forwardRef,
10
+ useModalContext,
11
+ } from "@chakra-ui/react";
12
+ import React from "react";
13
+ import { useSwipeable } from "react-swipeable";
14
+ export {
15
+ DrawerBody,
16
+ DrawerCloseButton,
17
+ DrawerFooter,
18
+ DrawerOverlay,
19
+ } from "@chakra-ui/react";
20
+ export type { DrawerProps } from "@chakra-ui/react";
21
+ export { ModalHeader as DrawerHeader } from "./ModalHeader";
22
+
23
+ type DrawerProps = ChakraDrawerProps;
24
+ export const Drawer = (props: DrawerProps) => {
25
+ return (
26
+ <DrawerProvider placement={props.placement}>
27
+ <ChakraDrawer {...props} />
28
+ </DrawerProvider>
29
+ );
30
+ };
31
+
32
+ type DrawerContentProps = ChakraDrawerContentProps;
33
+ export const DrawerContent = forwardRef<DrawerContentProps, any>(
34
+ ({ children, ...props }, ref) => {
35
+ const placement = useDrawerContext();
36
+ const { onClose } = useModalContext();
37
+ const handlers = useSwipeable({
38
+ onSwiped: (e) => {
39
+ const shouldClose =
40
+ (placement === "bottom" && e.dir === "Down") ||
41
+ (placement === "right" && e.dir === "Right") ||
42
+ (placement === "left" && e.dir === "Left") ||
43
+ (placement === "top" && e.dir === "Up") ||
44
+ (placement === "end" && e.dir === "Right") ||
45
+ (placement === "start" && e.dir === "Left");
46
+ if (shouldClose) {
47
+ onClose();
48
+ }
49
+ },
50
+ swipeDuration: 500,
51
+ });
52
+
53
+ const isTopOrBottom = placement === "top" || placement === "bottom";
54
+ const widthConstraits = isTopOrBottom
55
+ ? { width: ["100%", "37.5rem"], mx: "auto" }
56
+ : {};
57
+ return (
58
+ <Box {...handlers}>
59
+ <ChakraDrawerContent
60
+ {...widthConstraits}
61
+ borderTopRadius={placement === "bottom" ? "md" : "none"}
62
+ borderBottomRadius={placement === "top" ? "md" : "none"}
63
+ {...props}
64
+ ref={ref}
65
+ >
66
+ <Box position="relative">
67
+ <Box maxHeight="100vh" maxWidth="100vw" overflow="auto">
68
+ {isTopOrBottom && <Notch />}
69
+
70
+ <Box>{children}</Box>
71
+ </Box>
72
+ </Box>
73
+ </ChakraDrawerContent>
74
+ </Box>
75
+ );
76
+ }
77
+ );
78
+
79
+ const Notch = forwardRef<BoxProps, any>((props, ref) => {
80
+ const placement = useDrawerContext();
81
+ return (
82
+ <Box
83
+ position="absolute"
84
+ left={0}
85
+ right={0}
86
+ top={placement === "bottom" ? 0 : undefined}
87
+ bottom={placement === "top" ? 0 : undefined}
88
+ zIndex="modal"
89
+ {...props}
90
+ ref={ref}
91
+ >
92
+ <Center
93
+ background={`linear-gradient(to ${
94
+ placement === "bottom" ? "bottom" : "top"
95
+ }, white 0%, white 60%, transparent)`}
96
+ padding={2}
97
+ borderRadius="md"
98
+ >
99
+ <Box
100
+ width="2.265rem"
101
+ height={1}
102
+ backgroundColor="steel"
103
+ borderRadius="xs"
104
+ />
105
+ </Center>
106
+ </Box>
107
+ );
108
+ });
109
+
110
+ const DrawerContext = React.createContext<DrawerProps["placement"]>(undefined);
111
+ type DrawerProviderProps = {
112
+ children: React.ReactNode;
113
+ placement: DrawerProps["placement"];
114
+ };
115
+ const DrawerProvider = (props: DrawerProviderProps) => (
116
+ <DrawerContext.Provider value={props.placement}>
117
+ {props.children}
118
+ </DrawerContext.Provider>
119
+ );
120
+ const useDrawerContext = () => {
121
+ return React.useContext(DrawerContext);
122
+ };
@@ -0,0 +1,15 @@
1
+ export {
2
+ Modal,
3
+ ModalBody,
4
+ ModalCloseButton,
5
+ ModalContent,
6
+ ModalFooter,
7
+ ModalOverlay,
8
+ } from "@chakra-ui/react";
9
+ export type {
10
+ ModalBodyProps,
11
+ ModalContentProps,
12
+ ModalFooterProps,
13
+ ModalOverlayProps,
14
+ ModalProps,
15
+ } from "@chakra-ui/react";