@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.
Files changed (196) hide show
  1. package/.turbo/turbo-build.log +12 -10
  2. package/CHANGELOG.md +31 -0
  3. package/dist/index.d.ts +6720 -27
  4. package/dist/index.js +14143 -140
  5. package/dist/index.mjs +13822 -27
  6. package/package.json +19 -31
  7. package/src/accordion/Accordion.test.tsx +20 -0
  8. package/src/accordion/Accordion.tsx +62 -0
  9. package/src/accordion/AccordionContext.tsx +27 -0
  10. package/src/accordion/Expandable.tsx +157 -0
  11. package/src/accordion/index.tsx +2 -0
  12. package/src/alert/AlertIcon.tsx +75 -0
  13. package/src/alert/BaseAlert.test.tsx +37 -0
  14. package/src/alert/BaseAlert.tsx +21 -0
  15. package/src/alert/ClosableAlert.test.tsx +37 -0
  16. package/src/alert/ClosableAlert.tsx +75 -0
  17. package/src/alert/ExpandableAlert.test.tsx +84 -0
  18. package/src/alert/ExpandableAlert.tsx +84 -0
  19. package/src/alert/StaticAlert.tsx +25 -0
  20. package/src/alert/index.tsx +3 -0
  21. package/src/button/Button.test.tsx +23 -0
  22. package/src/button/Button.tsx +162 -0
  23. package/src/button/ButtonGroup.tsx +43 -0
  24. package/src/button/CloseButton.tsx +63 -0
  25. package/src/button/FloatingActionButton.tsx +113 -0
  26. package/src/button/IconButton.tsx +63 -0
  27. package/src/button/index.tsx +5 -0
  28. package/src/card/Card.tsx +59 -0
  29. package/src/card/index.tsx +1 -0
  30. package/src/datepicker/Calendar.tsx +32 -0
  31. package/src/datepicker/CalendarCell.tsx +74 -0
  32. package/src/datepicker/CalendarGrid.tsx +76 -0
  33. package/src/datepicker/CalendarHeader.tsx +153 -0
  34. package/src/datepicker/CalendarNavigationButton.tsx +26 -0
  35. package/src/datepicker/CalendarTriggerButton.tsx +36 -0
  36. package/src/datepicker/DateField.tsx +51 -0
  37. package/src/datepicker/DatePicker.tsx +153 -0
  38. package/src/datepicker/DateRangePicker.tsx +171 -0
  39. package/src/datepicker/DateTimeSegment.tsx +56 -0
  40. package/src/datepicker/RangeCalendar.tsx +35 -0
  41. package/src/datepicker/StyledField.tsx +31 -0
  42. package/src/datepicker/TimeField.tsx +46 -0
  43. package/src/datepicker/TimePicker.test.tsx +74 -0
  44. package/src/datepicker/TimePicker.tsx +196 -0
  45. package/src/datepicker/index.tsx +4 -0
  46. package/src/datepicker/utils.ts +33 -0
  47. package/src/i18n/index.tsx +38 -0
  48. package/src/image/index.tsx +2 -0
  49. package/src/index.tsx +25 -26
  50. package/src/input/CardSelect.tsx +165 -0
  51. package/src/input/Checkbox.tsx +24 -0
  52. package/src/input/CheckboxGroup.tsx +43 -0
  53. package/src/input/ChoiceChip.tsx +102 -0
  54. package/src/input/Dialog.tsx +29 -0
  55. package/src/input/FormControl.tsx +11 -0
  56. package/src/input/FormErrorMessage.tsx +91 -0
  57. package/src/input/FormLabel.tsx +11 -0
  58. package/src/input/InfoSelect.tsx +209 -0
  59. package/src/input/Input.tsx +59 -0
  60. package/src/input/InputElement.tsx +45 -0
  61. package/src/input/ListBox.tsx +123 -0
  62. package/src/input/NativeSelect.tsx +38 -0
  63. package/src/input/PasswordInput.tsx +70 -0
  64. package/src/input/Popover.tsx +70 -0
  65. package/src/input/Radio.tsx +34 -0
  66. package/src/input/RadioGroup.tsx +47 -0
  67. package/src/input/SearchInput.tsx +89 -0
  68. package/src/input/Switch.tsx +40 -0
  69. package/src/input/Textarea.tsx +98 -0
  70. package/src/input/index.tsx +20 -0
  71. package/src/layout/Divider.tsx +26 -0
  72. package/src/layout/Stack.tsx +42 -0
  73. package/src/layout/index.tsx +28 -0
  74. package/src/linjetag/InfoTag.tsx +54 -0
  75. package/src/linjetag/LineIcon.tsx +44 -0
  76. package/src/linjetag/TravelTag.tsx +121 -0
  77. package/src/linjetag/icons.tsx +80 -0
  78. package/src/linjetag/index.tsx +3 -0
  79. package/src/linjetag/types.d.ts +24 -0
  80. package/src/link/TextLink.tsx +45 -0
  81. package/src/link/index.tsx +1 -0
  82. package/src/loader/ClientOnly.tsx +29 -0
  83. package/src/loader/ColorInlineLoader.tsx +27 -0
  84. package/src/loader/ColorSpinner.tsx +44 -0
  85. package/src/loader/ContentLoader.tsx +27 -0
  86. package/src/loader/DarkFullScreenLoader.tsx +23 -0
  87. package/src/loader/DarkInlineLoader.tsx +25 -0
  88. package/src/loader/DarkSpinner.tsx +43 -0
  89. package/src/loader/LightFullScreenLoader.tsx +23 -0
  90. package/src/loader/LightInlineLoader.tsx +25 -0
  91. package/src/loader/LightSpinner.tsx +41 -0
  92. package/src/loader/Lottie.tsx +10 -0
  93. package/src/loader/ProgressBar.tsx +128 -0
  94. package/src/loader/ProgressLoader.tsx +140 -0
  95. package/src/loader/Skeleton.tsx +16 -0
  96. package/src/loader/SkeletonCircle.tsx +13 -0
  97. package/src/loader/SkeletonText.tsx +10 -0
  98. package/src/loader/index.tsx +14 -0
  99. package/src/loader/useHydrated.tsx +34 -0
  100. package/src/loader/useRotatingLabel.tsx +22 -0
  101. package/src/logo/VyLogo.tsx +101 -0
  102. package/src/logo/index.tsx +1 -0
  103. package/src/media-controller/JumpButton.tsx +69 -0
  104. package/src/media-controller/PlayPauseButton.tsx +67 -0
  105. package/src/media-controller/SkipButton.tsx +66 -0
  106. package/src/media-controller/icons.tsx +80 -0
  107. package/src/media-controller/index.test.tsx +59 -0
  108. package/src/media-controller/index.tsx +3 -0
  109. package/src/modal/Drawer.tsx +122 -0
  110. package/src/modal/Modal.tsx +15 -0
  111. package/src/modal/ModalHeader.tsx +31 -0
  112. package/src/modal/SimpleDrawer.tsx +44 -0
  113. package/src/modal/index.tsx +4 -0
  114. package/src/popover/PopoverWizardBody.tsx +91 -0
  115. package/src/popover/SimplePopover.tsx +75 -0
  116. package/src/popover/WizardPopover.tsx +61 -0
  117. package/src/popover/index.tsx +23 -0
  118. package/src/provider/SporProvider.tsx +67 -0
  119. package/src/provider/index.tsx +1 -0
  120. package/src/stepper/Stepper.tsx +115 -0
  121. package/src/stepper/StepperContext.tsx +55 -0
  122. package/src/stepper/StepperStep.tsx +48 -0
  123. package/src/stepper/index.tsx +2 -0
  124. package/src/tab/Tabs.tsx +20 -0
  125. package/src/tab/index.tsx +9 -0
  126. package/src/table/Table.tsx +58 -0
  127. package/src/table/index.tsx +19 -0
  128. package/src/theme/components/accordion.ts +143 -0
  129. package/src/theme/components/alert.ts +59 -0
  130. package/src/theme/components/badge.ts +109 -0
  131. package/src/theme/components/button.ts +217 -0
  132. package/src/theme/components/card-select.ts +158 -0
  133. package/src/theme/components/card.ts +174 -0
  134. package/src/theme/components/checkbox.ts +90 -0
  135. package/src/theme/components/choice-chip.ts +79 -0
  136. package/src/theme/components/close-button.ts +56 -0
  137. package/src/theme/components/code.ts +17 -0
  138. package/src/theme/components/datepicker.ts +194 -0
  139. package/src/theme/components/drawer.ts +92 -0
  140. package/src/theme/components/fab.ts +111 -0
  141. package/src/theme/components/form-label.ts +17 -0
  142. package/src/theme/components/form.ts +27 -0
  143. package/src/theme/components/index.ts +34 -0
  144. package/src/theme/components/info-select.ts +91 -0
  145. package/src/theme/components/info-tag.ts +49 -0
  146. package/src/theme/components/input.ts +97 -0
  147. package/src/theme/components/line-icon.ts +121 -0
  148. package/src/theme/components/link.ts +155 -0
  149. package/src/theme/components/listbox.ts +52 -0
  150. package/src/theme/components/media-controller-button.ts +134 -0
  151. package/src/theme/components/modal.ts +93 -0
  152. package/src/theme/components/popover.ts +63 -0
  153. package/src/theme/components/radio.ts +64 -0
  154. package/src/theme/components/select.ts +52 -0
  155. package/src/theme/components/skeleton.ts +40 -0
  156. package/src/theme/components/stepper.ts +230 -0
  157. package/src/theme/components/switch.ts +227 -0
  158. package/src/theme/components/table.ts +163 -0
  159. package/src/theme/components/tabs.ts +282 -0
  160. package/src/theme/components/textarea.ts +14 -0
  161. package/src/theme/components/toast.ts +28 -0
  162. package/src/theme/components/travel-tag.ts +267 -0
  163. package/src/theme/font-faces.ts +66 -0
  164. package/src/theme/foundations/borders.ts +11 -0
  165. package/src/theme/foundations/breakpoints.ts +9 -0
  166. package/src/theme/foundations/colors.ts +10 -0
  167. package/src/theme/foundations/config.ts +5 -0
  168. package/src/theme/foundations/fontSizes.ts +29 -0
  169. package/src/theme/foundations/fontWeights.ts +5 -0
  170. package/src/theme/foundations/fonts.ts +7 -0
  171. package/src/theme/foundations/index.ts +14 -0
  172. package/src/theme/foundations/lineHeights.ts +5 -0
  173. package/src/theme/foundations/radii.ts +12 -0
  174. package/src/theme/foundations/shadows.ts +8 -0
  175. package/src/theme/foundations/sizes.ts +34 -0
  176. package/src/theme/foundations/spacing.ts +30 -0
  177. package/src/theme/foundations/textStyles.ts +60 -0
  178. package/src/theme/foundations/zIndices.ts +17 -0
  179. package/src/theme/index.ts +14 -0
  180. package/src/theme/utils/box-shadow-utils.ts +44 -0
  181. package/src/theme/utils/focus-utils.ts +16 -0
  182. package/src/toast/ActionToast.test.tsx +22 -0
  183. package/src/toast/ActionToast.tsx +28 -0
  184. package/src/toast/BaseToast.test.tsx +27 -0
  185. package/src/toast/BaseToast.tsx +75 -0
  186. package/src/toast/ClosableToast.test.tsx +17 -0
  187. package/src/toast/ClosableToast.tsx +40 -0
  188. package/src/toast/index.tsx +1 -0
  189. package/src/toast/useToast.tsx +99 -0
  190. package/src/typography/Badge.tsx +68 -0
  191. package/src/typography/Code.tsx +32 -0
  192. package/src/typography/Heading.tsx +32 -0
  193. package/src/typography/Text.tsx +26 -0
  194. package/src/typography/index.tsx +4 -0
  195. package/src/util/externals.tsx +23 -0
  196. package/src/util/index.tsx +1 -0
@@ -0,0 +1,209 @@
1
+ import {
2
+ Box,
3
+ chakra,
4
+ ResponsiveValue,
5
+ useMultiStyleConfig,
6
+ } from "@chakra-ui/react";
7
+ import {
8
+ DropdownDownFill24Icon,
9
+ DropdownUpFill24Icon,
10
+ } from "@vygruppen/spor-icon-react";
11
+ import React, { useRef } from "react";
12
+ import { HiddenSelect, useButton, useSelect } from "react-aria";
13
+ import { useSelectState } from "react-stately";
14
+ import { createTexts, useTranslation } from "../";
15
+ import { ListBox } from "./ListBox";
16
+ import { Popover } from "./Popover";
17
+
18
+ type InfoSelectProps = {
19
+ /**
20
+ * Either a render function accepting an item, and returning a <SelectItem />,
21
+ * or a list of <SelectItem />s.
22
+ *
23
+ * Render function example:
24
+ * ```tsx
25
+ * <Select items={items}>
26
+ * {(item) => (
27
+ * <SelectItem key={item.value} value={item.value}>
28
+ * {item.label}
29
+ * </SelectItem>
30
+ * )}
31
+ * </Select>
32
+ * ```
33
+ *
34
+ * For this to work, the members in `items` need either a `key`
35
+ * or an `id` property.
36
+ *
37
+ * List of <SelectItem />s example:
38
+ * ```tsx
39
+ * <Select label="Choose a color">
40
+ * <SelectItem>Green</SelectItem>
41
+ * <SelectItem>Blue</SelectItem>
42
+ * <SelectItem>Yellow</SelectItem>
43
+ * </Select>
44
+ * ```
45
+ **/
46
+ children: any;
47
+ /**
48
+ * The items to render
49
+ *
50
+ * If you have a dynamic list of items you want to display, you should use this prop instead of mapping them out. This is a performance optimization.
51
+ *
52
+ * You can render each item in a render function, passed in as `children`:
53
+ *
54
+ * ```tsx
55
+ * <Select items={items}>
56
+ * {(item) => <div>{item.someProp}</div>}
57
+ * </Select>
58
+ * ```
59
+ */
60
+ items: any[];
61
+ /** Callback for when something is selected */
62
+ onChange?: (value: string | number) => void;
63
+ value?: string | number;
64
+ defaultValue?: string | number;
65
+ /** Controlled open state
66
+ *
67
+ * Useful if you want to control the open state from outside the component.
68
+ */
69
+ isOpen?: boolean;
70
+ /** Callback for when the open state of the select box changes.
71
+ *
72
+ * Useful if you want to control the open state from outside the component.
73
+ */
74
+ onOpenChange?: (isOpen: boolean) => void;
75
+ /** The label describing the choice */
76
+ label: string;
77
+ /** The name of the select element */
78
+ name?: string;
79
+ /**
80
+ * What's shown if nothing is selected.
81
+ *
82
+ * Defaults to a localized version of "choose an option"
83
+ */
84
+ placeholder?: string;
85
+ /** The width of the select box.
86
+ *
87
+ * Defaults to the width of the selected content
88
+ */
89
+ width?: ResponsiveValue<string | number>;
90
+ isDisabled?: boolean;
91
+ /** A list of disabled keys.
92
+ *
93
+ * ```tsx
94
+ * <Select label="Choose a color" disabledKeys={["blue", "yellow"]}>
95
+ * <SelectItem key="green">Green</SelectItem>
96
+ * <SelectItem key="blue">Blue</SelectItem>
97
+ * <SelectItem key="yellow">Yellow</SelectItem>
98
+ * </Select>
99
+ * ```
100
+ **/
101
+ disabledKeys?: string[];
102
+ };
103
+ /**
104
+ * A styled select component.
105
+ *
106
+ * This select component lets you choose between a list of options.
107
+ * Compared to the NativeSelect component, the InfoSelect component lets you style the options however you'd like – including both text, icons and other elements.
108
+ *
109
+ * ```tsx
110
+ * <InfoSelect label="Choose a color">
111
+ * <SelectOption>Blue</SelectOption>
112
+ * <SelectOption>Yellow</SelectOption>
113
+ * <SelectOption>Green</SelectOption>
114
+ * </InfoSelect>
115
+ * ```
116
+ *
117
+ * Alternatvely, you can pass the items into the `items` prop, and create a render function for the items.
118
+ *
119
+ * ```tsx
120
+ * <InfoSelect
121
+ * label="Choose a color"
122
+ * items={[
123
+ * { value: "#f00", label: "Red" },
124
+ * { value: "#0f0", label: "Green" },
125
+ * { value: "#00f", label: "Blue" },
126
+ * ]}
127
+ * >
128
+ * {(item) => (
129
+ * <SelectItem key={item.key}>
130
+ * {item.label}
131
+ * </SelectItem>
132
+ * )}
133
+ * </InfoSelect>
134
+ * ```
135
+ *
136
+ * @see https://spor.vy.no/komponenter/info-select
137
+ */
138
+ export const InfoSelect = ({
139
+ placeholder,
140
+ width = "100%",
141
+ onChange,
142
+ value,
143
+ defaultValue,
144
+ ...props
145
+ }: InfoSelectProps) => {
146
+ const renamedProps = {
147
+ onSelectionChange: onChange,
148
+ selectedKey: value,
149
+ defaultSelectedKey: defaultValue,
150
+ ...props,
151
+ };
152
+ const state = useSelectState(renamedProps);
153
+ const triggerRef = useRef<HTMLButtonElement>(null);
154
+ const { labelProps, triggerProps, valueProps, menuProps } = useSelect(
155
+ renamedProps,
156
+ state,
157
+ triggerRef
158
+ );
159
+
160
+ const styles = useMultiStyleConfig("InfoSelect", { isOpen: state.isOpen });
161
+ const { buttonProps } = useButton(triggerProps, triggerRef);
162
+ const { t } = useTranslation();
163
+
164
+ return (
165
+ <Box sx={styles.container}>
166
+ <chakra.div {...labelProps} sx={styles.label}>
167
+ {props.label}
168
+ </chakra.div>
169
+ <HiddenSelect
170
+ state={state}
171
+ triggerRef={triggerRef}
172
+ label={props.label}
173
+ name={props.name}
174
+ />
175
+
176
+ <chakra.button
177
+ type="button"
178
+ ref={triggerRef}
179
+ sx={styles.button}
180
+ {...buttonProps}
181
+ width={width}
182
+ >
183
+ <Box {...valueProps}>
184
+ {state.selectedItem
185
+ ? state.selectedItem.textValue ?? state.selectedItem.rendered
186
+ : placeholder ?? t(texts.selectAnOption)}
187
+ </Box>
188
+ <Box sx={styles.arrowIcon}>
189
+ {state.isOpen ? <DropdownUpFill24Icon /> : <DropdownDownFill24Icon />}
190
+ </Box>
191
+ </chakra.button>
192
+
193
+ {state.isOpen && (
194
+ <Popover state={state} triggerRef={triggerRef}>
195
+ <ListBox listBoxOptions={menuProps} state={state} borderBottomRadius="sm" />
196
+ </Popover>
197
+ )}
198
+ </Box>
199
+ );
200
+ };
201
+
202
+ const texts = createTexts({
203
+ selectAnOption: {
204
+ nb: "Velg et alternativ",
205
+ nn: "Velg eit alternativ",
206
+ sv: "Välj ett alternativ",
207
+ en: "Choose an option",
208
+ },
209
+ });
@@ -0,0 +1,59 @@
1
+ import {
2
+ FormLabel,
3
+ forwardRef,
4
+ Input as ChakraInput,
5
+ InputGroup,
6
+ InputLeftElement,
7
+ InputProps as ChakraInputProps,
8
+ InputRightElement,
9
+ useFormControlContext,
10
+ } from "@chakra-ui/react";
11
+ import React, { useId } from "react";
12
+
13
+ export type InputProps = Exclude<ChakraInputProps, "variant" | "size"> & {
14
+ /** The input's label */
15
+ label: string;
16
+ /** Icon that shows up to the left */
17
+ leftIcon?: React.ReactNode;
18
+ /** Icon that shows up to the right */
19
+ rightIcon?: React.ReactNode;
20
+ };
21
+ /**
22
+ * Inputs let you enter text or other data.
23
+ *
24
+ * You need to specify the label as a prop, since it doubles as the placeholder.
25
+ *
26
+ * ```tsx
27
+ * <Input label="E-mail" />
28
+ * ```
29
+ *
30
+ * You can also add icons to the left and right of the input. Please use the 24 px icons for this.
31
+ *
32
+ * ```tsx
33
+ * <Input label="E-mail" leftIcon={<EmailOutline24Icon />} />
34
+ * ```
35
+ */
36
+ export const Input = forwardRef<InputProps, "input">(
37
+ ({ label, leftIcon, rightIcon, id, ...props }, ref) => {
38
+ const formControlProps = useFormControlContext();
39
+ const fallbackId = `input-${useId()}`;
40
+ const inputId = id ?? formControlProps?.id ?? fallbackId;
41
+ return (
42
+ <InputGroup position="relative">
43
+ {leftIcon && <InputLeftElement>{leftIcon}</InputLeftElement>}
44
+ <ChakraInput
45
+ pl={leftIcon ? 7 : undefined}
46
+ pr={rightIcon ? 7 : undefined}
47
+ {...props}
48
+ id={inputId}
49
+ ref={ref}
50
+ placeholder=" " // This is needed to make the label work as expected
51
+ />
52
+ <FormLabel htmlFor={inputId} pointerEvents="none">
53
+ {label}
54
+ </FormLabel>
55
+ {rightIcon && <InputRightElement>{rightIcon}</InputRightElement>}
56
+ </InputGroup>
57
+ );
58
+ }
59
+ );
@@ -0,0 +1,45 @@
1
+ import {
2
+ As,
3
+ forwardRef,
4
+ InputElementProps as ChakraInputElementProps,
5
+ InputLeftElement as ChakraInputLeftElement,
6
+ InputRightElement as ChakraInputRightElement,
7
+ } from "@chakra-ui/react";
8
+ import React from "react";
9
+
10
+ export type InputElementProps = ChakraInputElementProps;
11
+ /**
12
+ * Places an element inside the left side of an input field.
13
+ *
14
+ * Must be used inside of an `InputGroup` component, and before the `Input` component.
15
+ *
16
+ * ```tsx
17
+ * <FormControl>
18
+ * <InputGroup>
19
+ * <InputLeftElement>🔎</InputLeftElement>
20
+ * <Input label="Search" />
21
+ * </InputGroup>
22
+ * </FormControl>
23
+ * ```
24
+ */
25
+ export const InputLeftElement = forwardRef<InputElementProps, "div">(
26
+ (props, ref) => <ChakraInputLeftElement {...props} ref={ref} />
27
+ );
28
+
29
+ /**
30
+ * Places an element inside the right side of an input field.
31
+ *
32
+ * Must be used inside of an `InputGroup` component, and after the `Input` component.
33
+ *
34
+ * ```tsx
35
+ * <FormControl>
36
+ * <InputGroup>
37
+ * <Input label="Search" />
38
+ * <InputRightElement>🔎</InputRightElement>
39
+ * </InputGroup>
40
+ * </FormControl>
41
+ * ```
42
+ */
43
+ export const InputRightElement = forwardRef<InputElementProps, "div">(
44
+ (props, ref) => <ChakraInputRightElement {...props} ref={ref} />
45
+ );
@@ -0,0 +1,123 @@
1
+ import {
2
+ Box,
3
+ BoxProps,
4
+ chakra,
5
+ forwardRef,
6
+ useMultiStyleConfig,
7
+ } from "@chakra-ui/react";
8
+ import type { Node } from "@react-types/shared";
9
+ import React, { RefObject, useContext, useRef } from "react";
10
+ import type { AriaListBoxOptions } from "react-aria";
11
+ import { useListBox, useOption } from "react-aria";
12
+ import type { ListState, SelectState } from "react-stately";
13
+
14
+ type ListBoxProps = {
15
+ listBoxRef?: React.RefObject<HTMLUListElement>;
16
+ listBoxOptions: AriaListBoxOptions<unknown>;
17
+ state: SelectState<unknown> | ListState<unknown>;
18
+ } & BoxProps;
19
+
20
+ type OptionProps = {
21
+ item: Node<unknown>;
22
+ state: SelectState<any> | ListState<unknown>;
23
+ };
24
+
25
+ /**
26
+ * A listbox component.
27
+ *
28
+ * This component is currently only thought to be used with the `InfoSelect` component. Usage outside of that is not documented, nor intended.
29
+ */
30
+ export const ListBox = forwardRef<ListBoxProps, "ul">((props, ref) => {
31
+ const { state, listBoxOptions, ...rest } = props;
32
+ const styles = useMultiStyleConfig("ListBox", {});
33
+ const internalRef = useRef<HTMLUListElement>(null);
34
+ const listBoxRef = (ref ?? internalRef) as RefObject<HTMLUListElement>;
35
+ const { listBoxProps } = useListBox(listBoxOptions, state, listBoxRef);
36
+
37
+ return (
38
+ <Box
39
+ as="ul"
40
+ {...listBoxProps}
41
+ sx={styles.container}
42
+ ref={listBoxRef as RefObject<any>}
43
+ {...rest}
44
+ >
45
+ {Array.from(state.collection).map((item) => (
46
+ <Option key={item.key} item={item} state={state} />
47
+ ))}
48
+ </Box>
49
+ );
50
+ });
51
+
52
+ type OptionContextValue = {
53
+ labelProps: React.HTMLAttributes<HTMLElement>;
54
+ descriptionProps: React.HTMLAttributes<HTMLElement>;
55
+ };
56
+
57
+ const OptionContext = React.createContext<OptionContextValue>({
58
+ labelProps: {},
59
+ descriptionProps: {},
60
+ });
61
+ const useOptionContext = () => {
62
+ return useContext(OptionContext);
63
+ };
64
+
65
+ const Option = ({ item, state }: OptionProps) => {
66
+ const ref = useRef<HTMLLIElement>(null);
67
+ const styles = useMultiStyleConfig("ListBox", {});
68
+ const { optionProps, labelProps, descriptionProps } = useOption(
69
+ { key: item.key },
70
+ state,
71
+ ref
72
+ );
73
+
74
+ return (
75
+ <chakra.li {...optionProps} ref={ref} sx={styles.item}>
76
+ <OptionContext.Provider value={{ labelProps, descriptionProps }}>
77
+ {item.rendered}
78
+ </OptionContext.Provider>
79
+ </chakra.li>
80
+ );
81
+ };
82
+
83
+ // The Label and Description components will be used within a <SelectItem>.
84
+ // They receive props from the OptionContext defined above.
85
+ // This ensures that the option is ARIA labelled by the label, and
86
+ // described by the description, which makes for better announcements
87
+ // for screen reader users.
88
+
89
+ /**
90
+ * Renders a label for a SelectItem.
91
+ *
92
+ * Useful if you want to render a custom SelectItem - especially if it has a description.
93
+ */
94
+ export function SelectItemLabel({ children }: { children: React.ReactNode }) {
95
+ let { labelProps } = useOptionContext();
96
+ const styles = useMultiStyleConfig("ListBox", {});
97
+ return (
98
+ <Box {...labelProps} sx={styles.label}>
99
+ {children}
100
+ </Box>
101
+ );
102
+ }
103
+
104
+ /**
105
+ * Renders a description for a SelectItem.
106
+ *
107
+ * Useful if you want to render a custom SelectItem with more than just a label.
108
+ */
109
+ export function SelectItemDescription({
110
+ children,
111
+ }: {
112
+ children: React.ReactNode;
113
+ }) {
114
+ let { descriptionProps } = useOptionContext();
115
+ const styles = useMultiStyleConfig("ListBox", {});
116
+ return (
117
+ <Box {...descriptionProps} sx={styles.description}>
118
+ {children}
119
+ </Box>
120
+ );
121
+ }
122
+
123
+ export { Item as SelectItem } from "react-stately";
@@ -0,0 +1,38 @@
1
+ import {
2
+ Select as ChakraSelect,
3
+ SelectProps as ChakraSelectProps,
4
+ forwardRef,
5
+ useMultiStyleConfig,
6
+ } from "@chakra-ui/react";
7
+ import React from "react";
8
+ import { FormControl, FormLabel } from ".";
9
+
10
+ export type NativeSelectProps = Exclude<
11
+ ChakraSelectProps,
12
+ "colorScheme" | "variant" | "size"
13
+ > & { label?: string };
14
+ /**
15
+ * Selects let you choose between several options
16
+ *
17
+ * You should consider only using the Select component when you have more than 4 options. Otherwise, you should use the `<Radio>` component.
18
+ *
19
+ * ```tsx
20
+ * <NativeSelect label="Select level of luxury">
21
+ * <option>No luxury</option>
22
+ * <option>Some luxury</option>
23
+ * <option>Lots of luxury</option>
24
+ * <option>I'm rich</option>
25
+ * </NativeSelect>
26
+ * ```
27
+ */
28
+ export const NativeSelect = forwardRef<NativeSelectProps, "select">(
29
+ ({ label, ...props }, ref) => {
30
+ const styles = useMultiStyleConfig("Select", props);
31
+ return (
32
+ <FormControl>
33
+ <ChakraSelect {...props} rootProps={{ __css: styles.root }} ref={ref} />
34
+ {label && <FormLabel>{label}</FormLabel>}
35
+ </FormControl>
36
+ );
37
+ }
38
+ );
@@ -0,0 +1,70 @@
1
+ import {
2
+ Button,
3
+ Input as ChakraInput,
4
+ forwardRef,
5
+ useDisclosure,
6
+ useFormControlContext,
7
+ } from "@chakra-ui/react";
8
+ import React, { useId } from "react";
9
+ import {
10
+ FormLabel,
11
+ InputGroup,
12
+ InputLeftElement,
13
+ InputProps,
14
+ InputRightElement,
15
+ } from ".";
16
+ import { createTexts, useTranslation } from "..";
17
+
18
+ export type PasswordInputProps = InputProps;
19
+ export const PasswordInput = forwardRef<PasswordInputProps, "input">(
20
+ ({ leftIcon, id, label, ...props }, ref) => {
21
+ const { isOpen: isShowingPassword, onToggle } = useDisclosure();
22
+ const { t } = useTranslation();
23
+ const formControlProps = useFormControlContext();
24
+ const autoGeneratedId = `password-input-${useId()}`;
25
+ const inputId = id ?? formControlProps?.id ?? autoGeneratedId;
26
+ return (
27
+ <InputGroup position="relative">
28
+ {leftIcon && <InputLeftElement>{leftIcon}</InputLeftElement>}
29
+ <ChakraInput
30
+ {...props}
31
+ id={inputId}
32
+ placeholder=" " // This is needed to make the label work as expected
33
+ type={isShowingPassword ? "text" : "password"}
34
+ paddingRight={10}
35
+ paddingLeft={leftIcon ? 7 : undefined}
36
+ ref={ref}
37
+ />
38
+ <FormLabel htmlFor={inputId} pointerEvents="none">
39
+ {label}
40
+ </FormLabel>
41
+ <InputRightElement width="fit-content">
42
+ <Button
43
+ variant="ghost"
44
+ type="button"
45
+ onClick={onToggle}
46
+ borderRadius="sm"
47
+ mr={1}
48
+ >
49
+ {isShowingPassword ? t(texts.hidePassword) : t(texts.showPassword)}
50
+ </Button>
51
+ </InputRightElement>
52
+ </InputGroup>
53
+ );
54
+ }
55
+ );
56
+
57
+ const texts = createTexts({
58
+ showPassword: {
59
+ nb: "Vis",
60
+ nn: "Vis",
61
+ en: "Show",
62
+ sv: "Visa",
63
+ },
64
+ hidePassword: {
65
+ nb: "Skjul",
66
+ nn: "Skjul",
67
+ en: "Hide",
68
+ sv: "Dölj",
69
+ },
70
+ });
@@ -0,0 +1,70 @@
1
+ import { Box } from "@chakra-ui/react";
2
+ import React, { useRef } from "react";
3
+ import {
4
+ AriaPopoverProps,
5
+ DismissButton,
6
+ Overlay,
7
+ usePopover,
8
+ } from "react-aria";
9
+ import { OverlayTriggerState } from "react-stately";
10
+
11
+ type PopoverProps = {
12
+ /** The content to be shown as a popover */
13
+ children: React.ReactNode;
14
+ /** The internal state of the overlay trigger element.
15
+ *
16
+ * Get this from the useOverlayTriggerState hook or similar.
17
+ */
18
+ state: OverlayTriggerState;
19
+ /** The reference to the trigger button for this overlay */
20
+ triggerRef: React.RefObject<HTMLButtonElement>;
21
+ /** The offset in pixels between the bottom of the trigger and the top of the popover */
22
+ offset?: number;
23
+ /** The cross-axis offset (left or right) of the popover, compared to the center of the trigger element */
24
+ crossOffset?: number;
25
+ /** The position of the popover, relative to the popover.
26
+ *
27
+ * Defaults to "bottom"
28
+ */
29
+ placement?: AriaPopoverProps["placement"];
30
+ };
31
+ /**
32
+ * Internal popover component.
33
+ *
34
+ * Used to render accessible popover content, like a dropdown menu or a card select. Should not be used directly, but as a part of Spor components.
35
+ */
36
+ export const Popover = ({
37
+ children,
38
+ state,
39
+ triggerRef,
40
+ offset = 0,
41
+ crossOffset = 0,
42
+ placement = "bottom",
43
+ }: PopoverProps) => {
44
+ const popoverRef = useRef<HTMLDivElement>(null);
45
+ const { popoverProps, underlayProps } = usePopover(
46
+ {
47
+ triggerRef,
48
+ popoverRef,
49
+ offset,
50
+ crossOffset,
51
+ placement,
52
+ },
53
+ state
54
+ );
55
+
56
+ return (
57
+ <Overlay>
58
+ <Box {...underlayProps} position="fixed" inset="0" />
59
+ <Box
60
+ {...popoverProps}
61
+ ref={popoverRef}
62
+ minWidth={triggerRef.current?.clientWidth ?? "auto"}
63
+ >
64
+ <DismissButton onDismiss={state.close} />
65
+ {children}
66
+ <DismissButton onDismiss={state.close} />
67
+ </Box>
68
+ </Overlay>
69
+ );
70
+ };
@@ -0,0 +1,34 @@
1
+ import {
2
+ As,
3
+ Radio as ChakraRadio,
4
+ RadioProps as ChakraRadioProps,
5
+ forwardRef,
6
+ } from "@chakra-ui/react";
7
+ import React from "react";
8
+
9
+ export type RadioProps = Exclude<
10
+ ChakraRadioProps,
11
+ "colorScheme" | "size" | "variant"
12
+ >;
13
+
14
+ /**
15
+ * The humble radio button.
16
+ *
17
+ * Specify the label as `children` and the value as `value`.
18
+ *
19
+ * ```tsx
20
+ * <Radio value="#f00">Red</Radio>
21
+ * ```
22
+ *
23
+ * You typically want to place Radio components in a group with several other Radio components. You can do that with the `RadioGroup` component.
24
+ *
25
+ * ```tsx
26
+ * <RadioGroup name="ticket">
27
+ * <Radio value="economy">Economy</Radio>
28
+ * <Radio value="business">Business</Radio>
29
+ * <Radio value="first-class">First Class</Radio>
30
+ * </RadioGroup>
31
+ */
32
+ export const Radio = forwardRef<RadioProps, "input">((props, ref) => {
33
+ return <ChakraRadio {...props} ref={ref} />;
34
+ });
@@ -0,0 +1,47 @@
1
+ import {
2
+ forwardRef,
3
+ RadioGroup as ChakraRadioGroup,
4
+ RadioGroupProps as ChakraRadioGroupProps,
5
+ Stack,
6
+ StackDirection,
7
+ } from "@chakra-ui/react";
8
+ import React from "react";
9
+
10
+ export type RadioGroupProps = Exclude<
11
+ ChakraRadioGroupProps,
12
+ "colorScheme" | "size" | "variant"
13
+ > & {
14
+ direction?: StackDirection;
15
+ };
16
+ /**
17
+ * Radio groups are used to group several radio buttons together.
18
+ *
19
+ * You can pass the common `name` prop to the `RadioGroup`, instead of to each `Radio` component.
20
+ *
21
+ * ```tsx
22
+ * <RadioGroup name="ticket">
23
+ * <Radio>Economy</Radio>
24
+ * <Radio>Business</Radio>
25
+ * <Radio>First Class</Radio>
26
+ * </RadioGroup>
27
+ * ```
28
+ *
29
+ * By default, radio buttons show up horizontally. If you want them to show up vertically, please specify the `direction="column"` prop.
30
+ *
31
+ * ```tsx
32
+ * <RadioGroup name="ticket" direction="column">
33
+ * <Radio>Economy</Radio>
34
+ * <Radio>Business</Radio>
35
+ * <Radio>First Class</Radio>
36
+ * </RadioGroup>
37
+ * ```
38
+ */
39
+ export const RadioGroup = forwardRef<RadioGroupProps, "div">(
40
+ ({ children, direction = "row", ...rest }, ref) => {
41
+ return (
42
+ <ChakraRadioGroup {...rest} ref={ref}>
43
+ <Stack direction={direction}>{children}</Stack>
44
+ </ChakraRadioGroup>
45
+ );
46
+ }
47
+ );