@vygruppen/spor-react 9.13.1 → 9.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.
@@ -5,7 +5,7 @@ import {
5
5
  forwardRef,
6
6
  useMultiStyleConfig,
7
7
  } from "@chakra-ui/react";
8
- import React, { useContext, useEffect, useId } from "react";
8
+ import React, { useContext, useEffect, useId, useState } from "react";
9
9
  import { RadioCardGroupContext } from "./RadioCardGroup";
10
10
 
11
11
  /**
@@ -32,13 +32,16 @@ import { RadioCardGroupContext } from "./RadioCardGroup";
32
32
  */
33
33
 
34
34
  export type RadioCardProps = BoxProps & {
35
+ /** The value that will be passed to the `RadioCardGroup`'s `onChange` function if this `RadioCard` is selected.. */
35
36
  value: string;
37
+ /** The content of the RadioCard. */
36
38
  children: React.ReactNode;
39
+ /** Determines if the RadioCard is disabled. */
37
40
  isDisabled?: boolean;
38
41
  };
39
42
 
40
43
  export const RadioCard = forwardRef(
41
- ({ children, value = "base", isDisabled, ...props }: RadioCardProps, ref) => {
44
+ ({ children, value, isDisabled, ...props }: RadioCardProps, ref) => {
42
45
  const context = useContext(RadioCardGroupContext);
43
46
 
44
47
  if (!context) {
@@ -51,57 +54,69 @@ export const RadioCard = forwardRef(
51
54
 
52
55
  const styles = useMultiStyleConfig("RadioCard", { variant });
53
56
 
57
+ const [isKeyboardUser, setKeyboardUser] = useState(false);
58
+ const [isFocused, setFocus] = useState(false);
59
+
54
60
  const isChecked = selectedValue === value;
55
61
 
56
- const radioCardId = `radio-card-${useId()}`;
62
+ useEffect(() => {
63
+ const handleMouseDown = () => setKeyboardUser(false);
64
+ const handleKeyDown = (event: KeyboardEvent) => {
65
+ if (event.key === " ") {
66
+ setFocus(false);
67
+ } else {
68
+ setKeyboardUser(true);
69
+ }
70
+ };
71
+
72
+ window.addEventListener("mousedown", handleMouseDown);
73
+ window.addEventListener("keydown", handleKeyDown);
74
+
75
+ return () => {
76
+ window.removeEventListener("mousedown", handleMouseDown);
77
+ window.removeEventListener("keydown", handleKeyDown);
78
+ };
79
+ }, []);
57
80
 
58
81
  useEffect(() => {
59
- if (isChecked && typeof ref !== "function" && ref?.current) {
60
- ref.current.focus();
82
+ if (isKeyboardUser && isChecked) {
83
+ setFocus(true);
84
+ } else {
85
+ setFocus(false);
61
86
  }
62
- }, [isChecked]);
87
+ }, [isKeyboardUser, isChecked]);
63
88
 
64
- const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
65
- if (event.key === "Enter" || event.key === " ") {
66
- onChange(value);
67
- }
68
- if (
69
- event.key === "ArrowRight" ||
70
- event.key === "ArrowDown" ||
71
- event.key === "ArrowLeft" ||
72
- event.key === "ArrowUp"
73
- ) {
74
- const nextRadioCard = event.currentTarget
75
- .nextElementSibling as HTMLElement;
76
- if (nextRadioCard) {
77
- nextRadioCard.focus();
78
- }
79
- }
80
- };
89
+ const inputId = `radio-card-${useId()}`;
81
90
 
82
91
  return (
83
- <Box as="label" aria-label={String(children)} onKeyDown={handleKeyDown}>
92
+ <Box
93
+ onFocus={() => isKeyboardUser && setFocus(true)}
94
+ onBlur={() => setFocus(false)}
95
+ >
84
96
  <chakra.input
85
97
  type="radio"
86
- id={radioCardId}
87
- ref={ref}
88
- value={value}
98
+ id={inputId}
89
99
  name={name}
100
+ ref={ref}
90
101
  checked={isChecked}
91
102
  onChange={() => onChange(value)}
92
103
  disabled={isDisabled}
93
104
  __css={styles.radioInput}
94
105
  />
95
106
  <Box
96
- {...props}
97
- tabIndex={0}
98
- ref={ref}
99
- role="radio"
107
+ as="label"
108
+ name={name}
109
+ htmlFor={inputId}
100
110
  aria-checked={isChecked}
101
- aria-labelledby={radioCardId}
102
- __css={{ ...styles.container, ...(isChecked && styles.checked) }}
103
111
  data-checked={isChecked}
104
112
  data-disabled={isDisabled}
113
+ {...props}
114
+ __css={{
115
+ ...styles.container,
116
+ ...(isChecked && styles.checked),
117
+ ...(isFocused && !isChecked && styles.focused),
118
+ ...(isChecked && isFocused && styles.focusedChecked),
119
+ }}
105
120
  >
106
121
  {children}
107
122
  </Box>
@@ -1,7 +1,6 @@
1
1
  import { BoxProps, Stack } from "@chakra-ui/react";
2
2
  import React, { useState } from "react";
3
3
  import { FormLabel } from "../input";
4
- import { RadioCard } from "./RadioCard";
5
4
 
6
5
  /**
7
6
  * RadioCardGroupContext is used to pass down the state and handlers to the RadioCard components.
@@ -15,18 +14,26 @@ type RadioGroupContextProps = {
15
14
  onChange: (value: string) => void;
16
15
  variant?: "base" | "floating";
17
16
  defaultValue?: string;
17
+ value?: string;
18
18
  };
19
19
 
20
20
  export const RadioCardGroupContext =
21
21
  React.createContext<RadioGroupContextProps | null>(null);
22
22
 
23
23
  type RadioCardGroupProps = BoxProps & {
24
+ /** A string that will be assigned as the name attribute to all RadioCard components within the group. */
24
25
  name: string;
26
+ /** The RadioCard components that make up the group. */
25
27
  children: React.ReactNode;
28
+ /** Optional. Determines the style of the RadioCard. Can be either "base" or "floating". */
26
29
  variant?: "base" | "floating";
30
+ /** Optional. Determines the direction of the group. Can be either "row" or "column". */
27
31
  direction?: "row" | "column";
32
+ /** Optional. A label for the group. */
28
33
  groupLabel?: string;
34
+ /** Optional. The default value of the RadioCard group. */
29
35
  defaultValue?: string;
36
+ /** Optional. A function that will be called when the selected value changes. The function receives the value of the selected RadioCard. */
30
37
  onChange?: (value: string) => void;
31
38
  };
32
39
 
@@ -59,14 +66,7 @@ export const RadioCardGroup: React.FC<RadioCardGroupProps> = ({
59
66
  defaultValue: defaultValue || "",
60
67
  }}
61
68
  >
62
- <Stack
63
- as="fieldset"
64
- direction={direction}
65
- aria-labelledby={groupLabel || name}
66
- role="radiogroup"
67
- tabIndex={0}
68
- {...props}
69
- >
69
+ <Stack as="fieldset" direction={direction} {...props}>
70
70
  {groupLabel && (
71
71
  <FormLabel as="legend" id={groupLabel}>
72
72
  {groupLabel}
@@ -15,6 +15,7 @@ import React, { useEffect, useState } from "react";
15
15
  import { Button, IconButton } from "../button";
16
16
  import { createTexts, useTranslation } from "../i18n";
17
17
  import { Drawer } from "./Drawer";
18
+ import { DrawerBodyProps } from "./SimpleDrawer";
18
19
 
19
20
  type DrawerPlacement = "top" | "right" | "bottom" | "left";
20
21
 
@@ -33,6 +34,8 @@ type FullScreenDrawerProps = {
33
34
  isOpen: boolean;
34
35
  /** Function that will be called when the drawer closes */
35
36
  onClose: () => void;
37
+ /** Props for drawer body */
38
+ body?: DrawerBodyProps;
36
39
  };
37
40
 
38
41
  export const FullScreenDrawer = ({
@@ -43,6 +46,7 @@ export const FullScreenDrawer = ({
43
46
  rightButton = <DrawerCloseButton />,
44
47
  isOpen,
45
48
  onClose,
49
+ body,
46
50
  }: FullScreenDrawerProps) => {
47
51
  const [isContentBoxScrolled, setContentBoxScrolled] = useState(false);
48
52
 
@@ -75,7 +79,7 @@ export const FullScreenDrawer = ({
75
79
  leftButton={leftButton}
76
80
  rightButton={rightButton}
77
81
  />
78
- <DrawerBody overflow="auto" onScroll={onContentScroll}>
82
+ <DrawerBody overflow="auto" onScroll={onContentScroll} {...body}>
79
83
  {children}
80
84
  </DrawerBody>
81
85
  </DrawerContent>
@@ -8,12 +8,18 @@ import {
8
8
  DrawerOverlay,
9
9
  } from "./Drawer";
10
10
 
11
+ export type DrawerBodyProps = {
12
+ id?: string;
13
+ };
14
+
11
15
  export type SimpleDrawerProps = {
12
16
  children: React.ReactNode;
13
17
  title?: React.ReactNode;
14
18
  placement: "top" | "right" | "bottom" | "left";
15
19
  isOpen: boolean;
16
20
  onClose: () => void;
21
+ /** Props for drawer body */
22
+ body?: DrawerBodyProps;
17
23
  };
18
24
  /** A very basic drawer component that's easy to use
19
25
  *
@@ -29,6 +35,7 @@ export const SimpleDrawer = ({
29
35
  placement,
30
36
  children,
31
37
  title,
38
+ body,
32
39
  ...props
33
40
  }: SimpleDrawerProps) => {
34
41
  return (
@@ -37,7 +44,7 @@ export const SimpleDrawer = ({
37
44
  <DrawerContent>
38
45
  <DrawerCloseButton />
39
46
  {title && <DrawerHeader>{title}</DrawerHeader>}
40
- <DrawerBody>{children}</DrawerBody>
47
+ <DrawerBody {...body}>{children}</DrawerBody>
41
48
  </DrawerContent>
42
49
  </Drawer>
43
50
  );
@@ -1,25 +1,29 @@
1
1
  import { createMultiStyleConfigHelpers } from "@chakra-ui/react";
2
2
  import { baseBackground, baseBorder, baseText } from "../utils/base-utils";
3
3
  import { floatingBackground, floatingBorder } from "../utils/floating-utils";
4
- import { focusVisibleStyles } from "../utils/focus-utils";
5
- import { anatomy, mode } from "@chakra-ui/theme-tools";
4
+ import { anatomy } from "@chakra-ui/theme-tools";
6
5
  import { outlineBorder } from "../utils/outline-utils";
7
6
 
8
- const parts = anatomy("radio-card").parts("container", "checked", "radioInput");
7
+ const parts = anatomy("radio-card").parts(
8
+ "container",
9
+ "checked",
10
+ "radioInput",
11
+ "focused",
12
+ "focusedChecked",
13
+ );
9
14
  const helpers = createMultiStyleConfigHelpers(parts.keys);
10
15
 
11
16
  const config = helpers.defineMultiStyleConfig({
12
17
  baseStyle: (props: any) => ({
13
18
  container: {
14
- border: "none",
15
19
  overflow: "hidden",
16
20
  fontSize: "inherit",
17
21
  display: "block",
18
22
  cursor: "pointer",
19
23
  borderRadius: "sm",
20
- ...focusVisibleStyles(props),
21
24
  transitionProperty: "common",
22
25
  transitionDuration: "fast",
26
+
23
27
  _disabled: {
24
28
  pointerEvents: "none",
25
29
  ...baseBackground("disabled", props),
@@ -53,9 +57,6 @@ const config = helpers.defineMultiStyleConfig({
53
57
  ...baseBackground("active", props),
54
58
  ...baseBorder("active", props),
55
59
  },
56
- _focus: {
57
- ...outlineBorder("focus", props),
58
- },
59
60
  },
60
61
  checked: {
61
62
  _hover: {
@@ -65,11 +66,22 @@ const config = helpers.defineMultiStyleConfig({
65
66
  ...baseBackground("active", props),
66
67
  ...baseBorder("active", props),
67
68
  },
68
- _focus: {
69
- outline: "4px solid",
70
- outlineStyle: "double",
71
- ...outlineBorder("focus", props),
72
- outlineOffset: "-1px",
69
+ },
70
+ focusedChecked: {
71
+ outline: "4px solid",
72
+ outlineStyle: "double",
73
+ ...outlineBorder("focus", props),
74
+ outlineOffset: "-1px",
75
+ },
76
+ focused: {
77
+ outline: "2px solid",
78
+ ...outlineBorder("focus", props),
79
+ outlineOffset: "1px",
80
+ boxShadow: `inset 0 0 0 1px rgba(0, 0, 0, 0.40)`,
81
+ _hover: {
82
+ ...baseBorder("hover", props),
83
+ boxShadow: "none",
84
+ outlineOffset: "0px",
73
85
  },
74
86
  },
75
87
  }),
@@ -87,9 +99,6 @@ const config = helpers.defineMultiStyleConfig({
87
99
  ...floatingBackground("active", props),
88
100
  ...floatingBorder("active", props),
89
101
  },
90
- _focus: {
91
- ...outlineBorder("focus", props),
92
- },
93
102
  },
94
103
  checked: {
95
104
  _hover: {
@@ -100,11 +109,22 @@ const config = helpers.defineMultiStyleConfig({
100
109
  ...floatingBackground("active", props),
101
110
  ...floatingBorder("active", props),
102
111
  },
103
- _focus: {
104
- outline: "4px solid",
105
- outlineStyle: "double",
106
- ...outlineBorder("focus", props),
107
- outlineOffset: "-1px",
112
+ },
113
+ focusedChecked: {
114
+ outline: "4px solid",
115
+ outlineStyle: "double",
116
+ ...outlineBorder("focus", props),
117
+ outlineOffset: "-1px",
118
+ },
119
+ focused: {
120
+ outline: "2px solid",
121
+ ...outlineBorder("focus", props),
122
+ outlineOffset: "1px",
123
+ boxShadow: `inset 0 0 0 1px rgba(0, 0, 0, 0.10)`,
124
+ _hover: {
125
+ ...floatingBorder("hover", props),
126
+ boxShadow: "md",
127
+ outlineOffset: "0px",
108
128
  },
109
129
  },
110
130
  }),
@@ -63,4 +63,7 @@ export const fontFaces = `
63
63
  font-weight: 400;
64
64
  font-display: swap
65
65
  }
66
+ body {
67
+ font-family: "Vy Sans", sans-serif;
68
+ }
66
69
  `;