@vygruppen/spor-react 1.3.4 → 2.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 (196) hide show
  1. package/.turbo/turbo-build.log +12 -10
  2. package/CHANGELOG.md +25 -0
  3. package/dist/index.d.ts +6718 -27
  4. package/dist/index.js +14139 -140
  5. package/dist/index.mjs +13818 -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 +165 -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,31 @@
1
+ import {
2
+ forwardRef,
3
+ ModalHeader as ChakraModalHeader,
4
+ ModalHeaderProps as ChakraModalHeaderProps,
5
+ } from "@chakra-ui/react";
6
+ import React from "react";
7
+
8
+ export type ModalHeaderProps = ChakraModalHeaderProps & {
9
+ size?: "sm" | "lg";
10
+ };
11
+
12
+ /**
13
+ * ModalHeader
14
+ *
15
+ * You can specify the size with either `size="sm"` or `size="lg"`.
16
+ */
17
+ export const ModalHeader = forwardRef<ModalHeaderProps, "header">(
18
+ ({ size, ...props }, ref) => {
19
+ const styles = {
20
+ fontSize:
21
+ size === "lg"
22
+ ? ["mobile.lg", "desktop.lg"]
23
+ : ["mobile.md", "desktop.md"],
24
+ textAlign:
25
+ size === "lg"
26
+ ? "center"
27
+ : ("left" as ChakraModalHeaderProps["textAlign"]),
28
+ };
29
+ return <ChakraModalHeader {...props} ref={ref} {...styles} />;
30
+ }
31
+ );
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+ import {
3
+ Drawer,
4
+ DrawerBody,
5
+ DrawerCloseButton,
6
+ DrawerContent,
7
+ DrawerHeader,
8
+ DrawerOverlay,
9
+ } from "./Drawer";
10
+
11
+ export type SimpleDrawerProps = {
12
+ children: React.ReactNode;
13
+ title?: React.ReactNode;
14
+ placement: "top" | "right" | "bottom" | "left";
15
+ isOpen: boolean;
16
+ onClose: () => void;
17
+ };
18
+ /** A very basic drawer component that's easy to use
19
+ *
20
+ * ```tsx
21
+ * <SimpleDrawer placement="bottom" isOpen={isOpen} onClose={handleClose}>
22
+ * This is the drawer content
23
+ * </SimpleDrawer>
24
+ * ```
25
+ *
26
+ * For more advanced use cases, see the [Drawer](./Drawer.tsx) component.
27
+ */
28
+ export const SimpleDrawer = ({
29
+ placement,
30
+ children,
31
+ title,
32
+ ...props
33
+ }: SimpleDrawerProps) => {
34
+ return (
35
+ <Drawer placement={placement} {...props}>
36
+ <DrawerOverlay />
37
+ <DrawerContent>
38
+ <DrawerCloseButton />
39
+ {title && <DrawerHeader>{title}</DrawerHeader>}
40
+ <DrawerBody>{children}</DrawerBody>
41
+ </DrawerContent>
42
+ </Drawer>
43
+ );
44
+ };
@@ -0,0 +1,4 @@
1
+ export * from "./Drawer";
2
+ export * from "./Modal";
3
+ export * from "./ModalHeader";
4
+ export * from "./SimpleDrawer";
@@ -0,0 +1,91 @@
1
+ import { PopoverBody, usePopoverContext } from "@chakra-ui/react";
2
+ import { ArrowRightFill18Icon } from "@vygruppen/spor-icon-react";
3
+ import * as React from "react";
4
+ import { Box, Button, Flex, Stack, createTexts, useTranslation } from "..";
5
+
6
+ export type PopoverWizardProps = {
7
+ /** Each child will be their own step */
8
+ children: React.ReactNode;
9
+ };
10
+ /** A popover wizard is great for showing new features one by one */
11
+ export const PopoverWizardBody = ({ children }: PopoverWizardProps) => {
12
+ const [currentStep, setCurrentStep] = React.useState(1);
13
+ const totalSteps = React.Children.count(children);
14
+ const { isOpen } = usePopoverContext();
15
+ React.useEffect(() => {
16
+ if (!isOpen && currentStep > 1) {
17
+ const id = setTimeout(() => setCurrentStep(1), 500);
18
+ return () => clearTimeout(id);
19
+ }
20
+ }, [isOpen, currentStep]);
21
+ return (
22
+ <PopoverBody>
23
+ <Stack spacing={1.5}>
24
+ <Box>{React.Children.toArray(children)[currentStep - 1]}</Box>
25
+ <Flex gap={3}>
26
+ <StepIndicator totalSteps={totalSteps} currentStep={currentStep} />
27
+ <NextStepButton
28
+ isLastStep={totalSteps === currentStep}
29
+ onNext={() => setCurrentStep((prev) => prev + 1)}
30
+ />
31
+ </Flex>
32
+ </Stack>
33
+ </PopoverBody>
34
+ );
35
+ };
36
+
37
+ type StepIndicatorProps = { totalSteps: number; currentStep: number };
38
+ const StepIndicator = ({ totalSteps, currentStep }: StepIndicatorProps) => {
39
+ const steps = createRange(1, totalSteps);
40
+ return (
41
+ <Flex gap={1} alignItems="center">
42
+ {steps.map((step) => (
43
+ <Box
44
+ key={step}
45
+ width={1}
46
+ height={1}
47
+ borderRadius="50%"
48
+ transition="medium"
49
+ transitionProperty="background-color"
50
+ backgroundColor={step === currentStep ? "seaMist" : "greenHaze"}
51
+ />
52
+ ))}
53
+ </Flex>
54
+ );
55
+ };
56
+
57
+ const createRange = (start: number, end: number) => {
58
+ return new Array(end - start + 1).fill(null).map((_, i) => i + start);
59
+ };
60
+
61
+ type NextStepButtonProps = { isLastStep: boolean; onNext: () => void };
62
+ const NextStepButton = ({ isLastStep, onNext }: NextStepButtonProps) => {
63
+ const { onClose } = usePopoverContext();
64
+ const { t } = useTranslation();
65
+ return (
66
+ <Button
67
+ variant="additional"
68
+ size="sm"
69
+ color="white"
70
+ leftIcon={isLastStep ? undefined : <ArrowRightFill18Icon />}
71
+ onClick={isLastStep ? onClose : onNext}
72
+ >
73
+ {t(isLastStep ? texts.finish : texts.nextStep)}
74
+ </Button>
75
+ );
76
+ };
77
+
78
+ const texts = createTexts({
79
+ nextStep: {
80
+ nb: "Neste",
81
+ nn: "Neste",
82
+ sv: "Nästa",
83
+ en: "Next",
84
+ },
85
+ finish: {
86
+ nb: "Fullfør",
87
+ nn: "Fullfør",
88
+ sv: "Fullför",
89
+ en: "Finish",
90
+ },
91
+ });
@@ -0,0 +1,75 @@
1
+ import {
2
+ DarkMode,
3
+ Popover,
4
+ PopoverArrow,
5
+ PopoverBody,
6
+ PopoverCloseButton,
7
+ PopoverContent,
8
+ PopoverProps,
9
+ PopoverTrigger,
10
+ } from "@chakra-ui/react";
11
+ import React from "react";
12
+
13
+ type SimplePopoverProps = PopoverProps & {
14
+ /**
15
+ * Whatever is supposed to trigger the popover.
16
+ * Must be focusable - like a link or button */
17
+ triggerElement?: React.ReactNode;
18
+ /** Callback for when the popover is requested to close */
19
+ onClose?: () => void;
20
+ /** Should the popover have a close button? */
21
+ withCloseButton?: boolean;
22
+ /** The content of the popover */
23
+ children: React.ReactNode;
24
+ /** Use this prop if you want to control the open state */
25
+ isOpen?: boolean;
26
+ /** Whether or not the popover is open by default */
27
+ defaultIsOpen?: boolean;
28
+ /**
29
+ * Where the popover should be placed by default.
30
+ *
31
+ * Note - this is a suggestion, and may be overridden by space concerns.
32
+ */
33
+ placement?: "top" | "bottom" | "left" | "right";
34
+ /**
35
+ * The amount of spacing.
36
+ * Popovers with lots of content should be `lg`. Defaults to `sm`.
37
+ **/
38
+ size?: "sm" | "lg";
39
+ borderRadius?: string;
40
+ };
41
+ /** A basic popover component for basic content */
42
+ export const SimplePopover = ({
43
+ children,
44
+ triggerElement,
45
+ onClose,
46
+ isOpen,
47
+ defaultIsOpen,
48
+ placement = "bottom",
49
+ size = "sm",
50
+ withCloseButton = false,
51
+ borderRadius,
52
+ ...props
53
+ }: SimplePopoverProps) => {
54
+ return (
55
+ <DarkMode>
56
+ <Popover
57
+ onClose={onClose}
58
+ isOpen={isOpen}
59
+ defaultIsOpen={defaultIsOpen}
60
+ placement={placement}
61
+ size={size}
62
+ arrowSize={12}
63
+ arrowShadowColor="none"
64
+ {...props}
65
+ >
66
+ {triggerElement && <PopoverTrigger>{triggerElement}</PopoverTrigger>}
67
+ <PopoverContent borderRadius={borderRadius}>
68
+ <PopoverArrow />
69
+ {withCloseButton && <PopoverCloseButton />}
70
+ <PopoverBody>{children}</PopoverBody>
71
+ </PopoverContent>
72
+ </Popover>
73
+ </DarkMode>
74
+ );
75
+ };
@@ -0,0 +1,61 @@
1
+ import {
2
+ DarkMode,
3
+ Popover,
4
+ PopoverArrow,
5
+ PopoverCloseButton,
6
+ PopoverContent,
7
+ PopoverProps,
8
+ PopoverTrigger,
9
+ } from "@chakra-ui/react";
10
+ import * as React from "react";
11
+ import { PopoverWizardBody } from "./PopoverWizardBody";
12
+
13
+ export type WizardPopoverProps = PopoverProps & {
14
+ /** Steps in the wizard. Each item is its own step */
15
+ children: React.ReactNode;
16
+ /** The element that triggers the wizard */
17
+ triggerElement: React.ReactNode;
18
+ /**
19
+ * Where the popover should be placed by default.
20
+ *
21
+ * Note - this is a suggestion, and may be overridden by space concerns.
22
+ */
23
+ placement?: "top" | "bottom" | "left" | "right";
24
+ /** Should the popover have a close button? */
25
+ withCloseButton?: boolean;
26
+ };
27
+ /**
28
+ * A popover that displays its children one at a time, with a step indicator
29
+ *
30
+ * Each child is its own step. If you want several components inside a
31
+ * single slide, you want to wrap them in an external component (like a Stack).
32
+ *
33
+ * ```tsx
34
+ * <WizardPopover triggerElement={<Button>Click me</Button>}>
35
+ * <Text>First step</Text>
36
+ * <Text>Second step</Text>
37
+ * <Stack>
38
+ * <Text>Third step is special.</Text>
39
+ * <Text>It even has several paragraphs 🤯</Text>
40
+ * </Stack>
41
+ * </WizardPopover>
42
+ * ```
43
+ */
44
+ export const WizardPopover = ({
45
+ children,
46
+ triggerElement,
47
+ withCloseButton = false,
48
+ }: WizardPopoverProps) => {
49
+ return (
50
+ <DarkMode>
51
+ <Popover size="lg">
52
+ <PopoverTrigger>{triggerElement}</PopoverTrigger>
53
+ <PopoverContent>
54
+ <PopoverArrow />
55
+ {withCloseButton && <PopoverCloseButton />}
56
+ <PopoverWizardBody>{children}</PopoverWizardBody>
57
+ </PopoverContent>
58
+ </Popover>
59
+ </DarkMode>
60
+ );
61
+ };
@@ -0,0 +1,23 @@
1
+ export {
2
+ Popover,
3
+ PopoverAnchor,
4
+ PopoverArrow,
5
+ PopoverBody,
6
+ PopoverCloseButton,
7
+ PopoverContent,
8
+ PopoverFooter,
9
+ PopoverHeader,
10
+ PopoverTrigger,
11
+ } from "@chakra-ui/react";
12
+ export type {
13
+ PopoverArrowProps,
14
+ PopoverBodyProps,
15
+ PopoverCloseButtonProps,
16
+ PopoverContentProps,
17
+ PopoverFooterProps,
18
+ PopoverHeaderProps,
19
+ PopoverProps,
20
+ } from "@chakra-ui/react";
21
+ export * from "./PopoverWizardBody";
22
+ export * from "./SimplePopover";
23
+ export * from "./WizardPopover";
@@ -0,0 +1,67 @@
1
+ import { ChakraProvider, ChakraProviderProps } from "@chakra-ui/react";
2
+ import { Global } from "@emotion/react";
3
+ import React from "react";
4
+ import { Language, LanguageProvider } from "..";
5
+ import { theme as defaultSporTheme, fontFaces } from "../";
6
+
7
+ type SporProviderProps = ChakraProviderProps & {
8
+ language?: Language;
9
+ };
10
+
11
+ /**
12
+ * This component is used to provide the specified theme of colors and other
13
+ * design tokens to the remainder of the application, as well as the current language.
14
+ *
15
+ * Please place it as close to the root of your application as possible -
16
+ * at least before rendering any UI.
17
+ *
18
+ * You can pass your own theme to this component. If you don't (and most of the time you won't), the default Spor theme will be used.
19
+ *
20
+ * You should specify the current language of your application. This is specified to provide any built-in microcopy and labels for any Spor components. The default is Norwegian (bokmål).
21
+ *
22
+ * ```tsx
23
+ * * import { SporProvider, Language } from "@vygruppen/spor-react";
24
+ * const root = React.createRoot(document.getElementById("root"))
25
+ * root.render(
26
+ * <SporProvider language={Language.English}>
27
+ * <App />
28
+ * </SporProvider>
29
+ * );
30
+ * ```
31
+ *
32
+ * You can pass specific overrides to the theme if you need to. Adding application specific design tokens, for example could be a useful thing to do.
33
+ *
34
+ * ```tsx
35
+ * import { extendTheme, SporProvider } from "@vygruppen/spor-react";
36
+ * const theme = extendTheme({
37
+ * colors: { myApp: { primary: "tomato" } }
38
+ * });
39
+ * const root = React.createRoot(document.getElementById("root"))
40
+ * root.render(
41
+ * <SporProvider language={Language.Swedish} theme={theme}>
42
+ * <App />
43
+ * </SporProvider>
44
+ * );
45
+ * ```
46
+ */
47
+ export const SporProvider = ({
48
+ theme = defaultSporTheme,
49
+ language = Language.NorwegianBokmal,
50
+ children,
51
+ ...props
52
+ }: SporProviderProps) => {
53
+ return (
54
+ <LanguageProvider value={language}>
55
+ <ChakraProvider theme={theme} {...props}>
56
+ <Global styles={fontFaces} />
57
+ <Global
58
+ styles={`
59
+ html, body { color: ${theme.colors.darkGrey}; }
60
+ svg { display: initial; }
61
+ `}
62
+ />
63
+ {children}
64
+ </ChakraProvider>
65
+ </LanguageProvider>
66
+ );
67
+ };
@@ -0,0 +1 @@
1
+ export * from "./SporProvider";
@@ -0,0 +1,115 @@
1
+ import { Flex, HStack, useMultiStyleConfig } from "@chakra-ui/react";
2
+ import { DropdownLeftFill24Icon } from "@vygruppen/spor-icon-react";
3
+ import React from "react";
4
+ import { StepperStep } from ".";
5
+ import {
6
+ Box,
7
+ IconButton,
8
+ SimplePopover,
9
+ createTexts,
10
+ useTranslation,
11
+ } from "..";
12
+ import { StepperProvider } from "./StepperContext";
13
+
14
+ type StepperProps = {
15
+ onClick: (clickedStep: number) => void;
16
+ colorScheme: "light" | "dark" | "green";
17
+ title?: string;
18
+ activeStep: number;
19
+ steps: string[];
20
+ };
21
+ /**
22
+ * A stepper is used to show which step of a process a user is currently in.
23
+ *
24
+ * You specify the active step, which starts at 1 (not 0)
25
+ *
26
+ * ```tsx
27
+ * <Stepper
28
+ * title="Eksempel"
29
+ * onClick={handleStepClick}
30
+ * activeStep={2}
31
+ * steps={['Velg hvor', 'Velg når', 'Velg hvordan']}
32
+ * />
33
+ * ```
34
+ **/
35
+ export const Stepper = ({
36
+ onClick = () => {},
37
+ steps,
38
+ activeStep: activeStepAsStringOrNumber,
39
+ title,
40
+ colorScheme,
41
+ }: StepperProps) => {
42
+ const style = useMultiStyleConfig("Stepper", { colorScheme });
43
+ const numberOfSteps = steps.length;
44
+ const activeStep = Number(activeStepAsStringOrNumber);
45
+ const { t } = useTranslation();
46
+ return (
47
+ <Box __css={style.root}>
48
+ <StepperProvider
49
+ onClick={onClick}
50
+ activeStep={activeStep}
51
+ colorScheme={colorScheme}
52
+ numberOfSteps={numberOfSteps}
53
+ >
54
+ <Box __css={style.container}>
55
+ <Box __css={style.innerContainer}>
56
+ <HStack>
57
+ {activeStep > 1 && (
58
+ <IconButton
59
+ aria-label={t(texts.back)}
60
+ icon={<DropdownLeftFill24Icon />}
61
+ variant="ghost"
62
+ size="sm"
63
+ onClick={() => onClick(activeStep - 1)}
64
+ __css={style.backButton}
65
+ />
66
+ )}
67
+
68
+ <SimplePopover
69
+ triggerElement={
70
+ <Box as="button" __css={style.stepCounter}>
71
+ {t(texts.stepsOf(activeStep, numberOfSteps))}
72
+ </Box>
73
+ }
74
+ borderRadius="xs"
75
+ >
76
+ {steps.map((step, index) => (
77
+ <StepperStep key={step} stepNumber={index + 1}>
78
+ {step}
79
+ </StepperStep>
80
+ ))}
81
+ </SimplePopover>
82
+ </HStack>
83
+ {title && (
84
+ <Box as="h3" __css={style.title}>
85
+ {title}
86
+ </Box>
87
+ )}
88
+ </Box>
89
+ <Flex justifyContent="center" display={["none", "flex"]}>
90
+ {steps.map((step, index) => (
91
+ <StepperStep key={index} stepNumber={index + 1}>
92
+ {step}
93
+ </StepperStep>
94
+ ))}
95
+ </Flex>
96
+ </Box>
97
+ </StepperProvider>
98
+ </Box>
99
+ );
100
+ };
101
+
102
+ const texts = createTexts({
103
+ stepsOf: (activeStep, numberOfSteps) => ({
104
+ nb: `Steg ${activeStep} av ${numberOfSteps}`,
105
+ nn: `Steg ${activeStep} av ${numberOfSteps}`,
106
+ sv: `Steg ${activeStep} av ${numberOfSteps}`,
107
+ en: `Step ${activeStep} of ${numberOfSteps}`,
108
+ }),
109
+ back: {
110
+ nb: "Tilbake",
111
+ nn: "Tilbake",
112
+ sv: "Tillbaka",
113
+ en: "Back",
114
+ },
115
+ });
@@ -0,0 +1,55 @@
1
+ import React from "react";
2
+
3
+ type StepperContextType = {
4
+ activeStep: number;
5
+ numberOfSteps: number;
6
+ colorScheme: ColorScheme;
7
+ onClick: (clickedIndex: number) => void;
8
+ };
9
+ const StepperContext = React.createContext<StepperContextType | null>(null);
10
+
11
+ type ColorScheme = "green" | "light" | "dark";
12
+
13
+ type StepperProviderProps = {
14
+ /** Stepper steps */
15
+ children: React.ReactNode;
16
+ /** Callback whenever a stepper step is clicked */
17
+ onClick: (clickedIndex: number) => void;
18
+ /** The current color scheme */
19
+ colorScheme: ColorScheme;
20
+ /** The currently active step */
21
+ activeStep: number;
22
+ /** The amount of steps */
23
+ numberOfSteps: number;
24
+ };
25
+ /**
26
+ * Internal provider for sharing logic between stepper and stepper steps.
27
+ */
28
+ export const StepperProvider = ({
29
+ activeStep,
30
+ children,
31
+ onClick,
32
+ colorScheme,
33
+ numberOfSteps,
34
+ }: StepperProviderProps) => {
35
+ return (
36
+ <StepperContext.Provider
37
+ value={{ activeStep, onClick, colorScheme, numberOfSteps }}
38
+ >
39
+ {children}
40
+ </StepperContext.Provider>
41
+ );
42
+ };
43
+
44
+ /**
45
+ * Internal hook for sharing stepper state
46
+ */
47
+ export const useStepper = () => {
48
+ const context = React.useContext(StepperContext);
49
+ if (!context) {
50
+ throw new Error(
51
+ "useStepper must be used within a StepperProvider. Most likely, you forgot to wrap your StepperStep in a Stepper component"
52
+ );
53
+ }
54
+ return context;
55
+ };
@@ -0,0 +1,48 @@
1
+ import { Flex, useMultiStyleConfig } from "@chakra-ui/react";
2
+ import { DropdownRightFill18Icon } from "@vygruppen/spor-icon-react";
3
+ import { Box } from "@vygruppen/spor-layout-react";
4
+ import React from "react";
5
+ import { useStepper } from "./StepperContext";
6
+
7
+ type StepperStepProps = {
8
+ children: React.ReactNode;
9
+ stepNumber: number;
10
+ };
11
+ export const StepperStep = ({ children, stepNumber }: StepperStepProps) => {
12
+ const { activeStep, onClick, colorScheme } = useStepper();
13
+ const variant = getVariant(stepNumber!, activeStep);
14
+ const style = useMultiStyleConfig("Stepper", {
15
+ variant,
16
+ colorScheme,
17
+ });
18
+
19
+ return (
20
+ <Box __css={style.stepContainer}>
21
+ {stepNumber > 1 && (
22
+ <DropdownRightFill18Icon mx={5} display={["none", "block"]} />
23
+ )}
24
+
25
+ <Flex
26
+ __css={style.stepButton}
27
+ alignItems="center"
28
+ as="button"
29
+ type="button"
30
+ disabled={variant === "disabled" || variant === "active"}
31
+ onClick={() => onClick(stepNumber)}
32
+ >
33
+ <Box __css={style.stepNumber}>{stepNumber}</Box>
34
+ <Box __css={style.stepTitle}>{children}</Box>
35
+ </Flex>
36
+ </Box>
37
+ );
38
+ };
39
+
40
+ const getVariant = (stepNumber: number, activeStep: number) => {
41
+ if (stepNumber < activeStep) {
42
+ return "completed";
43
+ }
44
+ if (stepNumber === activeStep) {
45
+ return "active";
46
+ }
47
+ return "disabled";
48
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./Stepper";
2
+ export * from "./StepperStep";
@@ -0,0 +1,20 @@
1
+ import {
2
+ Tabs as ChakraTabs,
3
+ TabsProps as ChakraTabsProps,
4
+ forwardRef,
5
+ } from "@chakra-ui/react";
6
+ import * as React from "react";
7
+
8
+ export type TabsProps = Exclude<
9
+ ChakraTabsProps,
10
+ "colorScheme" | "variant" | "orientation" | "size"
11
+ > & {
12
+ colorScheme: "dark" | "light" | "green" | "grey";
13
+ /** Defaults to `md` */
14
+ size?: "sm" | "md" | "lg" | "xl";
15
+ /** Defaults to `round` */
16
+ variant?: "square" | "round";
17
+ };
18
+ export const Tabs = forwardRef<TabsProps, "div">((props, ref) => {
19
+ return <ChakraTabs {...props} ref={ref} />;
20
+ });
@@ -0,0 +1,9 @@
1
+ export { Tab, TabList, TabPanel, TabPanels } from "@chakra-ui/react";
2
+ export type {
3
+ TabListProps,
4
+ TabPanelProps,
5
+ TabPanelsProps,
6
+ TabProps,
7
+ } from "@chakra-ui/react";
8
+ export { Tabs } from "./Tabs";
9
+ export type { TabsProps } from "./Tabs";