@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,45 @@
|
|
1
|
+
import {
|
2
|
+
Link as ChakraLink,
|
3
|
+
LinkProps as ChakraLinkProps,
|
4
|
+
forwardRef,
|
5
|
+
} from "@chakra-ui/react";
|
6
|
+
import { LinkOutOutline24Icon } from "@vygruppen/spor-icon-react";
|
7
|
+
import React from "react";
|
8
|
+
import { createTexts, useTranslation } from "..";
|
9
|
+
|
10
|
+
type LinkProps = Omit<ChakraLinkProps, "variant"> & {
|
11
|
+
variant?: "primary" | "secondary" | "tertiary";
|
12
|
+
};
|
13
|
+
/** Link to different sites or parts of site
|
14
|
+
*
|
15
|
+
* You can specify the `variant` prop to get different link designs. `tertiary` should only be used on dark backgrounds.
|
16
|
+
*/
|
17
|
+
export const TextLink = forwardRef<LinkProps, "a">(
|
18
|
+
({ children, ...props }, ref) => {
|
19
|
+
const { t } = useTranslation();
|
20
|
+
const isExternal =
|
21
|
+
props.isExternal !== undefined
|
22
|
+
? props.isExternal
|
23
|
+
: Boolean(props.href?.match(/^https?:\/\//));
|
24
|
+
return (
|
25
|
+
<ChakraLink {...props} ref={ref} isExternal={isExternal}>
|
26
|
+
{children}
|
27
|
+
{isExternal && (
|
28
|
+
<LinkOutOutline24Icon
|
29
|
+
marginLeft={0.5}
|
30
|
+
aria-label={t(texts.externalLink)}
|
31
|
+
/>
|
32
|
+
)}
|
33
|
+
</ChakraLink>
|
34
|
+
);
|
35
|
+
}
|
36
|
+
);
|
37
|
+
|
38
|
+
const texts = createTexts({
|
39
|
+
externalLink: {
|
40
|
+
nb: "Ekstern lenke",
|
41
|
+
nn: "Ekstern lenke",
|
42
|
+
sv: "Extern länk",
|
43
|
+
en: "External link",
|
44
|
+
},
|
45
|
+
});
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from "./TextLink";
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import React from "react";
|
2
|
+
import { useHydrated } from "./useHydrated";
|
3
|
+
|
4
|
+
type ClientOnlyProps = {
|
5
|
+
/** A function that renders the client-side only component */
|
6
|
+
children: () => React.ReactNode;
|
7
|
+
/** An optional fallback to render in place on the server */
|
8
|
+
fallback?: React.ReactNode;
|
9
|
+
};
|
10
|
+
|
11
|
+
/**
|
12
|
+
* Render the children only after the JS has loaded client-side. Use an optional
|
13
|
+
* fallback component if the JS is not yet loaded.
|
14
|
+
*
|
15
|
+
* Example: Render a Chart component if JS loads, renders a simple FakeChart
|
16
|
+
* component server-side or if there is no JS. The FakeChart can have only the
|
17
|
+
* UI without the behavior or be a loading spinner or skeleton.
|
18
|
+
* ```tsx
|
19
|
+
* return (
|
20
|
+
* <ClientOnly fallback={<FakeChart />}>
|
21
|
+
* {() => <Chart />}
|
22
|
+
* </ClientOnly>
|
23
|
+
* );
|
24
|
+
* ```
|
25
|
+
*/
|
26
|
+
export const ClientOnly = ({ children, fallback = null }: ClientOnlyProps) => {
|
27
|
+
const isHydrated = useHydrated();
|
28
|
+
return <>{isHydrated ? children() : fallback}</>;
|
29
|
+
};
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import { Box, BoxProps, Center } from "@chakra-ui/react";
|
2
|
+
import { inlineLoaderColorData } from "@vygruppen/spor-loader";
|
3
|
+
import React from "react";
|
4
|
+
import { ClientOnly } from "./ClientOnly";
|
5
|
+
import Lottie from "./Lottie";
|
6
|
+
|
7
|
+
export type ColorInlineLoaderProps = Exclude<BoxProps, "children">;
|
8
|
+
/**
|
9
|
+
* Loading component that works well in bounded contexts, like inside a button.
|
10
|
+
*
|
11
|
+
* This component should only be used on light backgrounds with low saturation (e.g. white, light grey etc.). For colored backgrounds, please use the LightInlineLoader component.
|
12
|
+
*/
|
13
|
+
export const ColorInlineLoader = ({
|
14
|
+
width,
|
15
|
+
maxWidth,
|
16
|
+
...props
|
17
|
+
}: ColorInlineLoaderProps) => {
|
18
|
+
return (
|
19
|
+
<Center {...props}>
|
20
|
+
<Box width={width} maxWidth={maxWidth}>
|
21
|
+
<ClientOnly>
|
22
|
+
{() => <Lottie animationData={inlineLoaderColorData} />}
|
23
|
+
</ClientOnly>
|
24
|
+
</Box>
|
25
|
+
</Center>
|
26
|
+
);
|
27
|
+
};
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import { Box, BoxProps, Center } from "@chakra-ui/react";
|
2
|
+
import { spinnerColorData } from "@vygruppen/spor-loader";
|
3
|
+
import React from "react";
|
4
|
+
import { ClientOnly } from "./ClientOnly";
|
5
|
+
import Lottie from "./Lottie";
|
6
|
+
|
7
|
+
export type SpinnerProps = BoxProps;
|
8
|
+
export type ColorSpinnerProps = SpinnerProps;
|
9
|
+
/** A circular spinner
|
10
|
+
*
|
11
|
+
* Can be used in place of a loading animation, or for reloading app state, for instance.
|
12
|
+
*
|
13
|
+
* ```tsx
|
14
|
+
* <ColorSpinner width="64px" height="64px" />
|
15
|
+
* ```
|
16
|
+
*
|
17
|
+
* You can also pass an explanatory text as `children`:
|
18
|
+
*
|
19
|
+
* ```tsx
|
20
|
+
* <ColorSpinner>
|
21
|
+
* Hold your horses
|
22
|
+
* </ColorSpinner>
|
23
|
+
*/
|
24
|
+
export const ColorSpinner = ({
|
25
|
+
children,
|
26
|
+
width,
|
27
|
+
maxWidth,
|
28
|
+
...props
|
29
|
+
}: SpinnerProps) => {
|
30
|
+
return (
|
31
|
+
<Center flexDirection="column" {...props}>
|
32
|
+
<Box width={width} maxWidth={maxWidth}>
|
33
|
+
<ClientOnly>
|
34
|
+
{() => <Lottie animationData={spinnerColorData} />}
|
35
|
+
</ClientOnly>
|
36
|
+
</Box>
|
37
|
+
{children && (
|
38
|
+
<Box mt={3} fontWeight="bold">
|
39
|
+
{children}
|
40
|
+
</Box>
|
41
|
+
)}
|
42
|
+
</Center>
|
43
|
+
);
|
44
|
+
};
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import { Box, BoxProps } from "@chakra-ui/react";
|
2
|
+
import { contentLoaderData } from "@vygruppen/spor-loader";
|
3
|
+
import React from "react";
|
4
|
+
import { ClientOnly } from "./ClientOnly";
|
5
|
+
import Lottie from "./Lottie";
|
6
|
+
|
7
|
+
export type ContentLoaderProps = BoxProps;
|
8
|
+
/**
|
9
|
+
* ContentLoader is a component that renders a loading animation.
|
10
|
+
* It should mostly be used for
|
11
|
+
*/
|
12
|
+
export const ContentLoader = ({ children, ...props }: ContentLoaderProps) => {
|
13
|
+
return (
|
14
|
+
<Box {...props}>
|
15
|
+
<Box maxWidth="140px" mx="auto">
|
16
|
+
<ClientOnly>
|
17
|
+
{() => <Lottie animationData={contentLoaderData} />}
|
18
|
+
</ClientOnly>
|
19
|
+
</Box>
|
20
|
+
{children && (
|
21
|
+
<Box textAlign="center" fontWeight="bold">
|
22
|
+
{children}
|
23
|
+
</Box>
|
24
|
+
)}
|
25
|
+
</Box>
|
26
|
+
);
|
27
|
+
};
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import { Box, BoxProps, Center } from "@chakra-ui/react";
|
2
|
+
import { fullScreenLoaderWhiteData } from "@vygruppen/spor-loader";
|
3
|
+
import React from "react";
|
4
|
+
import { ClientOnly } from "./ClientOnly";
|
5
|
+
import Lottie from "./Lottie";
|
6
|
+
|
7
|
+
type DarkFullScreenLoaderProps = BoxProps;
|
8
|
+
|
9
|
+
export const DarkFullScreenLoader = ({
|
10
|
+
width,
|
11
|
+
maxWidth,
|
12
|
+
...props
|
13
|
+
}: DarkFullScreenLoaderProps) => {
|
14
|
+
return (
|
15
|
+
<Center height="100%" background="darkTeal" {...props}>
|
16
|
+
<Box width={width} maxWidth={maxWidth}>
|
17
|
+
<ClientOnly>
|
18
|
+
{() => <Lottie animationData={fullScreenLoaderWhiteData} />}
|
19
|
+
</ClientOnly>
|
20
|
+
</Box>
|
21
|
+
</Center>
|
22
|
+
);
|
23
|
+
};
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import { Box, BoxProps, Center } from "@chakra-ui/react";
|
2
|
+
import { inlineLoaderDarkData } from "@vygruppen/spor-loader";
|
3
|
+
import React from "react";
|
4
|
+
import { ClientOnly } from "./ClientOnly";
|
5
|
+
import Lottie from "./Lottie";
|
6
|
+
|
7
|
+
export type DarkInlineLoaderProps = Exclude<BoxProps, "children">;
|
8
|
+
/**
|
9
|
+
* Loading component that works well in bounded contexts, like inside a button.
|
10
|
+
*/
|
11
|
+
export const DarkInlineLoader = ({
|
12
|
+
width,
|
13
|
+
maxWidth,
|
14
|
+
...props
|
15
|
+
}: DarkInlineLoaderProps) => {
|
16
|
+
return (
|
17
|
+
<Center {...props}>
|
18
|
+
<Box width={width} maxWidth={maxWidth}>
|
19
|
+
<ClientOnly>
|
20
|
+
{() => <Lottie animationData={inlineLoaderDarkData} />}
|
21
|
+
</ClientOnly>
|
22
|
+
</Box>
|
23
|
+
</Center>
|
24
|
+
);
|
25
|
+
};
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import { Box, BoxProps, Center } from "@chakra-ui/react";
|
2
|
+
import { spinnerDarkData } from "@vygruppen/spor-loader";
|
3
|
+
import React from "react";
|
4
|
+
import { ClientOnly } from "./ClientOnly";
|
5
|
+
import Lottie from "./Lottie";
|
6
|
+
|
7
|
+
export type DarkSpinnerProps = BoxProps;
|
8
|
+
/** A circular spinner
|
9
|
+
*
|
10
|
+
* Can be used in place of a loading animation, or for reloading app state, for instance.
|
11
|
+
*
|
12
|
+
* ```tsx
|
13
|
+
* <DarkSpinner width="64px" height="64px" />
|
14
|
+
* ```
|
15
|
+
*
|
16
|
+
* You can also pass an explanatory text as `children`:
|
17
|
+
*
|
18
|
+
* ```tsx
|
19
|
+
* <DarkSpinner>
|
20
|
+
* Hold your horses
|
21
|
+
* </DarkSpinner>
|
22
|
+
*/
|
23
|
+
export const DarkSpinner = ({
|
24
|
+
children,
|
25
|
+
width,
|
26
|
+
maxWidth,
|
27
|
+
...props
|
28
|
+
}: DarkSpinnerProps) => {
|
29
|
+
return (
|
30
|
+
<Center flexDirection="column" {...props}>
|
31
|
+
<Box width={width} maxWidth={maxWidth}>
|
32
|
+
<ClientOnly>
|
33
|
+
{() => <Lottie animationData={spinnerDarkData} />}
|
34
|
+
</ClientOnly>
|
35
|
+
</Box>
|
36
|
+
{children && (
|
37
|
+
<Box mt={3} fontWeight="bold">
|
38
|
+
{children}
|
39
|
+
</Box>
|
40
|
+
)}
|
41
|
+
</Center>
|
42
|
+
);
|
43
|
+
};
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import { Box, BoxProps, Center } from "@chakra-ui/react";
|
2
|
+
import { fullScreenLoaderBlackData } from "@vygruppen/spor-loader";
|
3
|
+
import React from "react";
|
4
|
+
import { ClientOnly } from "./ClientOnly";
|
5
|
+
import Lottie from "./Lottie";
|
6
|
+
|
7
|
+
type LightFullScreenLoaderProps = BoxProps;
|
8
|
+
|
9
|
+
export const LightFullScreenLoader = ({
|
10
|
+
width,
|
11
|
+
maxWidth,
|
12
|
+
...props
|
13
|
+
}: LightFullScreenLoaderProps) => {
|
14
|
+
return (
|
15
|
+
<Center height="100%" background="white" {...props}>
|
16
|
+
<Box width={width} maxWidth={maxWidth}>
|
17
|
+
<ClientOnly>
|
18
|
+
{() => <Lottie animationData={fullScreenLoaderBlackData} />}
|
19
|
+
</ClientOnly>
|
20
|
+
</Box>
|
21
|
+
</Center>
|
22
|
+
);
|
23
|
+
};
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import { Box, BoxProps, Center } from "@chakra-ui/react";
|
2
|
+
import { inlineLoaderLightData } from "@vygruppen/spor-loader";
|
3
|
+
import React from "react";
|
4
|
+
import { ClientOnly } from "./ClientOnly";
|
5
|
+
import Lottie from "./Lottie";
|
6
|
+
|
7
|
+
export type LightInlineLoaderProps = Exclude<BoxProps, "children">;
|
8
|
+
/**
|
9
|
+
* Loading component that works well in bounded contexts, like inside a button.
|
10
|
+
*/
|
11
|
+
export const LightInlineLoader = ({
|
12
|
+
width,
|
13
|
+
maxWidth,
|
14
|
+
...props
|
15
|
+
}: LightInlineLoaderProps) => {
|
16
|
+
return (
|
17
|
+
<Center {...props}>
|
18
|
+
<Box width={width} maxWidth={maxWidth}>
|
19
|
+
<ClientOnly>
|
20
|
+
{() => <Lottie animationData={inlineLoaderLightData} />}
|
21
|
+
</ClientOnly>
|
22
|
+
</Box>
|
23
|
+
</Center>
|
24
|
+
);
|
25
|
+
};
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import { Box, BoxProps, Center } from "@chakra-ui/react";
|
2
|
+
import { spinnerLightData } from "@vygruppen/spor-loader";
|
3
|
+
import React from "react";
|
4
|
+
import { ClientOnly } from "./ClientOnly";
|
5
|
+
import Lottie from "./Lottie";
|
6
|
+
|
7
|
+
export type LightSpinnerProps = BoxProps;
|
8
|
+
/** A circular spinner
|
9
|
+
*
|
10
|
+
* Can be used in place of a loading animation, or for reloading app state, for instance.
|
11
|
+
*
|
12
|
+
* ```tsx
|
13
|
+
* <LightSpinner width="64px" height="64px" />
|
14
|
+
* ```
|
15
|
+
*
|
16
|
+
* You can also pass an explanatory text as `children`:
|
17
|
+
*
|
18
|
+
* ```tsx
|
19
|
+
* <LightSpinner>
|
20
|
+
* Hold your horses
|
21
|
+
* </LightSpinner>
|
22
|
+
*/
|
23
|
+
export const LightSpinner = ({
|
24
|
+
children,
|
25
|
+
width,
|
26
|
+
maxWidth,
|
27
|
+
...props
|
28
|
+
}: LightSpinnerProps) => {
|
29
|
+
return (
|
30
|
+
<Center flexDirection="column" {...props}>
|
31
|
+
<Box width={width} maxWidth={maxWidth}>
|
32
|
+
<ClientOnly>{() => <Lottie animationData={spinnerLightData} />}</ClientOnly>
|
33
|
+
</Box>
|
34
|
+
{children && (
|
35
|
+
<Box mt={3} fontWeight="bold">
|
36
|
+
{children}
|
37
|
+
</Box>
|
38
|
+
)}
|
39
|
+
</Center>
|
40
|
+
);
|
41
|
+
};
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import { useLottie } from "lottie-react";
|
2
|
+
import React from "react";
|
3
|
+
|
4
|
+
/**
|
5
|
+
* A wrapper around Lottie to make it tree-shakeable for SSR.
|
6
|
+
*/
|
7
|
+
export default function Lottie({ animationData }: { animationData: any }) {
|
8
|
+
const { View } = useLottie({ animationData, loop: true });
|
9
|
+
return <>{View}</>;
|
10
|
+
}
|
@@ -0,0 +1,128 @@
|
|
1
|
+
import { Box, BoxProps, Flex, Text } from "@chakra-ui/react";
|
2
|
+
import React from "react";
|
3
|
+
import { useProgressBar } from "react-aria";
|
4
|
+
import { createTexts, useTranslation } from "..";
|
5
|
+
import { useRotatingLabel } from "./useRotatingLabel";
|
6
|
+
|
7
|
+
type ProgressBarProps = BoxProps & {
|
8
|
+
/** The percentage of progress made.
|
9
|
+
*
|
10
|
+
* The value must be between 0 and 100 */
|
11
|
+
value: number;
|
12
|
+
/** The height of the progress bar.
|
13
|
+
* Defaults to .5rem
|
14
|
+
**/
|
15
|
+
height?: BoxProps["height"];
|
16
|
+
/** The width of the progress bar.
|
17
|
+
*
|
18
|
+
* Defaults to the width of its container
|
19
|
+
**/
|
20
|
+
width?: BoxProps["width"];
|
21
|
+
|
22
|
+
/** Pass if no label is passed to the label */
|
23
|
+
"aria-label": string;
|
24
|
+
/** Optional text shown below the loader.
|
25
|
+
*
|
26
|
+
* If you pass an array of strings, the text will rotate every 5 seconds. If you want to change the delay, pass the delay in milliseconds to the `labelRotationDelay` prop.
|
27
|
+
*/
|
28
|
+
label: string | string[];
|
29
|
+
/** The number of milliseconds a label is shown, if an array of strings is passed to the `label` prop.
|
30
|
+
*
|
31
|
+
* Defaults to 5000 (5 seconds).
|
32
|
+
*/
|
33
|
+
labelRotationDelay?: number;
|
34
|
+
};
|
35
|
+
|
36
|
+
/**
|
37
|
+
* Shows the progress of a loading process.
|
38
|
+
*
|
39
|
+
* You can pass the amount of progress with the `value` prop:
|
40
|
+
*
|
41
|
+
* ```tsx
|
42
|
+
* <ProgressBar value={50} />
|
43
|
+
* ```
|
44
|
+
*
|
45
|
+
* You can also pass a label to show below the loader:
|
46
|
+
*
|
47
|
+
* ```tsx
|
48
|
+
* <ProgressBar value={50} label="Loading..." />
|
49
|
+
* ```
|
50
|
+
*
|
51
|
+
* If you pass an array of strings, the text will rotate every 5 seconds. If you want to change the delay, pass the delay in milliseconds to the `labelRotationDelay` prop.
|
52
|
+
*
|
53
|
+
* ```tsx
|
54
|
+
* <ProgressBar value={50} label={["Loading...", "Almost there..."]} />
|
55
|
+
* ```
|
56
|
+
*
|
57
|
+
* If you don't pass a label, you should pass an `aria-label` prop:
|
58
|
+
*
|
59
|
+
* ```tsx
|
60
|
+
* <ProgressBar value={50} aria-label="Loading..." />
|
61
|
+
* ```
|
62
|
+
*/
|
63
|
+
export const ProgressBar = ({
|
64
|
+
value,
|
65
|
+
label,
|
66
|
+
labelRotationDelay = 5000,
|
67
|
+
height = "0.5rem",
|
68
|
+
width = "100%",
|
69
|
+
"aria-label": ariaLabel,
|
70
|
+
...rest
|
71
|
+
}: ProgressBarProps) => {
|
72
|
+
const { t } = useTranslation();
|
73
|
+
const currentLoadingText = useRotatingLabel({
|
74
|
+
label,
|
75
|
+
delay: labelRotationDelay,
|
76
|
+
});
|
77
|
+
const { labelProps, progressBarProps } = useProgressBar({
|
78
|
+
isIndeterminate: value === undefined,
|
79
|
+
value,
|
80
|
+
"aria-label": ariaLabel || t(texts.label(value)),
|
81
|
+
});
|
82
|
+
return (
|
83
|
+
<>
|
84
|
+
<Box
|
85
|
+
{...progressBarProps}
|
86
|
+
title={t(texts.label(value))}
|
87
|
+
minWidth="100px"
|
88
|
+
{...rest}
|
89
|
+
>
|
90
|
+
<Flex
|
91
|
+
backgroundColor="coralGreen"
|
92
|
+
borderRadius="sm"
|
93
|
+
width={width}
|
94
|
+
justifyContent="flex-start"
|
95
|
+
marginX="auto"
|
96
|
+
>
|
97
|
+
<Box
|
98
|
+
backgroundColor="greenHaze"
|
99
|
+
borderRadius="sm"
|
100
|
+
height={height}
|
101
|
+
width={`${value}%`}
|
102
|
+
maxWidth="100%"
|
103
|
+
transition="width .2s ease-out"
|
104
|
+
/>
|
105
|
+
</Flex>
|
106
|
+
{currentLoadingText && (
|
107
|
+
<Text
|
108
|
+
textAlign="center"
|
109
|
+
marginTop={2}
|
110
|
+
fontWeight="bold"
|
111
|
+
{...labelProps}
|
112
|
+
>
|
113
|
+
{currentLoadingText}
|
114
|
+
</Text>
|
115
|
+
)}
|
116
|
+
</Box>
|
117
|
+
</>
|
118
|
+
);
|
119
|
+
};
|
120
|
+
|
121
|
+
const texts = createTexts({
|
122
|
+
label: (value) => ({
|
123
|
+
nb: `${value}% ferdig`,
|
124
|
+
nn: `${value}% ferdig`,
|
125
|
+
sv: `${value}% klart`,
|
126
|
+
en: `${value}% done`,
|
127
|
+
}),
|
128
|
+
});
|
@@ -0,0 +1,140 @@
|
|
1
|
+
import { Box, BoxProps, Text } from "@chakra-ui/react";
|
2
|
+
import React, { useId, useRef } from "react";
|
3
|
+
import { useProgressBar } from "react-aria";
|
4
|
+
import { createTexts, useTranslation } from "..";
|
5
|
+
import { useRotatingLabel } from "./useRotatingLabel";
|
6
|
+
|
7
|
+
type ProgressLoaderProps = BoxProps & {
|
8
|
+
/** The percentage of progress made.
|
9
|
+
*
|
10
|
+
* The value must be between 0 and 100 */
|
11
|
+
value: number;
|
12
|
+
/** The width of the progress bar.
|
13
|
+
*
|
14
|
+
* Defaults to the width of its container
|
15
|
+
**/
|
16
|
+
width?: BoxProps["width"];
|
17
|
+
|
18
|
+
/** Pass if no label is passed to the label */
|
19
|
+
"aria-label": string;
|
20
|
+
/** Optional text shown below the loader.
|
21
|
+
*
|
22
|
+
* If you pass an array of strings, the text will rotate every 5 seconds. If you want to change the delay, pass the delay in milliseconds to the `labelRotationDelay` prop.
|
23
|
+
*/
|
24
|
+
label: string | string[];
|
25
|
+
/** The number of milliseconds a label is shown, if an array of strings is passed to the `label` prop.
|
26
|
+
*
|
27
|
+
* Defaults to 5000 (5 seconds).
|
28
|
+
*/
|
29
|
+
labelRotationDelay?: number;
|
30
|
+
};
|
31
|
+
|
32
|
+
/**
|
33
|
+
* Shows the progress of a loading process.
|
34
|
+
*
|
35
|
+
* You can pass the amount of progress with the `value` prop:
|
36
|
+
*
|
37
|
+
* ```tsx
|
38
|
+
* <ProgressLoader value={50} />
|
39
|
+
* ```
|
40
|
+
*
|
41
|
+
* You can also pass a label to show below the loader:
|
42
|
+
*
|
43
|
+
* ```tsx
|
44
|
+
* <ProgressLoader value={50} label="Loading..." />
|
45
|
+
* ```
|
46
|
+
*
|
47
|
+
* If you pass an array of strings, the text will rotate every 5 seconds. If you want to change the delay, pass the delay in milliseconds to the `labelRotationDelay` prop.
|
48
|
+
*
|
49
|
+
* ```tsx
|
50
|
+
* <ProgressLoader value={50} label={["Loading...", "Almost done..."]} />
|
51
|
+
* ```
|
52
|
+
*
|
53
|
+
* If you don't pass a label, you should pass an `aria-label` prop:
|
54
|
+
*
|
55
|
+
* ```tsx
|
56
|
+
* <ProgressLoader value={50} aria-label="Fetching your trips..." />
|
57
|
+
* ```
|
58
|
+
*/
|
59
|
+
export const ProgressLoader = ({
|
60
|
+
value,
|
61
|
+
label,
|
62
|
+
labelRotationDelay = 5000,
|
63
|
+
"aria-label": ariaLabel,
|
64
|
+
width,
|
65
|
+
...rest
|
66
|
+
}: ProgressLoaderProps) => {
|
67
|
+
const { t } = useTranslation();
|
68
|
+
const currentLoadingText = useRotatingLabel({
|
69
|
+
label,
|
70
|
+
delay: labelRotationDelay,
|
71
|
+
});
|
72
|
+
const { labelProps, progressBarProps } = useProgressBar({
|
73
|
+
isIndeterminate: value === undefined,
|
74
|
+
value,
|
75
|
+
"aria-label": ariaLabel ?? t(texts.fallbackLabel(value ?? "?")),
|
76
|
+
});
|
77
|
+
const pathRef = useRef<SVGPathElement>(null);
|
78
|
+
const progressPathLength = pathRef.current?.getTotalLength() ?? 0;
|
79
|
+
const progress = ((value - 100) / 100) * progressPathLength;
|
80
|
+
const id = useId();
|
81
|
+
return (
|
82
|
+
<Box {...progressBarProps} minWidth="100px" width={width} {...rest}>
|
83
|
+
<Box as="svg" viewBox="0 0 246 78" fill="none">
|
84
|
+
<Box
|
85
|
+
as="path"
|
86
|
+
id={`${id}-start-dot`}
|
87
|
+
d="M14.0479 44.8251C19.4332 44.8251 23.7988 40.5242 23.7988 35.2187C23.7988 29.9133 19.4332 25.6124 14.0479 25.6124C8.66254 25.6124 4.29688 29.9133 4.29688 35.2187C4.29688 40.5242 8.66254 44.8251 14.0479 44.8251Z"
|
88
|
+
fill="#FFB466"
|
89
|
+
/>
|
90
|
+
<Box
|
91
|
+
as="path"
|
92
|
+
id={`${id}-track`}
|
93
|
+
d="M204.911 39.1156C204.911 39.1156 175.012 46.8319 157.651 30.4354C140.29 14.0388 121 21.7547 110.391 47.6529C103.22 65.157 78.9634 67.0859 67.9533 47.6529C59.8376 33.3287 36.125 37.1866 36.125 37.1866"
|
94
|
+
stroke="coralGreen"
|
95
|
+
strokeWidth="13.6469"
|
96
|
+
strokeLinecap="round"
|
97
|
+
strokeLinejoin="round"
|
98
|
+
/>
|
99
|
+
<Box
|
100
|
+
as="path"
|
101
|
+
id={`${id}-progress`}
|
102
|
+
d="M204.911 39.1156C204.911 39.1156 175.012 46.8319 157.651 30.4354C140.29 14.0388 121 21.7547 110.391 47.6529C103.22 65.157 78.9634 67.0859 67.9533 47.6529C59.8376 33.3287 36.125 37.1866 36.125 37.1866"
|
103
|
+
stroke="greenHaze"
|
104
|
+
strokeWidth="13.6469"
|
105
|
+
strokeLinecap="round"
|
106
|
+
strokeLinejoin="round"
|
107
|
+
strokeDasharray={progressPathLength}
|
108
|
+
strokeDashoffset={progress}
|
109
|
+
transition="stroke-dashoffset .2s ease-out"
|
110
|
+
ref={pathRef as any}
|
111
|
+
/>
|
112
|
+
<Box
|
113
|
+
as="path"
|
114
|
+
id={`${id}-end-dot`}
|
115
|
+
d="M226.025 44.8251C231.411 44.8251 235.776 40.5242 235.776 35.2187C235.776 29.9133 231.411 25.6124 226.025 25.6124C220.64 25.6124 216.274 29.9133 216.274 35.2187C216.274 40.5242 220.64 44.8251 226.025 44.8251Z"
|
116
|
+
fill="#688CBA"
|
117
|
+
/>
|
118
|
+
</Box>
|
119
|
+
{currentLoadingText && (
|
120
|
+
<Text
|
121
|
+
textAlign="center"
|
122
|
+
marginTop={2}
|
123
|
+
fontWeight="bold"
|
124
|
+
{...labelProps}
|
125
|
+
>
|
126
|
+
{currentLoadingText}
|
127
|
+
</Text>
|
128
|
+
)}
|
129
|
+
</Box>
|
130
|
+
);
|
131
|
+
};
|
132
|
+
|
133
|
+
const texts = createTexts({
|
134
|
+
fallbackLabel: (value) => ({
|
135
|
+
nb: `${value}% ferdig`,
|
136
|
+
nn: `${value}% ferdig`,
|
137
|
+
sv: `${value}% klart`,
|
138
|
+
en: `${value}% done`,
|
139
|
+
}),
|
140
|
+
});
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import {
|
2
|
+
BoxProps,
|
3
|
+
forwardRef,
|
4
|
+
Skeleton as ChakraSkeleton,
|
5
|
+
} from "@chakra-ui/react";
|
6
|
+
import React from "react";
|
7
|
+
|
8
|
+
export type SkeletonProps = BoxProps & {
|
9
|
+
isLoaded?: boolean;
|
10
|
+
};
|
11
|
+
/**
|
12
|
+
* Skeleton renders a loading animation for a given box. It works great as a placeholder to avoid layout shifts.
|
13
|
+
*/
|
14
|
+
export const Skeleton = forwardRef<SkeletonProps, "div">((props, ref) => (
|
15
|
+
<ChakraSkeleton {...props} ref={ref} />
|
16
|
+
));
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import {
|
2
|
+
BoxProps,
|
3
|
+
SkeletonCircle as ChakraSkeletonCircle,
|
4
|
+
} from "@chakra-ui/react";
|
5
|
+
import React from "react";
|
6
|
+
|
7
|
+
export type SkeletonCircleProps = BoxProps;
|
8
|
+
/**
|
9
|
+
* SkeletonCircle renders a loading animation for a given circle. It works great as a placeholder to avoid layout shifts.
|
10
|
+
*/
|
11
|
+
export const SkeletonCircle = (props: SkeletonCircleProps) => (
|
12
|
+
<ChakraSkeletonCircle boxSize={6} borderRadius="50%" {...props} />
|
13
|
+
);
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import { BoxProps, SkeletonText as ChakraSkeletonText } from "@chakra-ui/react";
|
2
|
+
import React from "react";
|
3
|
+
|
4
|
+
export type SkeletonTextProps = BoxProps;
|
5
|
+
/**
|
6
|
+
* SkeletonText renders a loading animation for a given text. It works great as a placeholder to avoid layout shifts.
|
7
|
+
*/
|
8
|
+
export const SkeletonText = (props: SkeletonTextProps) => (
|
9
|
+
<ChakraSkeletonText boxSize={6} {...props} />
|
10
|
+
);
|