@vygruppen/spor-react 12.13.2 → 12.13.4
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 -12
- package/.turbo/turbo-postinstall.log +2 -1
- package/CHANGELOG.md +13 -0
- package/dist/index.cjs +124 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.mjs +125 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/datepicker/DatePicker.tsx +32 -14
- package/src/datepicker/StyledField.tsx +15 -2
- package/src/input/Field.tsx +3 -1
- package/src/input/FloatingLabel.tsx +6 -11
- package/src/input/Input.tsx +33 -2
- package/src/input/NativeSelect.tsx +1 -0
- package/src/input/Textarea.tsx +35 -1
- package/src/theme/slot-recipes/datepicker.ts +31 -0
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@vygruppen/spor-react",
|
3
3
|
"type": "module",
|
4
|
-
"version": "12.13.
|
4
|
+
"version": "12.13.4",
|
5
5
|
"exports": {
|
6
6
|
".": {
|
7
7
|
"types": "./dist/index.d.ts",
|
@@ -46,8 +46,8 @@
|
|
46
46
|
"react-stately": "^3.31.1",
|
47
47
|
"react-swipeable": "^7.0.1",
|
48
48
|
"usehooks-ts": "^3.1.0",
|
49
|
-
"@vygruppen/spor-icon-react": "4.2.1",
|
50
49
|
"@vygruppen/spor-design-tokens": "4.1.0",
|
50
|
+
"@vygruppen/spor-icon-react": "4.2.1",
|
51
51
|
"@vygruppen/spor-loader": "0.7.0"
|
52
52
|
},
|
53
53
|
"devDependencies": {
|
@@ -10,6 +10,7 @@ import {
|
|
10
10
|
useFieldContext,
|
11
11
|
useSlotRecipe,
|
12
12
|
} from "@chakra-ui/react";
|
13
|
+
import { CalendarOutline24Icon } from "@vygruppen/spor-icon-react";
|
13
14
|
import React, { forwardRef, PropsWithChildren, useId, useRef } from "react";
|
14
15
|
import {
|
15
16
|
AriaDatePickerProps,
|
@@ -42,6 +43,10 @@ type DatePickerProps = Omit<AriaDatePickerProps<DateValue>, "onChange"> &
|
|
42
43
|
withPortal?: boolean;
|
43
44
|
onChange?: (value: DateValue | null) => void;
|
44
45
|
positioning?: PopoverRootProps["positioning"];
|
46
|
+
noCalendar?: boolean;
|
47
|
+
overrideBorderColor?: string;
|
48
|
+
isActive?: boolean;
|
49
|
+
onClick?: () => void;
|
45
50
|
} & FieldBaseProps;
|
46
51
|
|
47
52
|
/**
|
@@ -97,8 +102,14 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
|
|
97
102
|
const styles = recipe({ variant });
|
98
103
|
const locale = useCurrentLocale();
|
99
104
|
|
105
|
+
const shouldShowCalendar =
|
106
|
+
state.isOpen && !props.isDisabled && !props.noCalendar;
|
107
|
+
|
100
108
|
const onFieldClick = () => {
|
101
|
-
|
109
|
+
if (!props.noCalendar) {
|
110
|
+
state.setOpen(true);
|
111
|
+
}
|
112
|
+
props.onClick?.();
|
102
113
|
};
|
103
114
|
|
104
115
|
const popoverContent = (
|
@@ -139,16 +150,25 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
|
|
139
150
|
paddingX={3}
|
140
151
|
minHeight={minHeight}
|
141
152
|
isDisabled={props.isDisabled}
|
153
|
+
isActive={props.isActive}
|
154
|
+
overrideBorderColor={props.overrideBorderColor}
|
142
155
|
>
|
143
|
-
|
144
|
-
<
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
156
|
+
{props.noCalendar ? (
|
157
|
+
<Box pr={3} pl={0.5} mr={0.5}>
|
158
|
+
<CalendarOutline24Icon />
|
159
|
+
</Box>
|
160
|
+
) : (
|
161
|
+
<ChakraPopover.Trigger asChild>
|
162
|
+
<CalendarTriggerButton
|
163
|
+
paddingLeft={1}
|
164
|
+
paddingRight={1}
|
165
|
+
variant={variant}
|
166
|
+
ref={ref}
|
167
|
+
{...buttonProps}
|
168
|
+
/>
|
169
|
+
</ChakraPopover.Trigger>
|
170
|
+
)}
|
171
|
+
|
152
172
|
<DateField
|
153
173
|
label={props.label}
|
154
174
|
labelProps={labelProps}
|
@@ -159,10 +179,8 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
|
|
159
179
|
</PopoverAnchor>
|
160
180
|
</Field>
|
161
181
|
|
162
|
-
{
|
163
|
-
<Portal>{popoverContent}</Portal>
|
164
|
-
)}
|
165
|
-
{state.isOpen && !props.isDisabled && !withPortal && popoverContent}
|
182
|
+
{shouldShowCalendar &&
|
183
|
+
(withPortal ? <Portal>{popoverContent}</Portal> : popoverContent)}
|
166
184
|
</ChakraPopover.Root>
|
167
185
|
</Box>
|
168
186
|
</I18nProvider>
|
@@ -14,10 +14,19 @@ type StyledFieldProps = BoxProps &
|
|
14
14
|
PropsWithChildren<DatePickerVariantProps> &
|
15
15
|
CalendarVariants & {
|
16
16
|
isDisabled?: boolean;
|
17
|
+
isActive?: boolean;
|
18
|
+
overrideBorderColor?: string;
|
17
19
|
};
|
18
20
|
export const StyledField = forwardRef<HTMLDivElement, StyledFieldProps>(
|
19
21
|
function StyledField(props, ref) {
|
20
|
-
const {
|
22
|
+
const {
|
23
|
+
children,
|
24
|
+
variant,
|
25
|
+
isDisabled,
|
26
|
+
isActive,
|
27
|
+
overrideBorderColor,
|
28
|
+
...otherProps
|
29
|
+
} = props;
|
21
30
|
const { invalid } = useFieldContext() ?? {
|
22
31
|
isInvalid: false,
|
23
32
|
};
|
@@ -30,7 +39,11 @@ export const StyledField = forwardRef<HTMLDivElement, StyledFieldProps>(
|
|
30
39
|
return (
|
31
40
|
<Box
|
32
41
|
{...otherProps}
|
33
|
-
css={
|
42
|
+
css={{
|
43
|
+
...styles.wrapper,
|
44
|
+
outlineColor: overrideBorderColor || undefined,
|
45
|
+
}}
|
46
|
+
data-active={isActive ? "" : undefined}
|
34
47
|
ref={ref}
|
35
48
|
aria-invalid={invalid}
|
36
49
|
aria-disabled={isDisabled}
|
package/src/input/Field.tsx
CHANGED
@@ -26,6 +26,7 @@ export type FieldBaseProps = {
|
|
26
26
|
helperText?: React.ReactNode;
|
27
27
|
errorText?: React.ReactNode;
|
28
28
|
floatingLabel?: boolean;
|
29
|
+
shouldFloat?: boolean;
|
29
30
|
};
|
30
31
|
|
31
32
|
export type FieldProps = Omit<
|
@@ -66,6 +67,7 @@ export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
|
66
67
|
required,
|
67
68
|
direction,
|
68
69
|
id,
|
70
|
+
shouldFloat,
|
69
71
|
...rest
|
70
72
|
} = props;
|
71
73
|
const recipe = useSlotRecipe({ key: "field" });
|
@@ -92,7 +94,7 @@ export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
|
92
94
|
{children}
|
93
95
|
|
94
96
|
{label && floatingLabel && (
|
95
|
-
<FloatingLabel>
|
97
|
+
<FloatingLabel data-float={shouldFloat ? true : undefined}>
|
96
98
|
{label}
|
97
99
|
<ChakraField.RequiredIndicator />
|
98
100
|
</FloatingLabel>
|
@@ -12,8 +12,6 @@ FloatingLabel.displayName = "FloatingLabel";
|
|
12
12
|
const floatingLabelStyles = defineStyle({
|
13
13
|
paddingX: 3,
|
14
14
|
fontWeight: "normal",
|
15
|
-
fontSize: ["mobile.xs", "desktop.xs"],
|
16
|
-
color: "text",
|
17
15
|
pointerEvents: "none",
|
18
16
|
zIndex: "docked",
|
19
17
|
_disabled: {
|
@@ -21,16 +19,13 @@ const floatingLabelStyles = defineStyle({
|
|
21
19
|
},
|
22
20
|
|
23
21
|
pos: "absolute",
|
24
|
-
top: "0.3rem",
|
25
22
|
transition: "position",
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
_peerFocusVisible: {
|
33
|
-
/* For when input is in focus */
|
23
|
+
|
24
|
+
top: "0.9rem",
|
25
|
+
color: "text",
|
26
|
+
fontSize: ["mobile.sm", "desktop.sm"],
|
27
|
+
|
28
|
+
"&[data-float]": {
|
34
29
|
fontSize: ["mobile.xs", "desktop.xs"],
|
35
30
|
color: "text",
|
36
31
|
top: "0.3rem",
|
package/src/input/Input.tsx
CHANGED
@@ -7,7 +7,7 @@ import {
|
|
7
7
|
InputElement,
|
8
8
|
useRecipe,
|
9
9
|
} from "@chakra-ui/react";
|
10
|
-
import React, { ComponentProps, forwardRef, ReactNode } from "react";
|
10
|
+
import React, { ComponentProps, forwardRef, ReactNode, useState } from "react";
|
11
11
|
|
12
12
|
type ChakraInputProps = ComponentProps<typeof ChakraInput>;
|
13
13
|
|
@@ -74,6 +74,20 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
74
74
|
const [recipeProps, restProps] = recipe.splitVariantProps(props);
|
75
75
|
const styles = recipe(recipeProps);
|
76
76
|
|
77
|
+
const [focused, setFocused] = useState(false);
|
78
|
+
|
79
|
+
const isControlled = props.value !== undefined;
|
80
|
+
|
81
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(
|
82
|
+
props.defaultValue ? String(props.defaultValue) : "",
|
83
|
+
);
|
84
|
+
|
85
|
+
const inputValue = isControlled
|
86
|
+
? String(props.value ?? "")
|
87
|
+
: uncontrolledValue;
|
88
|
+
|
89
|
+
const shouldFloat = inputValue.length > 0 || focused;
|
90
|
+
|
77
91
|
return (
|
78
92
|
<Field
|
79
93
|
invalid={invalid}
|
@@ -83,13 +97,13 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
83
97
|
errorText={errorText}
|
84
98
|
id={props.id}
|
85
99
|
label={
|
86
|
-
// Render startElement invisibly to align label text with input content when an icon is present
|
87
100
|
<Flex>
|
88
101
|
<Box visibility="hidden">{startElement}</Box>
|
89
102
|
{label}
|
90
103
|
</Flex>
|
91
104
|
}
|
92
105
|
floatingLabel={true}
|
106
|
+
shouldFloat={shouldFloat}
|
93
107
|
>
|
94
108
|
{startElement && (
|
95
109
|
<InputElement pointerEvents="none" paddingX={2}>
|
@@ -105,9 +119,25 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
105
119
|
paddingRight={endElement ? "2.6rem" : undefined}
|
106
120
|
{...restProps}
|
107
121
|
className={`peer ${props.className}`}
|
122
|
+
value={isControlled ? props.value : undefined}
|
123
|
+
onFocus={(e) => {
|
124
|
+
props.onFocus?.(e);
|
125
|
+
setFocused(true);
|
126
|
+
}}
|
127
|
+
onBlur={(e) => {
|
128
|
+
props.onBlur?.(e);
|
129
|
+
setFocused(false);
|
130
|
+
}}
|
131
|
+
onChange={(e) => {
|
132
|
+
props.onChange?.(e);
|
133
|
+
if (!isControlled) {
|
134
|
+
setUncontrolledValue(e.target.value);
|
135
|
+
}
|
136
|
+
}}
|
108
137
|
placeholder=""
|
109
138
|
css={styles}
|
110
139
|
/>
|
140
|
+
|
111
141
|
{endElement && (
|
112
142
|
<InputElement placement="end" paddingX={2}>
|
113
143
|
{endElement}
|
@@ -117,4 +147,5 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
117
147
|
);
|
118
148
|
},
|
119
149
|
);
|
150
|
+
|
120
151
|
Input.displayName = "Input";
|
package/src/input/Textarea.tsx
CHANGED
@@ -85,6 +85,11 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
85
85
|
readOnly,
|
86
86
|
helperText,
|
87
87
|
floatingLabel,
|
88
|
+
value,
|
89
|
+
defaultValue,
|
90
|
+
onFocus,
|
91
|
+
onBlur,
|
92
|
+
onChange,
|
88
93
|
...restProps
|
89
94
|
} = props;
|
90
95
|
const recipe = useRecipe({ key: "textarea" });
|
@@ -92,6 +97,14 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
92
97
|
|
93
98
|
const { labelRef, labelHeight } = useLabelHeight(label);
|
94
99
|
|
100
|
+
const [focused, setFocused] = useState(false);
|
101
|
+
const isControlled = value !== undefined;
|
102
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(
|
103
|
+
defaultValue ? String(defaultValue) : "",
|
104
|
+
);
|
105
|
+
const inputValue = isControlled ? String(value ?? "") : uncontrolledValue;
|
106
|
+
const shouldFloat = inputValue.length > 0 || focused;
|
107
|
+
|
95
108
|
return (
|
96
109
|
<Field
|
97
110
|
errorText={errorText}
|
@@ -100,6 +113,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
100
113
|
required={required}
|
101
114
|
readOnly={readOnly}
|
102
115
|
floatingLabel={floatingLabel}
|
116
|
+
shouldFloat={shouldFloat}
|
103
117
|
position="relative"
|
104
118
|
>
|
105
119
|
<ChakraTextarea
|
@@ -107,14 +121,34 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
107
121
|
css={styles}
|
108
122
|
className="peer"
|
109
123
|
ref={ref}
|
124
|
+
value={isControlled ? value : undefined}
|
125
|
+
defaultValue={defaultValue}
|
126
|
+
onFocus={(e) => {
|
127
|
+
onFocus?.(e);
|
128
|
+
setFocused(true);
|
129
|
+
}}
|
130
|
+
onBlur={(e) => {
|
131
|
+
onBlur?.(e);
|
132
|
+
setFocused(false);
|
133
|
+
}}
|
134
|
+
onChange={(e) => {
|
135
|
+
onChange?.(e);
|
136
|
+
if (!isControlled) setUncontrolledValue(e.target.value);
|
137
|
+
}}
|
110
138
|
style={
|
111
139
|
{ "--label-height": `${labelHeight}px` } as React.CSSProperties
|
112
140
|
}
|
113
141
|
placeholder=" "
|
114
142
|
/>
|
115
|
-
<FloatingLabel
|
143
|
+
<FloatingLabel
|
144
|
+
ref={labelRef}
|
145
|
+
data-float={shouldFloat ? true : undefined}
|
146
|
+
>
|
147
|
+
{label}
|
148
|
+
</FloatingLabel>
|
116
149
|
</Field>
|
117
150
|
);
|
118
151
|
},
|
119
152
|
);
|
153
|
+
|
120
154
|
Textarea.displayName = "Textarea";
|
@@ -28,6 +28,13 @@ export const datePickerSlotRecipe = defineSlotRecipe({
|
|
28
28
|
outline: "2px solid",
|
29
29
|
outlineColor: "outline.focus",
|
30
30
|
},
|
31
|
+
"&[data-active]": {
|
32
|
+
outline: "2px solid",
|
33
|
+
outlineColor: "outline.focus",
|
34
|
+
"&:hover": {
|
35
|
+
outlineColor: "outline.focus",
|
36
|
+
},
|
37
|
+
},
|
31
38
|
},
|
32
39
|
inputLabel: {
|
33
40
|
fontSize: "mobile.xs",
|
@@ -158,6 +165,14 @@ export const datePickerSlotRecipe = defineSlotRecipe({
|
|
158
165
|
outline: "1px solid",
|
159
166
|
outlineColor: "core.outline",
|
160
167
|
},
|
168
|
+
|
169
|
+
"&[data-active]": {
|
170
|
+
outline: "2px solid",
|
171
|
+
outlineColor: "outline.focus",
|
172
|
+
_hover: {
|
173
|
+
outlineColor: "outline.focus",
|
174
|
+
},
|
175
|
+
},
|
161
176
|
},
|
162
177
|
_invalid: {
|
163
178
|
outline: "2px solid",
|
@@ -183,6 +198,14 @@ export const datePickerSlotRecipe = defineSlotRecipe({
|
|
183
198
|
outline: "1px solid",
|
184
199
|
outlineColor: "core.outline",
|
185
200
|
},
|
201
|
+
|
202
|
+
"&[data-active]": {
|
203
|
+
outline: "2px solid",
|
204
|
+
outlineColor: "outline.focus",
|
205
|
+
_hover: {
|
206
|
+
outlineColor: "outline.focus",
|
207
|
+
},
|
208
|
+
},
|
186
209
|
},
|
187
210
|
_invalid: {
|
188
211
|
outline: "2px solid",
|
@@ -200,6 +223,14 @@ export const datePickerSlotRecipe = defineSlotRecipe({
|
|
200
223
|
outline: "1px solid",
|
201
224
|
outlineColor: "core.outline",
|
202
225
|
},
|
226
|
+
|
227
|
+
"&[data-active]": {
|
228
|
+
outline: "2px solid",
|
229
|
+
outlineColor: "outline.focus",
|
230
|
+
_hover: {
|
231
|
+
outlineColor: "outline.focus",
|
232
|
+
},
|
233
|
+
},
|
203
234
|
},
|
204
235
|
_invalid: {
|
205
236
|
outline: "2px solid",
|