@vygruppen/spor-react 1.3.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +12 -10
- package/CHANGELOG.md +25 -0
- 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,74 @@
|
|
1
|
+
import { Box, useMultiStyleConfig } from "@chakra-ui/react";
|
2
|
+
import {
|
3
|
+
CalendarDate,
|
4
|
+
DateValue,
|
5
|
+
isSameMonth,
|
6
|
+
isToday,
|
7
|
+
} from "@internationalized/date";
|
8
|
+
import React, { useRef } from "react";
|
9
|
+
import { useCalendarCell } from "react-aria";
|
10
|
+
import { CalendarState, RangeCalendarState } from "react-stately";
|
11
|
+
|
12
|
+
type CalendarCellProps = {
|
13
|
+
state: CalendarState | RangeCalendarState;
|
14
|
+
date: CalendarDate;
|
15
|
+
currentMonth: DateValue;
|
16
|
+
};
|
17
|
+
export function CalendarCell({ state, date, currentMonth }: CalendarCellProps) {
|
18
|
+
const ref = useRef(null);
|
19
|
+
const { cellProps, buttonProps, isSelected, isDisabled, isUnavailable } =
|
20
|
+
useCalendarCell({ date }, state, ref);
|
21
|
+
|
22
|
+
const isOutsideMonth = !isSameMonth(currentMonth, date);
|
23
|
+
const styles = useMultiStyleConfig("Datepicker", {});
|
24
|
+
|
25
|
+
const stateProps: Record<string, any> = {};
|
26
|
+
if (isSelected) {
|
27
|
+
stateProps["data-selected"] = true;
|
28
|
+
}
|
29
|
+
if (isDisabled || isUnavailable) {
|
30
|
+
stateProps["data-disabled"] = true;
|
31
|
+
}
|
32
|
+
if (isToday(date, "Europe/Oslo")) {
|
33
|
+
stateProps["data-today"] = true;
|
34
|
+
}
|
35
|
+
if (isOutsideMonth) {
|
36
|
+
stateProps["data-unavailable"] = true;
|
37
|
+
}
|
38
|
+
|
39
|
+
return (
|
40
|
+
<Box
|
41
|
+
as="td"
|
42
|
+
{...cellProps}
|
43
|
+
textAlign="center"
|
44
|
+
sx={{
|
45
|
+
'&[aria-selected="true"] + [aria-selected="true"] > button': {
|
46
|
+
"&::before": {
|
47
|
+
content: '""',
|
48
|
+
display: "block",
|
49
|
+
width: "100%",
|
50
|
+
height: "100%",
|
51
|
+
backgroundColor: "darkTeal",
|
52
|
+
position: "absolute",
|
53
|
+
left: "-50%",
|
54
|
+
top: 0,
|
55
|
+
bottom: 0,
|
56
|
+
zIndex: -1,
|
57
|
+
},
|
58
|
+
},
|
59
|
+
}}
|
60
|
+
>
|
61
|
+
<Box
|
62
|
+
as="button"
|
63
|
+
type="button"
|
64
|
+
{...buttonProps}
|
65
|
+
{...stateProps}
|
66
|
+
ref={ref}
|
67
|
+
sx={styles.dateCell}
|
68
|
+
width="100%"
|
69
|
+
>
|
70
|
+
{date.day}
|
71
|
+
</Box>
|
72
|
+
</Box>
|
73
|
+
);
|
74
|
+
}
|
@@ -0,0 +1,76 @@
|
|
1
|
+
import { endOfMonth, getWeeksInMonth } from "@internationalized/date";
|
2
|
+
import React from "react";
|
3
|
+
import { AriaCalendarGridProps, useCalendarGrid } from "react-aria";
|
4
|
+
import { CalendarState, RangeCalendarState } from "react-stately";
|
5
|
+
import { Language, useTranslation } from "../i18n";
|
6
|
+
import { Text } from "../typography";
|
7
|
+
import { CalendarCell } from "./CalendarCell";
|
8
|
+
import { useCurrentLocale } from "./utils";
|
9
|
+
|
10
|
+
const weekDays: Record<Language, string[]> = {
|
11
|
+
nb: ["Ma", "Ti", "On", "To", "Fr", "Lø", "Sø"],
|
12
|
+
nn: ["Ma", "Ti", "On", "To", "Fr", "Lø", "Sø"],
|
13
|
+
sv: ["Må", "Ti", "On", "To", "Fr", "Lö", "Sö"],
|
14
|
+
en: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
|
15
|
+
};
|
16
|
+
|
17
|
+
type CalendarGridProps = AriaCalendarGridProps & {
|
18
|
+
state: CalendarState | RangeCalendarState;
|
19
|
+
offset?: { months?: number };
|
20
|
+
};
|
21
|
+
export function CalendarGrid({ state, offset = {} }: CalendarGridProps) {
|
22
|
+
const { language } = useTranslation();
|
23
|
+
const locale = useCurrentLocale();
|
24
|
+
const startDate = state.visibleRange.start.add(offset);
|
25
|
+
const endDate = endOfMonth(startDate);
|
26
|
+
const { gridProps, headerProps } = useCalendarGrid(
|
27
|
+
{
|
28
|
+
startDate,
|
29
|
+
endDate,
|
30
|
+
},
|
31
|
+
state
|
32
|
+
);
|
33
|
+
|
34
|
+
// Get the number of weeks in the month so we can render the proper number of rows.
|
35
|
+
const weeksInMonth = getWeeksInMonth(state.visibleRange.start, locale);
|
36
|
+
const weeksInMonthRange = new Array(weeksInMonth).fill(0).map((_, i) => i);
|
37
|
+
|
38
|
+
return (
|
39
|
+
<table {...gridProps}>
|
40
|
+
<thead {...headerProps}>
|
41
|
+
<tr>
|
42
|
+
{weekDays[language].map((day, index) => (
|
43
|
+
<Text
|
44
|
+
as="th"
|
45
|
+
key={index}
|
46
|
+
color={index < 5 ? "darkGrey" : "greenHaze"}
|
47
|
+
variant="sm"
|
48
|
+
>
|
49
|
+
{day}
|
50
|
+
</Text>
|
51
|
+
))}
|
52
|
+
</tr>
|
53
|
+
</thead>
|
54
|
+
<tbody>
|
55
|
+
{weeksInMonthRange.map((weekIndex) => (
|
56
|
+
<tr key={weekIndex}>
|
57
|
+
{state
|
58
|
+
.getDatesInWeek(weekIndex, startDate)
|
59
|
+
.map((date, dayIndex) =>
|
60
|
+
date ? (
|
61
|
+
<CalendarCell
|
62
|
+
key={dayIndex}
|
63
|
+
state={state}
|
64
|
+
date={date}
|
65
|
+
currentMonth={startDate}
|
66
|
+
/>
|
67
|
+
) : (
|
68
|
+
<td key={dayIndex} />
|
69
|
+
)
|
70
|
+
)}
|
71
|
+
</tr>
|
72
|
+
))}
|
73
|
+
</tbody>
|
74
|
+
</table>
|
75
|
+
);
|
76
|
+
}
|
@@ -0,0 +1,153 @@
|
|
1
|
+
import { Flex } from "@chakra-ui/react";
|
2
|
+
import { getLocalTimeZone } from "@internationalized/date";
|
3
|
+
import {
|
4
|
+
ArrowLeftOutline24Icon,
|
5
|
+
ArrowRightOutline24Icon,
|
6
|
+
} from "@vygruppen/spor-icon-react";
|
7
|
+
import React from "react";
|
8
|
+
import { CalendarState, RangeCalendarState } from "react-stately";
|
9
|
+
import { createTexts, useTranslation } from "../i18n";
|
10
|
+
import { Heading } from "../typography";
|
11
|
+
import { CalendarNavigationButton } from "./CalendarNavigationButton";
|
12
|
+
import { useCurrentLocale } from "./utils";
|
13
|
+
|
14
|
+
type CalendarHeaderProps = {
|
15
|
+
state: CalendarState | RangeCalendarState;
|
16
|
+
title?: string;
|
17
|
+
showYearNavigation?: boolean;
|
18
|
+
};
|
19
|
+
export function CalendarHeader({
|
20
|
+
state,
|
21
|
+
showYearNavigation = false,
|
22
|
+
title,
|
23
|
+
}: CalendarHeaderProps) {
|
24
|
+
const locale = useCurrentLocale();
|
25
|
+
const monthFormatter = Intl.DateTimeFormat(locale, {
|
26
|
+
month: "long",
|
27
|
+
});
|
28
|
+
const jsDate = state.focusedDate.toDate(getLocalTimeZone());
|
29
|
+
|
30
|
+
const monthTitle = monthFormatter.format(jsDate);
|
31
|
+
const monthAndYearTitle = `${monthTitle} ${state.focusedDate.year}`;
|
32
|
+
|
33
|
+
const isPreviousYearDisabled = state.isInvalid(
|
34
|
+
state.visibleRange.start.subtract({ years: 1 })
|
35
|
+
);
|
36
|
+
const isNextYearDisabled = state.isInvalid(
|
37
|
+
state.visibleRange.start.add({ years: 1 })
|
38
|
+
);
|
39
|
+
const areAllOtherYearsDisabled = isPreviousYearDisabled && isNextYearDisabled;
|
40
|
+
const isYearPickerVisible = showYearNavigation && !areAllOtherYearsDisabled;
|
41
|
+
|
42
|
+
return (
|
43
|
+
<Flex alignItems="center" paddingBottom="4" justifyContent="space-between">
|
44
|
+
<CalendarNavigator
|
45
|
+
title={
|
46
|
+
title ? title : isYearPickerVisible ? monthTitle : monthAndYearTitle
|
47
|
+
}
|
48
|
+
unit="month"
|
49
|
+
onPrevious={() =>
|
50
|
+
state.setFocusedDate(state.focusedDate.subtract({ months: 1 }))
|
51
|
+
}
|
52
|
+
onNext={() =>
|
53
|
+
state.setFocusedDate(state.focusedDate.add({ months: 1 }))
|
54
|
+
}
|
55
|
+
isNextDisabled={!state.isPreviousVisibleRangeInvalid}
|
56
|
+
isPreviousDisabled={!state.isNextVisibleRangeInvalid}
|
57
|
+
/>
|
58
|
+
{isYearPickerVisible && (
|
59
|
+
<CalendarNavigator
|
60
|
+
title={jsDate.getFullYear().toString()}
|
61
|
+
unit="year"
|
62
|
+
onPrevious={() =>
|
63
|
+
state.setFocusedDate(state.focusedDate.subtract({ years: 1 }))
|
64
|
+
}
|
65
|
+
onNext={() =>
|
66
|
+
state.setFocusedDate(state.focusedDate.add({ years: 1 }))
|
67
|
+
}
|
68
|
+
isPreviousDisabled={isPreviousYearDisabled}
|
69
|
+
isNextDisabled={isNextYearDisabled}
|
70
|
+
/>
|
71
|
+
)}
|
72
|
+
</Flex>
|
73
|
+
);
|
74
|
+
}
|
75
|
+
|
76
|
+
const capitalize = (str: string = "") =>
|
77
|
+
str.charAt(0).toUpperCase() + str.slice(1);
|
78
|
+
|
79
|
+
type CalendarNavigatorProps = {
|
80
|
+
/** The unit of time you want to navigate with */
|
81
|
+
unit: "month" | "year";
|
82
|
+
/** The text in the middle */
|
83
|
+
title: string;
|
84
|
+
/** Callback for when you click back */
|
85
|
+
onPrevious: () => void;
|
86
|
+
/** Callback for when you click forward */
|
87
|
+
onNext: () => void;
|
88
|
+
isNextDisabled: boolean;
|
89
|
+
isPreviousDisabled: boolean;
|
90
|
+
};
|
91
|
+
export const CalendarNavigator = ({
|
92
|
+
title,
|
93
|
+
unit,
|
94
|
+
onPrevious,
|
95
|
+
isPreviousDisabled,
|
96
|
+
onNext,
|
97
|
+
isNextDisabled,
|
98
|
+
}: CalendarNavigatorProps) => {
|
99
|
+
const { t } = useTranslation();
|
100
|
+
return (
|
101
|
+
<Flex alignItems="center" flexGrow={1}>
|
102
|
+
<CalendarNavigationButton
|
103
|
+
onPress={onPrevious}
|
104
|
+
isDisabled={isPreviousDisabled}
|
105
|
+
icon={<ArrowLeftOutline24Icon />}
|
106
|
+
aria-label={`${t(texts.previous)} ${t(texts[unit])}`}
|
107
|
+
/>
|
108
|
+
<Heading
|
109
|
+
as="div"
|
110
|
+
role="heading"
|
111
|
+
variant="sm"
|
112
|
+
fontWeight="bold"
|
113
|
+
flex="1"
|
114
|
+
textAlign="center"
|
115
|
+
>
|
116
|
+
{capitalize(title)}
|
117
|
+
</Heading>
|
118
|
+
<CalendarNavigationButton
|
119
|
+
onPress={onNext}
|
120
|
+
isDisabled={isNextDisabled}
|
121
|
+
icon={<ArrowRightOutline24Icon />}
|
122
|
+
aria-label={`${t(texts.next)} ${t(texts[unit])}`}
|
123
|
+
/>
|
124
|
+
</Flex>
|
125
|
+
);
|
126
|
+
};
|
127
|
+
|
128
|
+
const texts = createTexts({
|
129
|
+
previous: {
|
130
|
+
nb: "Forrige",
|
131
|
+
nn: "Forrige",
|
132
|
+
sv: "Föregående",
|
133
|
+
en: "Previous",
|
134
|
+
},
|
135
|
+
next: {
|
136
|
+
nb: "Neste",
|
137
|
+
nn: "Neste",
|
138
|
+
sv: "Nästa",
|
139
|
+
en: "Next",
|
140
|
+
},
|
141
|
+
month: {
|
142
|
+
nb: "måned",
|
143
|
+
nn: "månad",
|
144
|
+
sv: "månad",
|
145
|
+
en: "month",
|
146
|
+
},
|
147
|
+
year: {
|
148
|
+
nb: "år",
|
149
|
+
nn: "år",
|
150
|
+
sv: "år",
|
151
|
+
en: "year",
|
152
|
+
},
|
153
|
+
});
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import React, { useRef } from "react";
|
2
|
+
import { AriaButtonProps, useButton } from "react-aria";
|
3
|
+
import { IconButton } from "..";
|
4
|
+
|
5
|
+
type CalendarButtonProps = AriaButtonProps<"button"> & {
|
6
|
+
icon: React.ReactElement;
|
7
|
+
"aria-label": string;
|
8
|
+
};
|
9
|
+
export function CalendarNavigationButton({
|
10
|
+
icon,
|
11
|
+
"aria-label": ariaLabel,
|
12
|
+
...rest
|
13
|
+
}: CalendarButtonProps) {
|
14
|
+
const ref = useRef(null);
|
15
|
+
const { buttonProps } = useButton(rest, ref);
|
16
|
+
return (
|
17
|
+
<IconButton
|
18
|
+
{...buttonProps}
|
19
|
+
ref={ref}
|
20
|
+
icon={icon}
|
21
|
+
aria-label={ariaLabel}
|
22
|
+
size="sm"
|
23
|
+
variant="ghost"
|
24
|
+
/>
|
25
|
+
);
|
26
|
+
}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import { Box, PopoverAnchor, useMultiStyleConfig } from "@chakra-ui/react";
|
2
|
+
import { CalendarOutline24Icon } from "@vygruppen/spor-icon-react";
|
3
|
+
import React, { useRef } from "react";
|
4
|
+
import { AriaButtonProps, useButton } from "react-aria";
|
5
|
+
import { createTexts, useTranslation } from "..";
|
6
|
+
|
7
|
+
type CalendarTriggerButtonProps = AriaButtonProps<"button">;
|
8
|
+
export const CalendarTriggerButton = (props: CalendarTriggerButtonProps) => {
|
9
|
+
const { t } = useTranslation();
|
10
|
+
const styles = useMultiStyleConfig("Datepicker", {});
|
11
|
+
const ref = useRef(null);
|
12
|
+
const { buttonProps } = useButton(props, ref);
|
13
|
+
return (
|
14
|
+
<PopoverAnchor>
|
15
|
+
<Box
|
16
|
+
ref={ref}
|
17
|
+
as="button"
|
18
|
+
type="button"
|
19
|
+
aria-label={t(texts.openCalendar)}
|
20
|
+
sx={styles.calendarTriggerButton}
|
21
|
+
{...(buttonProps as any)}
|
22
|
+
>
|
23
|
+
<CalendarOutline24Icon />
|
24
|
+
</Box>
|
25
|
+
</PopoverAnchor>
|
26
|
+
);
|
27
|
+
};
|
28
|
+
|
29
|
+
const texts = createTexts({
|
30
|
+
openCalendar: {
|
31
|
+
nb: "Åpne kalender",
|
32
|
+
nn: "Åpne kalendar",
|
33
|
+
sv: "Öppna kalender",
|
34
|
+
en: "Open calendar",
|
35
|
+
},
|
36
|
+
});
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import { Box, Flex, FormLabel, useMultiStyleConfig } from "@chakra-ui/react";
|
2
|
+
import { DateValue, GregorianCalendar } from "@internationalized/date";
|
3
|
+
import { useDateFieldState } from "@react-stately/datepicker";
|
4
|
+
import { DOMAttributes, FocusableElement } from "@react-types/shared";
|
5
|
+
import React, { useRef } from "react";
|
6
|
+
import { AriaDateFieldProps, useDateField } from "react-aria";
|
7
|
+
import { DateTimeSegment } from "./DateTimeSegment";
|
8
|
+
import { useCurrentLocale } from "./utils";
|
9
|
+
|
10
|
+
function createCalendar(identifier: string) {
|
11
|
+
switch (identifier) {
|
12
|
+
case "gregory":
|
13
|
+
return new GregorianCalendar();
|
14
|
+
default:
|
15
|
+
throw new Error(`Unsupported calendar ${identifier}`);
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
type DateFieldProps = AriaDateFieldProps<DateValue> & {
|
20
|
+
label?: React.ReactNode;
|
21
|
+
labelProps?: DOMAttributes<FocusableElement>;
|
22
|
+
name?: string;
|
23
|
+
};
|
24
|
+
export function DateField(props: DateFieldProps) {
|
25
|
+
const locale = useCurrentLocale();
|
26
|
+
const styles = useMultiStyleConfig("Datepicker", {});
|
27
|
+
const state = useDateFieldState({
|
28
|
+
...props,
|
29
|
+
locale,
|
30
|
+
createCalendar,
|
31
|
+
});
|
32
|
+
|
33
|
+
const ref = useRef(null);
|
34
|
+
const { fieldProps, labelProps } = useDateField(props, state, ref);
|
35
|
+
|
36
|
+
return (
|
37
|
+
<Box minWidth="6rem">
|
38
|
+
{props.label && (
|
39
|
+
<FormLabel {...props.labelProps} {...labelProps} sx={styles.inputLabel}>
|
40
|
+
{props.label}
|
41
|
+
</FormLabel>
|
42
|
+
)}
|
43
|
+
<Flex {...fieldProps} ref={ref}>
|
44
|
+
{state.segments.map((segment, i) => (
|
45
|
+
<DateTimeSegment key={i} segment={segment} state={state} />
|
46
|
+
))}
|
47
|
+
</Flex>
|
48
|
+
<input type="hidden" value={state.value?.toString()} name={props.name} />
|
49
|
+
</Box>
|
50
|
+
);
|
51
|
+
}
|
@@ -0,0 +1,153 @@
|
|
1
|
+
import {
|
2
|
+
Box,
|
3
|
+
BoxProps,
|
4
|
+
InputGroup,
|
5
|
+
Popover,
|
6
|
+
PopoverAnchor,
|
7
|
+
PopoverArrow,
|
8
|
+
PopoverBody,
|
9
|
+
PopoverContent,
|
10
|
+
Portal,
|
11
|
+
ResponsiveValue,
|
12
|
+
useBreakpointValue,
|
13
|
+
useFormControlContext,
|
14
|
+
} from "@chakra-ui/react";
|
15
|
+
import { DateValue } from "@internationalized/date";
|
16
|
+
import { useDatePickerState } from "@react-stately/datepicker";
|
17
|
+
import { CalendarOutline24Icon } from "@vygruppen/spor-icon-react";
|
18
|
+
import React, { useRef } from "react";
|
19
|
+
import { AriaDatePickerProps, I18nProvider, useDatePicker } from "react-aria";
|
20
|
+
import { FormErrorMessage } from "..";
|
21
|
+
import { Calendar } from "./Calendar";
|
22
|
+
import { CalendarTriggerButton } from "./CalendarTriggerButton";
|
23
|
+
import { DateField } from "./DateField";
|
24
|
+
import { StyledField } from "./StyledField";
|
25
|
+
import { useCurrentLocale } from "./utils";
|
26
|
+
|
27
|
+
type DatePickerProps = AriaDatePickerProps<DateValue> &
|
28
|
+
Pick<BoxProps, "minHeight"> & {
|
29
|
+
variant: ResponsiveValue<"simple" | "with-trigger">;
|
30
|
+
name?: string;
|
31
|
+
showYearNavigation?: boolean;
|
32
|
+
};
|
33
|
+
/**
|
34
|
+
* A date picker component.
|
35
|
+
*
|
36
|
+
* There are two versions of this component – a simple one, and one with a trigger button for showing the calendar. Use whatever fits your design.
|
37
|
+
*
|
38
|
+
* ```tsx
|
39
|
+
* <DatePicker label="Dato" variant="simple" />
|
40
|
+
* ```
|
41
|
+
*/
|
42
|
+
export function DatePicker({
|
43
|
+
variant,
|
44
|
+
errorMessage,
|
45
|
+
minHeight,
|
46
|
+
showYearNavigation,
|
47
|
+
...props
|
48
|
+
}: DatePickerProps) {
|
49
|
+
const formControlProps = useFormControlContext();
|
50
|
+
const state = useDatePickerState({
|
51
|
+
...props,
|
52
|
+
shouldCloseOnSelect: true,
|
53
|
+
errorMessage,
|
54
|
+
isRequired: props.isRequired ?? formControlProps?.isRequired,
|
55
|
+
validationState: formControlProps?.isInvalid ? "invalid" : "valid",
|
56
|
+
});
|
57
|
+
const ref = useRef(null);
|
58
|
+
const {
|
59
|
+
groupProps,
|
60
|
+
labelProps,
|
61
|
+
fieldProps,
|
62
|
+
buttonProps,
|
63
|
+
dialogProps,
|
64
|
+
calendarProps,
|
65
|
+
errorMessageProps,
|
66
|
+
} = useDatePicker(props, state, ref);
|
67
|
+
|
68
|
+
const responsiveVariant =
|
69
|
+
useBreakpointValue(typeof variant === "string" ? [variant] : variant) ??
|
70
|
+
"simple";
|
71
|
+
|
72
|
+
const locale = useCurrentLocale();
|
73
|
+
|
74
|
+
const handleEnterClick = (e: React.KeyboardEvent) => {
|
75
|
+
if (responsiveVariant === "simple" && e.key === "Enter" && !state.isOpen) {
|
76
|
+
// Don't submit the form
|
77
|
+
e.stopPropagation();
|
78
|
+
state.setOpen(true);
|
79
|
+
}
|
80
|
+
};
|
81
|
+
|
82
|
+
const onFieldClick = () => {
|
83
|
+
if (!hasTrigger) {
|
84
|
+
state.setOpen(true);
|
85
|
+
}
|
86
|
+
};
|
87
|
+
|
88
|
+
const hasTrigger = responsiveVariant === "with-trigger";
|
89
|
+
|
90
|
+
return (
|
91
|
+
<I18nProvider locale={locale}>
|
92
|
+
<Box position="relative" display="inline-flex" flexDirection="column">
|
93
|
+
<Popover
|
94
|
+
{...dialogProps}
|
95
|
+
isOpen={state.isOpen}
|
96
|
+
onClose={() => state.setOpen(false)}
|
97
|
+
onOpen={() => state.setOpen(true)}
|
98
|
+
closeOnBlur
|
99
|
+
closeOnEsc
|
100
|
+
returnFocusOnClose
|
101
|
+
>
|
102
|
+
<InputGroup
|
103
|
+
{...groupProps}
|
104
|
+
ref={ref}
|
105
|
+
width="auto"
|
106
|
+
display="inline-flex"
|
107
|
+
>
|
108
|
+
<PopoverAnchor>
|
109
|
+
<StyledField
|
110
|
+
variant={responsiveVariant}
|
111
|
+
onClick={onFieldClick}
|
112
|
+
onKeyPress={handleEnterClick}
|
113
|
+
paddingX={3}
|
114
|
+
minHeight={minHeight}
|
115
|
+
>
|
116
|
+
{!hasTrigger && (
|
117
|
+
<CalendarOutline24Icon marginRight={2} alignSelf="center" />
|
118
|
+
)}
|
119
|
+
<DateField
|
120
|
+
label={props.label}
|
121
|
+
labelProps={labelProps}
|
122
|
+
name={props.name}
|
123
|
+
{...fieldProps}
|
124
|
+
/>
|
125
|
+
</StyledField>
|
126
|
+
</PopoverAnchor>
|
127
|
+
{hasTrigger && <CalendarTriggerButton {...buttonProps} />}
|
128
|
+
</InputGroup>
|
129
|
+
<FormErrorMessage {...errorMessageProps}>
|
130
|
+
{errorMessage}
|
131
|
+
</FormErrorMessage>
|
132
|
+
{state.isOpen && !props.isDisabled && (
|
133
|
+
<Portal>
|
134
|
+
<PopoverContent
|
135
|
+
backgroundColor="white"
|
136
|
+
color="darkGrey"
|
137
|
+
boxShadow="md"
|
138
|
+
>
|
139
|
+
<PopoverArrow backgroundColor="white" />
|
140
|
+
<PopoverBody>
|
141
|
+
<Calendar
|
142
|
+
{...calendarProps}
|
143
|
+
showYearNavigation={showYearNavigation}
|
144
|
+
/>
|
145
|
+
</PopoverBody>
|
146
|
+
</PopoverContent>
|
147
|
+
</Portal>
|
148
|
+
)}
|
149
|
+
</Popover>
|
150
|
+
</Box>
|
151
|
+
</I18nProvider>
|
152
|
+
);
|
153
|
+
}
|