@vygruppen/spor-react 10.1.0 → 10.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vygruppen/spor-react",
3
- "version": "10.1.0",
3
+ "version": "10.3.0",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -10,7 +10,7 @@ import {
10
10
  } from "@chakra-ui/react";
11
11
  import React, { useId } from "react";
12
12
 
13
- export type InputProps = Omit<ChakraInputProps, "variant" | "size"> & {
13
+ export type InputProps = Omit<ChakraInputProps, "size"> & {
14
14
  /** The input's label */
15
15
  label: string;
16
16
  /** Icon that shows up to the left */
@@ -32,6 +32,12 @@ export type InputProps = Omit<ChakraInputProps, "variant" | "size"> & {
32
32
  * ```tsx
33
33
  * <Input label="E-mail" leftIcon={<EmailOutline24Icon />} />
34
34
  * ```
35
+ *
36
+ * Input has two variants base, and floating.
37
+ *
38
+ * ```tsx
39
+ * <Input label="E-mail" leftIcon={<EmailOutline24Icon />} variant="floating" />
40
+ * ```
35
41
  */
36
42
  export const Input = forwardRef<InputProps, "input">(
37
43
  ({ label, leftIcon, rightIcon, id, size, ...props }, ref) => {
@@ -4,7 +4,7 @@ import {
4
4
  useFormControl,
5
5
  useMultiStyleConfig,
6
6
  } from "@chakra-ui/react";
7
- import React from "react";
7
+ import React, { useRef } from "react";
8
8
  import {
9
9
  Box,
10
10
  BoxProps,
@@ -35,6 +35,8 @@ type NumericStepperProps = {
35
35
  stepSize?: number;
36
36
  /** Whether to show the number input when value is zero */
37
37
  showZero?: boolean;
38
+ /** Name added to the aria-label of subtract and add buttons. */
39
+ ariaLabelContext?: { singular: string; plural: string };
38
40
  } & Omit<BoxProps, "onChange">;
39
41
  /** A simple stepper component for integer values
40
42
  *
@@ -72,8 +74,10 @@ export function NumericStepper({
72
74
  withInput = true,
73
75
  stepSize = 1,
74
76
  showZero = false,
77
+ ariaLabelContext = { singular: "", plural: "" },
75
78
  ...boxProps
76
79
  }: NumericStepperProps) {
80
+ const addButtonRef = useRef<HTMLButtonElement>(null);
77
81
  const { t } = useTranslation();
78
82
  const styles = useMultiStyleConfig("NumericStepper", {});
79
83
  const [value, onChange] = useControllableState<number>({
@@ -84,12 +88,26 @@ export function NumericStepper({
84
88
  const formControlProps = useFormControl({ id: idProp, isDisabled });
85
89
  const clampedStepSize = Math.max(Math.min(stepSize, 10), 1);
86
90
 
91
+ const focusOnAddButton = () => {
92
+ addButtonRef.current?.focus();
93
+ };
94
+
87
95
  return (
88
96
  <Flex __css={styles.container} {...boxProps}>
89
97
  <VerySmallButton
90
98
  icon={<SubtractIcon stepLabel={clampedStepSize} />}
91
- aria-label={t(texts.decrementButtonAriaLabel(clampedStepSize))}
92
- onClick={() => onChange(Math.max(value - clampedStepSize, minValue))}
99
+ aria-label={t(
100
+ texts.decrementButtonAriaLabel(
101
+ clampedStepSize,
102
+ stepSize == 1 ? ariaLabelContext.singular : ariaLabelContext.plural,
103
+ ),
104
+ )}
105
+ onClick={() => {
106
+ onChange(Math.max(value - clampedStepSize, minValue));
107
+ if (Math.max(value - clampedStepSize, minValue) <= minValue) {
108
+ focusOnAddButton();
109
+ }
110
+ }}
93
111
  visibility={value <= minValue ? "hidden" : "visible"}
94
112
  isDisabled={formControlProps.disabled}
95
113
  id={value <= minValue ? undefined : formControlProps.id}
@@ -107,27 +125,48 @@ export function NumericStepper({
107
125
  width={`${Math.max(value.toString().length + 1, 3)}ch`}
108
126
  visibility={!showZero && value === 0 ? "hidden" : "visible"}
109
127
  aria-live="assertive"
110
- aria-label={value.toString()}
128
+ aria-label={
129
+ ariaLabelContext.plural !== ""
130
+ ? t(texts.currentNumberAriaLabel(ariaLabelContext.plural))
131
+ : ""
132
+ }
111
133
  onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
112
134
  const numericInput = Number(e.target.value);
113
135
  if (Number.isNaN(numericInput)) {
114
136
  return;
115
137
  }
116
138
  onChange(Math.max(Math.min(numericInput, maxValue), minValue));
139
+ if (
140
+ !showZero &&
141
+ Math.max(Math.min(numericInput, maxValue), minValue) === 0
142
+ ) {
143
+ focusOnAddButton();
144
+ }
117
145
  }}
118
146
  />
119
147
  ) : (
120
148
  <chakra.text
121
149
  sx={styles.text}
122
150
  visibility={!showZero && value === 0 ? "hidden" : "visible"}
123
- aria-label={value.toString()}
151
+ aria-live="assertive"
152
+ aria-label={
153
+ ariaLabelContext.plural !== ""
154
+ ? t(texts.currentNumberAriaLabel(ariaLabelContext.plural))
155
+ : ""
156
+ }
124
157
  >
125
158
  {value}
126
159
  </chakra.text>
127
160
  )}
128
161
  <VerySmallButton
162
+ ref={addButtonRef}
129
163
  icon={<AddIcon stepLabel={clampedStepSize} />}
130
- aria-label={t(texts.incrementButtonAriaLabel(clampedStepSize))}
164
+ aria-label={t(
165
+ texts.incrementButtonAriaLabel(
166
+ clampedStepSize,
167
+ stepSize == 1 ? ariaLabelContext.singular : ariaLabelContext.plural,
168
+ ),
169
+ )}
131
170
  onClick={() => onChange(Math.min(value + clampedStepSize, maxValue))}
132
171
  visibility={value >= maxValue ? "hidden" : "visible"}
133
172
  isDisabled={formControlProps.disabled}
@@ -152,12 +191,18 @@ type VerySmallButtonProps = {
152
191
  id?: string;
153
192
  };
154
193
  /** Internal override for extra small icon buttons */
155
- const VerySmallButton = (props: VerySmallButtonProps) => {
194
+ const VerySmallButton = React.forwardRef((props: VerySmallButtonProps, ref) => {
156
195
  const styles = useMultiStyleConfig("NumericStepper", {});
157
196
  return (
158
- <IconButton variant="primary" size="xs" sx={styles.button} {...props} />
197
+ <IconButton
198
+ variant="primary"
199
+ size="xs"
200
+ sx={styles.button}
201
+ ref={ref}
202
+ {...props}
203
+ />
159
204
  );
160
- };
205
+ });
161
206
 
162
207
  type IconPropTypes = BoxProps & { stepLabel: number };
163
208
 
@@ -221,20 +266,28 @@ const AddIcon = ({ stepLabel, ...props }: IconPropTypes) => (
221
266
  );
222
267
 
223
268
  const texts = createTexts({
224
- decrementButtonAriaLabel(stepSize) {
269
+ currentNumberAriaLabel(ariaContext) {
270
+ return {
271
+ nb: `Valgt antall ${ariaContext}`,
272
+ en: `Chosen number of ${ariaContext}`,
273
+ nn: `Valgt antall ${ariaContext}`,
274
+ sv: `Valgt antall ${ariaContext}`,
275
+ };
276
+ },
277
+ decrementButtonAriaLabel(stepSize, ariaContext) {
225
278
  return {
226
- nb: `Trekk fra ${stepSize}`,
227
- en: `Subtract ${stepSize}`,
228
- nn: `Trekk frå ${stepSize}`,
229
- sv: `Subtrahera ${stepSize}`,
279
+ nb: `Trekk fra ${stepSize} ${ariaContext}`,
280
+ en: `Subtract ${stepSize} ${ariaContext}`,
281
+ nn: `Trekk frå ${stepSize} ${ariaContext}`,
282
+ sv: `Subtrahera ${stepSize} ${ariaContext}`,
230
283
  };
231
284
  },
232
- incrementButtonAriaLabel(stepSize) {
285
+ incrementButtonAriaLabel(stepSize, ariaContext) {
233
286
  return {
234
- nb: `Legg til ${stepSize}`,
235
- en: `Add ${stepSize}`,
236
- nn: `Legg til ${stepSize}`,
237
- sv: `Lägg till ${stepSize}`,
287
+ nb: `Legg til ${stepSize} ${ariaContext}`,
288
+ en: `Add ${stepSize} ${ariaContext}`,
289
+ nn: `Legg til ${stepSize} ${ariaContext}`,
290
+ sv: `Lägg till ${stepSize} ${ariaContext}`,
238
291
  };
239
292
  },
240
293
  });
@@ -26,6 +26,6 @@ export const ModalHeader = forwardRef<ModalHeaderProps, "header">(
26
26
  ? "center"
27
27
  : ("left" as ChakraModalHeaderProps["textAlign"]),
28
28
  };
29
- return <ChakraModalHeader {...props} ref={ref} {...styles} />;
29
+ return <ChakraModalHeader as={"h1"} {...props} ref={ref} {...styles} />;
30
30
  },
31
31
  );
@@ -3,6 +3,7 @@ import { createMultiStyleConfigHelpers } from "@chakra-ui/react";
3
3
  import { baseBackground, baseBorder, baseText } from "../utils/base-utils";
4
4
  import { focusVisibleStyles } from "../utils/focus-utils";
5
5
  import { surface } from "../utils/surface-utils";
6
+ import { floatingBackground, floatingBorder } from "../utils/floating-utils";
6
7
 
7
8
  const helpers = createMultiStyleConfigHelpers(parts.keys);
8
9
 
@@ -20,9 +21,6 @@ const config = helpers.defineMultiStyleConfig({
20
21
  paddingX: 3,
21
22
  height: 8,
22
23
  fontSize: "mobile.md",
23
- ...baseBackground("default", props),
24
-
25
- ...baseBorder("default", props),
26
24
  _hover: {
27
25
  ...baseBorder("hover", props),
28
26
  },
@@ -73,6 +71,37 @@ const config = helpers.defineMultiStyleConfig({
73
71
  },
74
72
  },
75
73
  }),
74
+ variants: {
75
+ base: (props) => ({
76
+ field: {
77
+ ...baseBackground("default", props),
78
+ ...baseBorder("default", props),
79
+ },
80
+ }),
81
+ floating: (props) => ({
82
+ field: {
83
+ boxShadow: "sm",
84
+ ...floatingBackground("default", props),
85
+ ...floatingBorder("default", props),
86
+
87
+ _hover: {
88
+ ...floatingBorder("hover", props),
89
+ ...floatingBackground("hover", props),
90
+ },
91
+ _active: {
92
+ ...floatingBorder("active", props),
93
+ ...floatingBackground("active", props),
94
+ },
95
+ _selected: {
96
+ ...floatingBorder("selected", props),
97
+ ...floatingBackground("selected", props),
98
+ },
99
+ },
100
+ }),
101
+ },
102
+ defaultProps: {
103
+ variant: "base",
104
+ },
76
105
  });
77
106
 
78
107
  export default config;