@vygruppen/spor-react 1.3.3 → 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.
- package/.turbo/turbo-build.log +12 -10
- package/CHANGELOG.md +40 -0
- package/README.md +1 -1
- package/dist/index.d.ts +6718 -27
- package/dist/index.js +14139 -140
- package/dist/index.mjs +13818 -27
- package/package.json +19 -31
- package/src/accordion/Accordion.test.tsx +20 -0
- package/src/accordion/Accordion.tsx +62 -0
- package/src/accordion/AccordionContext.tsx +27 -0
- package/src/accordion/Expandable.tsx +157 -0
- package/src/accordion/index.tsx +2 -0
- package/src/alert/AlertIcon.tsx +75 -0
- package/src/alert/BaseAlert.test.tsx +37 -0
- package/src/alert/BaseAlert.tsx +21 -0
- package/src/alert/ClosableAlert.test.tsx +37 -0
- package/src/alert/ClosableAlert.tsx +75 -0
- package/src/alert/ExpandableAlert.test.tsx +84 -0
- package/src/alert/ExpandableAlert.tsx +84 -0
- package/src/alert/StaticAlert.tsx +25 -0
- package/src/alert/index.tsx +3 -0
- package/src/button/Button.test.tsx +23 -0
- package/src/button/Button.tsx +162 -0
- package/src/button/ButtonGroup.tsx +43 -0
- package/src/button/CloseButton.tsx +63 -0
- package/src/button/FloatingActionButton.tsx +113 -0
- package/src/button/IconButton.tsx +63 -0
- package/src/button/index.tsx +5 -0
- package/src/card/Card.tsx +59 -0
- package/src/card/index.tsx +1 -0
- package/src/datepicker/Calendar.tsx +32 -0
- package/src/datepicker/CalendarCell.tsx +74 -0
- package/src/datepicker/CalendarGrid.tsx +76 -0
- package/src/datepicker/CalendarHeader.tsx +153 -0
- package/src/datepicker/CalendarNavigationButton.tsx +26 -0
- package/src/datepicker/CalendarTriggerButton.tsx +36 -0
- package/src/datepicker/DateField.tsx +51 -0
- package/src/datepicker/DatePicker.tsx +153 -0
- package/src/datepicker/DateRangePicker.tsx +165 -0
- package/src/datepicker/DateTimeSegment.tsx +56 -0
- package/src/datepicker/RangeCalendar.tsx +35 -0
- package/src/datepicker/StyledField.tsx +31 -0
- package/src/datepicker/TimeField.tsx +46 -0
- package/src/datepicker/TimePicker.test.tsx +74 -0
- package/src/datepicker/TimePicker.tsx +196 -0
- package/src/datepicker/index.tsx +4 -0
- package/src/datepicker/utils.ts +33 -0
- package/src/i18n/index.tsx +38 -0
- package/src/image/index.tsx +2 -0
- package/src/index.tsx +25 -26
- package/src/input/CardSelect.tsx +165 -0
- package/src/input/Checkbox.tsx +24 -0
- package/src/input/CheckboxGroup.tsx +43 -0
- package/src/input/ChoiceChip.tsx +102 -0
- package/src/input/Dialog.tsx +29 -0
- package/src/input/FormControl.tsx +11 -0
- package/src/input/FormErrorMessage.tsx +91 -0
- package/src/input/FormLabel.tsx +11 -0
- package/src/input/InfoSelect.tsx +209 -0
- package/src/input/Input.tsx +59 -0
- package/src/input/InputElement.tsx +45 -0
- package/src/input/ListBox.tsx +123 -0
- package/src/input/NativeSelect.tsx +38 -0
- package/src/input/PasswordInput.tsx +70 -0
- package/src/input/Popover.tsx +70 -0
- package/src/input/Radio.tsx +34 -0
- package/src/input/RadioGroup.tsx +47 -0
- package/src/input/SearchInput.tsx +89 -0
- package/src/input/Switch.tsx +40 -0
- package/src/input/Textarea.tsx +98 -0
- package/src/input/index.tsx +20 -0
- package/src/layout/Divider.tsx +26 -0
- package/src/layout/Stack.tsx +42 -0
- package/src/layout/index.tsx +28 -0
- package/src/linjetag/InfoTag.tsx +54 -0
- package/src/linjetag/LineIcon.tsx +44 -0
- package/src/linjetag/TravelTag.tsx +121 -0
- package/src/linjetag/icons.tsx +80 -0
- package/src/linjetag/index.tsx +3 -0
- package/src/linjetag/types.d.ts +24 -0
- package/src/link/TextLink.tsx +45 -0
- package/src/link/index.tsx +1 -0
- package/src/loader/ClientOnly.tsx +29 -0
- package/src/loader/ColorInlineLoader.tsx +27 -0
- package/src/loader/ColorSpinner.tsx +44 -0
- package/src/loader/ContentLoader.tsx +27 -0
- package/src/loader/DarkFullScreenLoader.tsx +23 -0
- package/src/loader/DarkInlineLoader.tsx +25 -0
- package/src/loader/DarkSpinner.tsx +43 -0
- package/src/loader/LightFullScreenLoader.tsx +23 -0
- package/src/loader/LightInlineLoader.tsx +25 -0
- package/src/loader/LightSpinner.tsx +41 -0
- package/src/loader/Lottie.tsx +10 -0
- package/src/loader/ProgressBar.tsx +128 -0
- package/src/loader/ProgressLoader.tsx +140 -0
- package/src/loader/Skeleton.tsx +16 -0
- package/src/loader/SkeletonCircle.tsx +13 -0
- package/src/loader/SkeletonText.tsx +10 -0
- package/src/loader/index.tsx +14 -0
- package/src/loader/useHydrated.tsx +34 -0
- package/src/loader/useRotatingLabel.tsx +22 -0
- package/src/logo/VyLogo.tsx +101 -0
- package/src/logo/index.tsx +1 -0
- package/src/media-controller/JumpButton.tsx +69 -0
- package/src/media-controller/PlayPauseButton.tsx +67 -0
- package/src/media-controller/SkipButton.tsx +66 -0
- package/src/media-controller/icons.tsx +80 -0
- package/src/media-controller/index.test.tsx +59 -0
- package/src/media-controller/index.tsx +3 -0
- package/src/modal/Drawer.tsx +122 -0
- package/src/modal/Modal.tsx +15 -0
- package/src/modal/ModalHeader.tsx +31 -0
- package/src/modal/SimpleDrawer.tsx +44 -0
- package/src/modal/index.tsx +4 -0
- package/src/popover/PopoverWizardBody.tsx +91 -0
- package/src/popover/SimplePopover.tsx +75 -0
- package/src/popover/WizardPopover.tsx +61 -0
- package/src/popover/index.tsx +23 -0
- package/src/provider/SporProvider.tsx +67 -0
- package/src/provider/index.tsx +1 -0
- package/src/stepper/Stepper.tsx +115 -0
- package/src/stepper/StepperContext.tsx +55 -0
- package/src/stepper/StepperStep.tsx +48 -0
- package/src/stepper/index.tsx +2 -0
- package/src/tab/Tabs.tsx +20 -0
- package/src/tab/index.tsx +9 -0
- package/src/table/Table.tsx +58 -0
- package/src/table/index.tsx +19 -0
- package/src/theme/components/accordion.ts +143 -0
- package/src/theme/components/alert.ts +59 -0
- package/src/theme/components/badge.ts +109 -0
- package/src/theme/components/button.ts +217 -0
- package/src/theme/components/card-select.ts +158 -0
- package/src/theme/components/card.ts +174 -0
- package/src/theme/components/checkbox.ts +90 -0
- package/src/theme/components/choice-chip.ts +79 -0
- package/src/theme/components/close-button.ts +56 -0
- package/src/theme/components/code.ts +17 -0
- package/src/theme/components/datepicker.ts +194 -0
- package/src/theme/components/drawer.ts +92 -0
- package/src/theme/components/fab.ts +111 -0
- package/src/theme/components/form-label.ts +17 -0
- package/src/theme/components/form.ts +27 -0
- package/src/theme/components/index.ts +34 -0
- package/src/theme/components/info-select.ts +91 -0
- package/src/theme/components/info-tag.ts +49 -0
- package/src/theme/components/input.ts +97 -0
- package/src/theme/components/line-icon.ts +121 -0
- package/src/theme/components/link.ts +155 -0
- package/src/theme/components/listbox.ts +52 -0
- package/src/theme/components/media-controller-button.ts +134 -0
- package/src/theme/components/modal.ts +93 -0
- package/src/theme/components/popover.ts +63 -0
- package/src/theme/components/radio.ts +64 -0
- package/src/theme/components/select.ts +52 -0
- package/src/theme/components/skeleton.ts +40 -0
- package/src/theme/components/stepper.ts +230 -0
- package/src/theme/components/switch.ts +227 -0
- package/src/theme/components/table.ts +163 -0
- package/src/theme/components/tabs.ts +282 -0
- package/src/theme/components/textarea.ts +14 -0
- package/src/theme/components/toast.ts +28 -0
- package/src/theme/components/travel-tag.ts +267 -0
- package/src/theme/font-faces.ts +66 -0
- package/src/theme/foundations/borders.ts +11 -0
- package/src/theme/foundations/breakpoints.ts +9 -0
- package/src/theme/foundations/colors.ts +10 -0
- package/src/theme/foundations/config.ts +5 -0
- package/src/theme/foundations/fontSizes.ts +29 -0
- package/src/theme/foundations/fontWeights.ts +5 -0
- package/src/theme/foundations/fonts.ts +7 -0
- package/src/theme/foundations/index.ts +14 -0
- package/src/theme/foundations/lineHeights.ts +5 -0
- package/src/theme/foundations/radii.ts +12 -0
- package/src/theme/foundations/shadows.ts +8 -0
- package/src/theme/foundations/sizes.ts +34 -0
- package/src/theme/foundations/spacing.ts +30 -0
- package/src/theme/foundations/textStyles.ts +60 -0
- package/src/theme/foundations/zIndices.ts +17 -0
- package/src/theme/index.ts +14 -0
- package/src/theme/utils/box-shadow-utils.ts +44 -0
- package/src/theme/utils/focus-utils.ts +16 -0
- package/src/toast/ActionToast.test.tsx +22 -0
- package/src/toast/ActionToast.tsx +28 -0
- package/src/toast/BaseToast.test.tsx +27 -0
- package/src/toast/BaseToast.tsx +75 -0
- package/src/toast/ClosableToast.test.tsx +17 -0
- package/src/toast/ClosableToast.tsx +40 -0
- package/src/toast/index.tsx +1 -0
- package/src/toast/useToast.tsx +99 -0
- package/src/typography/Badge.tsx +68 -0
- package/src/typography/Code.tsx +32 -0
- package/src/typography/Heading.tsx +32 -0
- package/src/typography/Text.tsx +26 -0
- package/src/typography/index.tsx +4 -0
- package/src/util/externals.tsx +23 -0
- 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,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
|
+
};
|
package/src/tab/Tabs.tsx
ADDED
@@ -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
|
+
});
|