@vygruppen/spor-react 12.13.2 → 12.13.3
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 +1 -1
- package/CHANGELOG.md +7 -0
- package/dist/index.cjs +95 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.mjs +96 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/datepicker/DatePicker.tsx +29 -14
- package/src/datepicker/StyledField.tsx +16 -1
- 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 +4 -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.3",
|
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,9 @@ 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;
|
45
49
|
} & FieldBaseProps;
|
46
50
|
|
47
51
|
/**
|
@@ -97,7 +101,11 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
|
|
97
101
|
const styles = recipe({ variant });
|
98
102
|
const locale = useCurrentLocale();
|
99
103
|
|
104
|
+
const shouldShowCalendar =
|
105
|
+
state.isOpen && !props.isDisabled && !props.noCalendar;
|
106
|
+
|
100
107
|
const onFieldClick = () => {
|
108
|
+
if (props.noCalendar) return;
|
101
109
|
state.setOpen(true);
|
102
110
|
};
|
103
111
|
|
@@ -135,20 +143,29 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
|
|
135
143
|
<PopoverAnchor>
|
136
144
|
<StyledField
|
137
145
|
variant={variant}
|
138
|
-
onClick={onFieldClick}
|
146
|
+
onClick={props.noCalendar ? undefined : onFieldClick}
|
139
147
|
paddingX={3}
|
140
148
|
minHeight={minHeight}
|
141
149
|
isDisabled={props.isDisabled}
|
150
|
+
isActive={props.isActive}
|
151
|
+
overrideBorderColor={props.overrideBorderColor}
|
142
152
|
>
|
143
|
-
|
144
|
-
<
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
153
|
+
{props.noCalendar ? (
|
154
|
+
<Box pr={3} pl={0.5} mr={0.5}>
|
155
|
+
<CalendarOutline24Icon />
|
156
|
+
</Box>
|
157
|
+
) : (
|
158
|
+
<ChakraPopover.Trigger asChild>
|
159
|
+
<CalendarTriggerButton
|
160
|
+
paddingLeft={1}
|
161
|
+
paddingRight={1}
|
162
|
+
variant={variant}
|
163
|
+
ref={ref}
|
164
|
+
{...buttonProps}
|
165
|
+
/>
|
166
|
+
</ChakraPopover.Trigger>
|
167
|
+
)}
|
168
|
+
|
152
169
|
<DateField
|
153
170
|
label={props.label}
|
154
171
|
labelProps={labelProps}
|
@@ -159,10 +176,8 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
|
|
159
176
|
</PopoverAnchor>
|
160
177
|
</Field>
|
161
178
|
|
162
|
-
{
|
163
|
-
<Portal>{popoverContent}</Portal>
|
164
|
-
)}
|
165
|
-
{state.isOpen && !props.isDisabled && !withPortal && popoverContent}
|
179
|
+
{shouldShowCalendar &&
|
180
|
+
(withPortal ? <Portal>{popoverContent}</Portal> : popoverContent)}
|
166
181
|
</ChakraPopover.Root>
|
167
182
|
</Box>
|
168
183
|
</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
|
};
|
@@ -31,6 +40,12 @@ export const StyledField = forwardRef<HTMLDivElement, StyledFieldProps>(
|
|
31
40
|
<Box
|
32
41
|
{...otherProps}
|
33
42
|
css={styles.wrapper}
|
43
|
+
data-active={isActive ? "" : undefined}
|
44
|
+
style={
|
45
|
+
overrideBorderColor
|
46
|
+
? { outlineColor: overrideBorderColor }
|
47
|
+
: undefined
|
48
|
+
}
|
34
49
|
ref={ref}
|
35
50
|
aria-invalid={invalid}
|
36
51
|
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,10 @@ 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
|
+
},
|
31
35
|
},
|
32
36
|
inputLabel: {
|
33
37
|
fontSize: "mobile.xs",
|