@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,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
|
+
);
|