@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.
- package/.turbo/turbo-build.log +12 -10
- package/CHANGELOG.md +31 -0
- package/dist/index.d.ts +6720 -27
- package/dist/index.js +14143 -140
- package/dist/index.mjs +13822 -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 +171 -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,84 @@
|
|
1
|
+
import {
|
2
|
+
Accordion,
|
3
|
+
AccordionButton,
|
4
|
+
AccordionIcon,
|
5
|
+
AccordionItem,
|
6
|
+
AccordionPanel,
|
7
|
+
Box,
|
8
|
+
Flex,
|
9
|
+
} from "@chakra-ui/react";
|
10
|
+
import React from "react";
|
11
|
+
import { AlertIcon } from "./AlertIcon";
|
12
|
+
import { BaseAlert, BaseAlertProps } from "./BaseAlert";
|
13
|
+
|
14
|
+
type ExpandableAlertProps = BaseAlertProps & {
|
15
|
+
/** The title string */
|
16
|
+
title: string;
|
17
|
+
/** Callback for when the expandable panel is opened or closed */
|
18
|
+
onToggle?: (isOpen: boolean) => void;
|
19
|
+
/** Whether or not the default state of the expandable alert is open */
|
20
|
+
defaultOpen?: boolean;
|
21
|
+
/**
|
22
|
+
* The HTML element used for the `title` prop.
|
23
|
+
*
|
24
|
+
* Defaults to h3 */
|
25
|
+
headingLevel?: "h2" | "h3" | "h4" | "h5" | "h6";
|
26
|
+
};
|
27
|
+
/**
|
28
|
+
* An expandable alert component.
|
29
|
+
*
|
30
|
+
* A regular alert with an expandable body. The expandable body can be used to provide more information about the alert.
|
31
|
+
*
|
32
|
+
* ```tsx
|
33
|
+
* <ExpandableAlert variant="alt-transport" title="Replacement bus service">
|
34
|
+
* The replacement bus service will be running from 10:00 to 16:00.
|
35
|
+
* </ExpandableAlert>
|
36
|
+
* ```
|
37
|
+
*/
|
38
|
+
export const ExpandableAlert = ({
|
39
|
+
variant,
|
40
|
+
children,
|
41
|
+
title,
|
42
|
+
headingLevel = "h3",
|
43
|
+
defaultOpen = false,
|
44
|
+
onToggle = () => {},
|
45
|
+
}: ExpandableAlertProps) => {
|
46
|
+
return (
|
47
|
+
<BaseAlert variant={variant} paddingX={0} paddingY={0} padding={0}>
|
48
|
+
<Accordion
|
49
|
+
onChange={(expandedIndex) => onToggle(expandedIndex === 0)}
|
50
|
+
defaultIndex={defaultOpen ? 0 : -1}
|
51
|
+
allowToggle
|
52
|
+
flexGrow="1"
|
53
|
+
>
|
54
|
+
<AccordionItem>
|
55
|
+
<AccordionButton paddingX={3} paddingY={2}>
|
56
|
+
<Flex
|
57
|
+
justifyContent="space-between"
|
58
|
+
alignItems="center"
|
59
|
+
flexGrow="1"
|
60
|
+
>
|
61
|
+
<Flex as={headingLevel}>
|
62
|
+
<AlertIcon variant={variant} />
|
63
|
+
<Box
|
64
|
+
as="span"
|
65
|
+
sx={{
|
66
|
+
// Truncate the title to one line
|
67
|
+
display: "-webkit-box",
|
68
|
+
overflow: "hidden",
|
69
|
+
"-webkit-line-clamp": "1",
|
70
|
+
"-webkit-box-orient": "vertical",
|
71
|
+
}}
|
72
|
+
>
|
73
|
+
{title}
|
74
|
+
</Box>
|
75
|
+
</Flex>
|
76
|
+
<AccordionIcon />
|
77
|
+
</Flex>
|
78
|
+
</AccordionButton>
|
79
|
+
<AccordionPanel>{children}</AccordionPanel>
|
80
|
+
</AccordionItem>
|
81
|
+
</Accordion>
|
82
|
+
</BaseAlert>
|
83
|
+
);
|
84
|
+
};
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import React from "react";
|
2
|
+
import { AlertIcon } from "./AlertIcon";
|
3
|
+
import { BaseAlert, BaseAlertProps } from "./BaseAlert";
|
4
|
+
|
5
|
+
type StaticAlertProps = BaseAlertProps;
|
6
|
+
|
7
|
+
/**
|
8
|
+
* A static alert component.
|
9
|
+
*
|
10
|
+
* This alert component cannot be closed, nor dismissed.
|
11
|
+
*
|
12
|
+
* ```tsx
|
13
|
+
* <StaticAlert variant="info" title="Nice to know">
|
14
|
+
* Thomas the Train was originally only a wooden toy made for the creator’s son.
|
15
|
+
* </StaticAlert>
|
16
|
+
* ```
|
17
|
+
*/
|
18
|
+
export const StaticAlert = ({ children, ...props }: StaticAlertProps) => {
|
19
|
+
return (
|
20
|
+
<BaseAlert {...props}>
|
21
|
+
<AlertIcon variant={props.variant} />
|
22
|
+
{children}
|
23
|
+
</BaseAlert>
|
24
|
+
);
|
25
|
+
};
|
@@ -0,0 +1,23 @@
|
|
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 { Button } from ".";
|
6
|
+
|
7
|
+
describe("<Button />", () => {
|
8
|
+
it("works like a button", async () => {
|
9
|
+
const handleClick = vi.fn();
|
10
|
+
const { getByRole } = render(
|
11
|
+
<Button variant="primary" onClick={handleClick}>
|
12
|
+
Click me
|
13
|
+
</Button>
|
14
|
+
);
|
15
|
+
getByRole("button").click();
|
16
|
+
expect(handleClick).toHaveBeenCalled();
|
17
|
+
});
|
18
|
+
|
19
|
+
it("is accessible", async () => {
|
20
|
+
const { container } = render(<Button variant="primary">Click me</Button>);
|
21
|
+
expect(await axe(container)).toHaveNoViolations();
|
22
|
+
});
|
23
|
+
});
|
@@ -0,0 +1,162 @@
|
|
1
|
+
import {
|
2
|
+
Box,
|
3
|
+
Center,
|
4
|
+
Button as ChakraButton,
|
5
|
+
ButtonProps as ChakraButtonProps,
|
6
|
+
forwardRef,
|
7
|
+
useButtonGroup,
|
8
|
+
} from "@chakra-ui/react";
|
9
|
+
import React from "react";
|
10
|
+
import { createTexts, useTranslation } from "../i18n";
|
11
|
+
import { ColorInlineLoader } from "../loader";
|
12
|
+
|
13
|
+
export type ButtonProps = Exclude<
|
14
|
+
ChakraButtonProps,
|
15
|
+
"colorScheme" | "loadingText" | "size" | "variant"
|
16
|
+
> & {
|
17
|
+
/**
|
18
|
+
* The size of the button.
|
19
|
+
*
|
20
|
+
* Defaults to "md"
|
21
|
+
* */
|
22
|
+
size?: "xs" | "sm" | "md" | "lg";
|
23
|
+
/** The different variants of a button
|
24
|
+
*
|
25
|
+
* Defaults to "primary"
|
26
|
+
*/
|
27
|
+
variant?:
|
28
|
+
| "control"
|
29
|
+
| "primary"
|
30
|
+
| "secondary"
|
31
|
+
| "tertiary"
|
32
|
+
| "additional"
|
33
|
+
| "ghost"
|
34
|
+
| "floating";
|
35
|
+
};
|
36
|
+
/**
|
37
|
+
* Buttons are used to trigger actions.
|
38
|
+
*
|
39
|
+
* There are several button variants. You can specify which one you want with the `variant` prop. The available variants are:
|
40
|
+
*
|
41
|
+
* - `control`: This button is used for ticket controls only.
|
42
|
+
* - `primary`: This is our main button. It's used for the main actions in a view, like a call to action. There should only be a single primary button in each view.
|
43
|
+
* - `secondary`: Used for secondary actions in a view, and when you need to make several actions available at the same time.
|
44
|
+
* - `tertiary`: Used for non-essential actions, as well as in combination with the primary button.
|
45
|
+
* - `additional`: Used for additional choices, like a less important tertiary action.
|
46
|
+
* - `ghost`: Used inside other interactive elements, like date pickers and input fields.
|
47
|
+
* - `floating`: Used for floating actions, like a menu button in a menu.
|
48
|
+
*
|
49
|
+
* ```tsx
|
50
|
+
* <Button variant="primary" onClick={confirmOrder}>
|
51
|
+
* Buy trip
|
52
|
+
* </Button>
|
53
|
+
* ```
|
54
|
+
*
|
55
|
+
* There are also different sizes. You can specify which one you want with the `size` prop. The available sizes are "lg", "md", "sm" and "xs".
|
56
|
+
*
|
57
|
+
* ```tsx
|
58
|
+
* <Button variant="tertiary" size="sm" onClick={cancelOrder}>
|
59
|
+
* Cancel trip
|
60
|
+
* </Button>
|
61
|
+
* ```
|
62
|
+
*
|
63
|
+
* @see https://spor.vy.no/komponenter/button
|
64
|
+
*/
|
65
|
+
export const Button = forwardRef<ButtonProps, "button">((props, ref) => {
|
66
|
+
const {
|
67
|
+
size,
|
68
|
+
variant,
|
69
|
+
children,
|
70
|
+
isLoading,
|
71
|
+
isDisabled,
|
72
|
+
leftIcon,
|
73
|
+
rightIcon,
|
74
|
+
} = props;
|
75
|
+
const ariaLabel = useCorrectAriaLabel(props);
|
76
|
+
const buttonGroup = useButtonGroup();
|
77
|
+
const finalVariant = (variant ??
|
78
|
+
buttonGroup?.variant ??
|
79
|
+
"primary") as Required<ButtonProps["variant"]>;
|
80
|
+
const finalSize = (size ?? buttonGroup?.size ?? "md") as Required<
|
81
|
+
ButtonProps["size"]
|
82
|
+
>;
|
83
|
+
|
84
|
+
return (
|
85
|
+
<ChakraButton
|
86
|
+
size={finalSize}
|
87
|
+
variant={finalVariant}
|
88
|
+
{...props}
|
89
|
+
ref={ref}
|
90
|
+
aria-label={ariaLabel}
|
91
|
+
aria-busy={isLoading}
|
92
|
+
isDisabled={isDisabled || isLoading}
|
93
|
+
leftIcon={
|
94
|
+
isLoading && leftIcon ? (
|
95
|
+
<Box visibility={isLoading ? "hidden" : "visible"} aria-hidden="true">
|
96
|
+
{leftIcon}
|
97
|
+
</Box>
|
98
|
+
) : (
|
99
|
+
leftIcon
|
100
|
+
)
|
101
|
+
}
|
102
|
+
rightIcon={
|
103
|
+
isLoading && rightIcon ? (
|
104
|
+
<Box visibility={isLoading ? "hidden" : "visible"} aria-hidden="true">
|
105
|
+
{rightIcon}
|
106
|
+
</Box>
|
107
|
+
) : (
|
108
|
+
rightIcon
|
109
|
+
)
|
110
|
+
}
|
111
|
+
position="relative"
|
112
|
+
>
|
113
|
+
{isLoading && (
|
114
|
+
<Center
|
115
|
+
position="absolute"
|
116
|
+
right="0"
|
117
|
+
paddingBottom={1}
|
118
|
+
left="0"
|
119
|
+
paddingTop={2}
|
120
|
+
>
|
121
|
+
<ColorInlineLoader
|
122
|
+
maxWidth={getLoaderWidth(finalSize)}
|
123
|
+
width="100%"
|
124
|
+
mx={2}
|
125
|
+
/>
|
126
|
+
</Center>
|
127
|
+
)}
|
128
|
+
<Box visibility={isLoading ? "hidden" : "visible"}>{children}</Box>
|
129
|
+
</ChakraButton>
|
130
|
+
);
|
131
|
+
});
|
132
|
+
|
133
|
+
function getLoaderWidth(size: any) {
|
134
|
+
switch (size) {
|
135
|
+
case "xs":
|
136
|
+
return "4rem";
|
137
|
+
case "sm":
|
138
|
+
return "4rem";
|
139
|
+
case "md":
|
140
|
+
return "5rem";
|
141
|
+
case "lg":
|
142
|
+
default:
|
143
|
+
return "6rem";
|
144
|
+
}
|
145
|
+
}
|
146
|
+
|
147
|
+
function useCorrectAriaLabel(props: ButtonProps): string {
|
148
|
+
const { t } = useTranslation();
|
149
|
+
if (props.isLoading) {
|
150
|
+
return String(props.loadingText) ?? t(texts.loadingText);
|
151
|
+
}
|
152
|
+
return props["aria-label"] as string;
|
153
|
+
}
|
154
|
+
|
155
|
+
const texts = createTexts({
|
156
|
+
loadingText: {
|
157
|
+
nb: "Laster…",
|
158
|
+
nn: "Lastar…",
|
159
|
+
en: "Loading…",
|
160
|
+
sv: "Laddar…",
|
161
|
+
},
|
162
|
+
});
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import {
|
2
|
+
ButtonGroup as ChakraButtonGroup,
|
3
|
+
ButtonGroupProps as ChakraButtonGroupProps,
|
4
|
+
forwardRef,
|
5
|
+
} from "@chakra-ui/react";
|
6
|
+
import React from "react";
|
7
|
+
|
8
|
+
export type ButtonGroupProps = ChakraButtonGroupProps;
|
9
|
+
/**
|
10
|
+
* Group buttons together with a `ButtonGroup`!
|
11
|
+
*
|
12
|
+
* If you have more than one button next to eachother, you might want to add a `ButtonGroup` to group them.
|
13
|
+
*
|
14
|
+
* ```tsx
|
15
|
+
* <ButtonGroup>
|
16
|
+
* <Button variant="tertiary">Cancel</Button>
|
17
|
+
* <Button variant="primary">Save</Button>
|
18
|
+
* </ButtonGroup>
|
19
|
+
* ```
|
20
|
+
*
|
21
|
+
* You can specify the size of all buttons in a group with the `size` prop. You can also set the same variant across all buttons with the `variant` prop.
|
22
|
+
*
|
23
|
+
* ```tsx
|
24
|
+
* <ButtonGroup variant="secondary" size="md">
|
25
|
+
* <Button>Open</Button>
|
26
|
+
* <Button>Save</Button>
|
27
|
+
* </ButtonGroup>
|
28
|
+
* ```
|
29
|
+
*
|
30
|
+
* Finally, you can join several buttons together with the `isAttached` prop.
|
31
|
+
*
|
32
|
+
* ```tsx
|
33
|
+
* <ButtonGroup variant="secondary" size="md" isAttached>
|
34
|
+
* <Button>Open</Button>
|
35
|
+
* <IconButton>
|
36
|
+
* <SaveIcon aria-label="Save"/>
|
37
|
+
* </IconButton>
|
38
|
+
* </ButtonGroup>
|
39
|
+
* ```
|
40
|
+
*/
|
41
|
+
export const ButtonGroup = forwardRef<ButtonGroupProps, "div">((props, ref) => (
|
42
|
+
<ChakraButtonGroup {...props} ref={ref} />
|
43
|
+
));
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import { forwardRef } from "@chakra-ui/react";
|
2
|
+
import {
|
3
|
+
CloseFill18Icon,
|
4
|
+
CloseFill24Icon,
|
5
|
+
CloseFill30Icon,
|
6
|
+
} from "@vygruppen/spor-icon-react";
|
7
|
+
import React from "react";
|
8
|
+
import { createTexts, useTranslation } from "../i18n";
|
9
|
+
import { IconButton, IconButtonProps } from "./IconButton";
|
10
|
+
|
11
|
+
export type CloseButtonProps = Omit<
|
12
|
+
IconButtonProps,
|
13
|
+
"variant" | "aria-label"
|
14
|
+
> & {
|
15
|
+
/** Defaults to a localized version of "close" */
|
16
|
+
"aria-label"?: string;
|
17
|
+
};
|
18
|
+
|
19
|
+
/**
|
20
|
+
* A close button component.
|
21
|
+
*
|
22
|
+
* This button closes stuff, like modals and dialogs.
|
23
|
+
*
|
24
|
+
* ```tsx
|
25
|
+
* <CloseButton onClick={closeModal} />
|
26
|
+
* ```
|
27
|
+
*/
|
28
|
+
export const CloseButton = forwardRef<CloseButtonProps, "button">(
|
29
|
+
({ size = "sm", ...props }, ref) => {
|
30
|
+
const { t } = useTranslation();
|
31
|
+
return (
|
32
|
+
<IconButton
|
33
|
+
ref={ref}
|
34
|
+
variant="ghost"
|
35
|
+
icon={getIcon(size)}
|
36
|
+
size={size}
|
37
|
+
aria-label={props["aria-label"] || t(texts.close)}
|
38
|
+
{...props}
|
39
|
+
/>
|
40
|
+
);
|
41
|
+
}
|
42
|
+
);
|
43
|
+
|
44
|
+
const getIcon = (size: CloseButtonProps["size"]) => {
|
45
|
+
switch (size) {
|
46
|
+
case "xs":
|
47
|
+
case "sm":
|
48
|
+
return <CloseFill18Icon />;
|
49
|
+
case "md":
|
50
|
+
return <CloseFill24Icon />;
|
51
|
+
case "lg":
|
52
|
+
return <CloseFill30Icon />;
|
53
|
+
}
|
54
|
+
};
|
55
|
+
|
56
|
+
const texts = createTexts({
|
57
|
+
close: {
|
58
|
+
en: "Close",
|
59
|
+
nb: "Lukk",
|
60
|
+
nn: "Lukk",
|
61
|
+
sv: "Stäng",
|
62
|
+
},
|
63
|
+
});
|
@@ -0,0 +1,113 @@
|
|
1
|
+
import { Box, BoxProps, useMultiStyleConfig } from "@chakra-ui/react";
|
2
|
+
import { motion } from "framer-motion";
|
3
|
+
import React from "react";
|
4
|
+
|
5
|
+
const MotionBox = motion(Box);
|
6
|
+
|
7
|
+
type FloatingActionButtonProps = BoxProps & {
|
8
|
+
variant?: "green" | "light" | "dark";
|
9
|
+
placement?: "bottom right" | "bottom left" | "top right" | "top left";
|
10
|
+
icon: React.ReactNode;
|
11
|
+
children: React.ReactNode;
|
12
|
+
isTextVisible?: boolean;
|
13
|
+
};
|
14
|
+
|
15
|
+
/**
|
16
|
+
* Creates a floating action button.
|
17
|
+
*
|
18
|
+
* By default it will be placed at the bottom right of the screen. You can override this with specifying the `placement` prop.
|
19
|
+
*
|
20
|
+
* ```tsx
|
21
|
+
* <FloatingActionButton
|
22
|
+
* variant="green"
|
23
|
+
* icon={<TicketControlFill30Icon />}
|
24
|
+
* placement="bottom right"
|
25
|
+
* />
|
26
|
+
*/
|
27
|
+
export const FloatingActionButton = ({
|
28
|
+
children,
|
29
|
+
icon,
|
30
|
+
variant,
|
31
|
+
isTextVisible: externalIsTextVisible,
|
32
|
+
placement = "bottom right",
|
33
|
+
...props
|
34
|
+
}: FloatingActionButtonProps) => {
|
35
|
+
const [isTextVisible, setIsTextVisible] = React.useState(
|
36
|
+
externalIsTextVisible !== undefined ? externalIsTextVisible : true
|
37
|
+
);
|
38
|
+
const scrollDirection = useScrollDirection();
|
39
|
+
React.useEffect(() => {
|
40
|
+
if (externalIsTextVisible !== undefined) {
|
41
|
+
return;
|
42
|
+
}
|
43
|
+
const id = window.setTimeout(
|
44
|
+
() => setIsTextVisible(scrollDirection !== "down"),
|
45
|
+
1000
|
46
|
+
);
|
47
|
+
return () => window.clearTimeout(id);
|
48
|
+
}, [scrollDirection, externalIsTextVisible]);
|
49
|
+
|
50
|
+
React.useEffect(() => {
|
51
|
+
setIsTextVisible(!!externalIsTextVisible);
|
52
|
+
}, [externalIsTextVisible]);
|
53
|
+
|
54
|
+
const style = useMultiStyleConfig("FloatingActionButton", {
|
55
|
+
variant,
|
56
|
+
isTextVisible,
|
57
|
+
placement,
|
58
|
+
});
|
59
|
+
return (
|
60
|
+
<MotionBox
|
61
|
+
__css={style.container}
|
62
|
+
as="button"
|
63
|
+
aria-label={children}
|
64
|
+
{...props}
|
65
|
+
>
|
66
|
+
<Box __css={style.icon}>
|
67
|
+
{icon}
|
68
|
+
</Box>
|
69
|
+
<MotionBox
|
70
|
+
animate={isTextVisible ? "show" : "hide"}
|
71
|
+
initial="show"
|
72
|
+
variants={{
|
73
|
+
show: {
|
74
|
+
opacity: 1,
|
75
|
+
width: "auto",
|
76
|
+
visibility: "visible",
|
77
|
+
},
|
78
|
+
hide: {
|
79
|
+
opacity: 0,
|
80
|
+
width: 0,
|
81
|
+
visibility: "hidden",
|
82
|
+
},
|
83
|
+
}}
|
84
|
+
__css={style.text}
|
85
|
+
>
|
86
|
+
{children}
|
87
|
+
</MotionBox>
|
88
|
+
</MotionBox>
|
89
|
+
);
|
90
|
+
};
|
91
|
+
|
92
|
+
type ScrollDirection = "up" | "down" | null;
|
93
|
+
const useScrollDirection = () => {
|
94
|
+
const [scrollDirection, setScrollDirection] =
|
95
|
+
React.useState<ScrollDirection>(null);
|
96
|
+
const lastScrollPosition = React.useRef(window.scrollY);
|
97
|
+
React.useEffect(() => {
|
98
|
+
const onScroll = () => {
|
99
|
+
const delta = window.scrollY - lastScrollPosition.current;
|
100
|
+
if (delta === 0) {
|
101
|
+
return;
|
102
|
+
}
|
103
|
+
|
104
|
+
lastScrollPosition.current = window.scrollY;
|
105
|
+
setScrollDirection(delta > 0 ? "down" : "up");
|
106
|
+
};
|
107
|
+
window.addEventListener("scroll", onScroll);
|
108
|
+
return () => {
|
109
|
+
window.removeEventListener("scroll", onScroll);
|
110
|
+
};
|
111
|
+
}, [scrollDirection]);
|
112
|
+
return scrollDirection;
|
113
|
+
};
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import {
|
2
|
+
As,
|
3
|
+
IconButton as ChakraIconButton,
|
4
|
+
IconButtonProps as ChakraIconButtonProps,
|
5
|
+
forwardRef,
|
6
|
+
} from "@chakra-ui/react";
|
7
|
+
import React from "react";
|
8
|
+
import { ColorSpinner } from "..";
|
9
|
+
|
10
|
+
export type IconButtonProps = Omit<ChakraIconButtonProps, "variant"> & {
|
11
|
+
variant:
|
12
|
+
| "control"
|
13
|
+
| "primary"
|
14
|
+
| "secondary"
|
15
|
+
| "tertiary"
|
16
|
+
| "additional"
|
17
|
+
| "ghost";
|
18
|
+
};
|
19
|
+
/**
|
20
|
+
* An icon-only button.
|
21
|
+
*
|
22
|
+
* Remember to specify the `aria-label` prop for screen readers.
|
23
|
+
*
|
24
|
+
* There are several icon button variants. You can specify which one you want with the `variant` prop. The available variants are:
|
25
|
+
*
|
26
|
+
* - `control`: This button is used for ticket controls only.
|
27
|
+
* - `primary`: This is our main button. It's used for the main actions in a view, like a call to action. There should only be a single primary button in each view.
|
28
|
+
* - `secondary`: Used for secondary actions in a view, and when you need to make several actions available at the same time.
|
29
|
+
* - `tertiary`: Used for non-essential actions, as well as in combination with the primary button.
|
30
|
+
* - `additional`: Used for additional choices, like a less important tertiary action.
|
31
|
+
* - `ghost`: Used inside other interactive elements, like date pickers and input fields.
|
32
|
+
*
|
33
|
+
* ```tsx
|
34
|
+
* <IconButton
|
35
|
+
* aria-label="Buy trip"
|
36
|
+
* icon={<ShoppingCartIcon />}
|
37
|
+
* variant="primary"
|
38
|
+
* onClick={confirmOrder}
|
39
|
+
* />
|
40
|
+
* ```
|
41
|
+
*
|
42
|
+
* There are also different sizes. You can specify which one you want with the `size` prop. The available sizes are "lg", "md", "sm" and "xs".
|
43
|
+
*
|
44
|
+
* ```tsx
|
45
|
+
* <IconButton
|
46
|
+
* aria-label="Cancel trip"
|
47
|
+
* icon={<CancelIcon />}
|
48
|
+
* variant="ghost"
|
49
|
+
* size="sm"
|
50
|
+
* onClick={cancelOrder}
|
51
|
+
* />
|
52
|
+
* ```
|
53
|
+
*/
|
54
|
+
export const IconButton = forwardRef<IconButtonProps, As>(
|
55
|
+
({ ...props }, ref) => (
|
56
|
+
<ChakraIconButton
|
57
|
+
title={props["aria-label"]}
|
58
|
+
{...props}
|
59
|
+
spinner={<ColorSpinner m={1} />}
|
60
|
+
ref={ref}
|
61
|
+
/>
|
62
|
+
)
|
63
|
+
);
|
@@ -0,0 +1,59 @@
|
|
1
|
+
import { As, forwardRef, useStyleConfig } from "@chakra-ui/react";
|
2
|
+
import React from "react";
|
3
|
+
import { Box, BoxProps } from "../layout";
|
4
|
+
|
5
|
+
export type CardProps = Exclude<BoxProps, "size"> & {
|
6
|
+
size?: "sm" | "lg";
|
7
|
+
children: React.ReactNode;
|
8
|
+
colorScheme:
|
9
|
+
| "white"
|
10
|
+
| "grey"
|
11
|
+
| "blue"
|
12
|
+
| "green"
|
13
|
+
| "teal"
|
14
|
+
| "yellow"
|
15
|
+
| "orange";
|
16
|
+
};
|
17
|
+
/**
|
18
|
+
* Renders a card.
|
19
|
+
*
|
20
|
+
* The most basic version looks like this:
|
21
|
+
*
|
22
|
+
* ```tsx
|
23
|
+
* <Card>
|
24
|
+
* Content
|
25
|
+
* </Card>
|
26
|
+
* ```
|
27
|
+
*
|
28
|
+
* There are lots of color schemes available. You can also set the size as either `sm` or `lg`. The default is `lg`.
|
29
|
+
*
|
30
|
+
* ```tsx
|
31
|
+
* <Card colorScheme="orange" size="sm">
|
32
|
+
* A smaller card
|
33
|
+
* </Card>
|
34
|
+
* ```
|
35
|
+
*
|
36
|
+
* Cards are not interactive by default. If you specify the `as` prop to be a link or a button, you can make them work as links or buttons respectively. This will also give it a drop shadow.
|
37
|
+
*
|
38
|
+
* ```tsx
|
39
|
+
* <Card colorScheme="blue" as="button" onClick={handleClick}>
|
40
|
+
* Click for profit
|
41
|
+
* </Card>
|
42
|
+
* <Card colorScheme="green" as="a" href="https://vy.no">
|
43
|
+
* Go to start
|
44
|
+
* </Card>
|
45
|
+
* ```
|
46
|
+
*/
|
47
|
+
export const Card = forwardRef<CardProps, As>(
|
48
|
+
({ size = "lg", colorScheme = "white", children, ...props }, ref) => {
|
49
|
+
const styles = useStyleConfig("Card", {
|
50
|
+
colorScheme,
|
51
|
+
size,
|
52
|
+
});
|
53
|
+
return (
|
54
|
+
<Box __css={styles} {...props} ref={ref}>
|
55
|
+
{children}
|
56
|
+
</Box>
|
57
|
+
);
|
58
|
+
}
|
59
|
+
);
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from "./Card";
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import { Box } from "@chakra-ui/react";
|
2
|
+
import { createCalendar, DateValue } from "@internationalized/date";
|
3
|
+
import { useCalendarState } from "@react-stately/calendar";
|
4
|
+
import React from "react";
|
5
|
+
import {
|
6
|
+
CalendarProps as ReactAriaCalendarProps,
|
7
|
+
useCalendar,
|
8
|
+
} from "react-aria";
|
9
|
+
import { CalendarGrid } from "./CalendarGrid";
|
10
|
+
import { CalendarHeader } from "./CalendarHeader";
|
11
|
+
import { useCurrentLocale } from "./utils";
|
12
|
+
|
13
|
+
type CalendarProps = ReactAriaCalendarProps<DateValue> & {
|
14
|
+
showYearNavigation?: boolean;
|
15
|
+
};
|
16
|
+
export function Calendar({ showYearNavigation, ...props }: CalendarProps) {
|
17
|
+
const locale = useCurrentLocale();
|
18
|
+
const state = useCalendarState({
|
19
|
+
...props,
|
20
|
+
locale,
|
21
|
+
createCalendar,
|
22
|
+
});
|
23
|
+
|
24
|
+
const { calendarProps } = useCalendar(props, state);
|
25
|
+
|
26
|
+
return (
|
27
|
+
<Box {...calendarProps}>
|
28
|
+
<CalendarHeader state={state} showYearNavigation={showYearNavigation} />
|
29
|
+
<CalendarGrid state={state} />
|
30
|
+
</Box>
|
31
|
+
);
|
32
|
+
}
|