@vygruppen/spor-react 12.1.0 → 12.1.2

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": "12.1.0",
3
+ "version": "12.1.2",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -35,7 +35,7 @@
35
35
  "react-stately": "^3.31.1",
36
36
  "react-swipeable": "^7.0.1",
37
37
  "usehooks-ts": "^3.1.0",
38
- "@vygruppen/spor-design-tokens": "4.0.4",
38
+ "@vygruppen/spor-design-tokens": "4.0.5",
39
39
  "@vygruppen/spor-icon-react": "4.0.3",
40
40
  "@vygruppen/spor-loader": "0.6.0"
41
41
  },
@@ -7,7 +7,7 @@ import {
7
7
  type RecipeVariantProps,
8
8
  Span,
9
9
  } from "@chakra-ui/react";
10
- import React, { forwardRef, PropsWithChildren } from "react";
10
+ import React, { cloneElement, forwardRef, PropsWithChildren } from "react";
11
11
 
12
12
  import { createTexts, useTranslation } from "../i18n";
13
13
  import { ColorInlineLoader } from "../loader";
@@ -29,7 +29,7 @@ export type ButtonProps = Exclude<
29
29
  /* Display icon to the right */
30
30
  rightIcon?: React.ReactNode;
31
31
  /* "primary" | "secondary" | "tertiary" | "ghost" | "floating". Defaults to primary. */
32
- variant: "primary" | "secondary" | "tertiary" | "ghost" | "floating";
32
+ variant?: "primary" | "secondary" | "tertiary" | "ghost" | "floating";
33
33
  /* "lg" | "md" | "sm" | "xs". Defaults to md. */
34
34
  size?: "lg" | "md" | "sm" | "xs";
35
35
  /* Link to a downloadable resource. */
@@ -65,9 +65,37 @@ export type ButtonProps = Exclude<
65
65
  * @see https://spor.vy.no/components/button
66
66
  */
67
67
 
68
+ const ButtonContent = ({
69
+ leftIcon,
70
+ children,
71
+ rightIcon,
72
+ }: PropsWithChildren<Pick<ButtonProps, "leftIcon" | "rightIcon">>) => (
73
+ <>
74
+ {leftIcon}
75
+ {children}
76
+ {rightIcon && <Span marginLeft="auto">{rightIcon}</Span>}
77
+ </>
78
+ );
79
+
80
+ const LoadingContent = ({
81
+ children,
82
+ loadingText,
83
+ }: PropsWithChildren<Pick<ButtonProps, "size" | "loadingText">>) => (
84
+ <>
85
+ <Flex gap="1" visibility="hidden">
86
+ {children}
87
+ </Flex>
88
+ <Center position="absolute" inset="1px 0">
89
+ <ColorInlineLoader width="80%" marginX={2} marginY={2} />
90
+ {loadingText && <Box>{loadingText}</Box>}
91
+ </Center>
92
+ </>
93
+ );
94
+
95
+ // eslint-disable-next-line react/display-name
68
96
  export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
69
- (props, ref) => {
70
- const {
97
+ (
98
+ {
71
99
  loading,
72
100
  disabled,
73
101
  loadingText,
@@ -78,16 +106,35 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
78
106
  type = "button",
79
107
  children,
80
108
  ...rest
81
- } = props;
82
- const ariaLabel = useCorrectAriaLabel(props);
109
+ },
110
+ ref,
111
+ ) => {
112
+ const { t } = useTranslation();
83
113
 
84
- const buttonContent = (
85
- <>
86
- {leftIcon}
87
- {children}
88
- {rightIcon && <Span marginLeft="auto">{rightIcon}</Span>}
89
- </>
90
- );
114
+ const ariaLabel = loading
115
+ ? String(loadingText ?? t(texts.loadingText))
116
+ : (rest["aria-label"] as string);
117
+
118
+ const renderContent = () => {
119
+ const content = rest.asChild
120
+ ? (children as React.ReactElement).props.children
121
+ : children;
122
+
123
+ if (loading)
124
+ return (
125
+ <LoadingContent size={size} loadingText={loadingText}>
126
+ <ButtonContent leftIcon={leftIcon} rightIcon={rightIcon}>
127
+ {content}
128
+ </ButtonContent>
129
+ </LoadingContent>
130
+ );
131
+
132
+ return (
133
+ <ButtonContent leftIcon={leftIcon} rightIcon={rightIcon}>
134
+ {content}
135
+ </ButtonContent>
136
+ );
137
+ };
91
138
 
92
139
  return (
93
140
  <ChakraButton
@@ -101,51 +148,16 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
101
148
  size={size}
102
149
  {...rest}
103
150
  >
104
- {loading ? (
105
- <>
106
- <Flex gap="1" visibility="hidden">
107
- {buttonContent}
108
- </Flex>
109
- <Center position="absolute" right={0} left={0} top={1} bottom={0}>
110
- <ColorInlineLoader
111
- maxWidth={getLoaderWidth(size)}
112
- width="80%"
113
- marginX={2}
114
- marginY={2}
115
- />
116
- {loadingText && <Box>{loadingText}</Box>}
117
- </Center>
118
- </>
119
- ) : (
120
- buttonContent
121
- )}
151
+ {rest.asChild
152
+ ? cloneElement(children as React.ReactElement, {
153
+ children: renderContent(),
154
+ })
155
+ : renderContent()}
122
156
  </ChakraButton>
123
157
  );
124
158
  },
125
159
  );
126
160
 
127
- function getLoaderWidth(size: ButtonProps["size"]): string {
128
- switch (size) {
129
- case "xs":
130
- return "4rem";
131
- case "sm":
132
- return "4rem";
133
- case "md":
134
- return "5rem";
135
- case "lg":
136
- default:
137
- return "6rem";
138
- }
139
- }
140
-
141
- function useCorrectAriaLabel(props: ButtonProps): string {
142
- const { t } = useTranslation();
143
- if (props.loading) {
144
- return String(props.loadingText) ?? t(texts.loadingText);
145
- }
146
- return props["aria-label"] as string;
147
- }
148
-
149
161
  const texts = createTexts({
150
162
  loadingText: {
151
163
  nb: "Laster…",
@@ -3,11 +3,18 @@ import {
3
3
  Link as ChakraLink,
4
4
  LinkProps as ChakraLinkProps,
5
5
  RecipeVariantProps,
6
+ VisuallyHidden,
6
7
  } from "@chakra-ui/react";
7
8
  import { LinkOutOutline24Icon } from "@vygruppen/spor-icon-react";
8
- import React, { forwardRef, PropsWithChildren } from "react";
9
+ import React, {
10
+ cloneElement,
11
+ forwardRef,
12
+ isValidElement,
13
+ PropsWithChildren,
14
+ } from "react";
15
+
16
+ import { createTexts, useTranslation } from "@/i18n";
9
17
 
10
- import { createTexts, useTranslation } from "..";
11
18
  import { linkRecipe } from "../theme/recipes/link";
12
19
 
13
20
  type linkVariantProps = RecipeVariantProps<typeof linkRecipe>;
@@ -29,17 +36,45 @@ export type LinkProps = Exclude<ChakraLinkProps, "variant"> &
29
36
  * </TextLink>
30
37
  * ```
31
38
  */
39
+ const ExternalIcon = ({ label }: { label: string }) => (
40
+ <>
41
+ <LinkOutOutline24Icon aria-hidden />
42
+ <VisuallyHidden>{label}</VisuallyHidden>
43
+ </>
44
+ );
45
+
46
+ // eslint-disable-next-line react/display-name
32
47
  export const TextLink = forwardRef<HTMLAnchorElement, LinkProps>(
33
- ({ children, ...props }, ref) => {
48
+ ({ children, external, href, ...props }, ref) => {
34
49
  const { t } = useTranslation();
35
- const external =
36
- props.external !== undefined
37
- ? props.external
38
- : Boolean(props.href?.match(/^https?:\/\//));
50
+
51
+ const isExternal =
52
+ external ??
53
+ Boolean(href?.startsWith("http://") || href?.startsWith("https://"));
54
+
55
+ const externalLabel = t ? t(texts.externalLink) : texts.externalLink.en;
56
+
57
+ // If asChild is true, we need to clone the children and add the external icon
58
+ if (props.asChild && isValidElement(children)) {
59
+ return (
60
+ <ChakraLink href={href} {...props} ref={ref}>
61
+ {cloneElement(children as React.ReactElement, {
62
+ ...children.props,
63
+ children: (
64
+ <>
65
+ {children.props.children}
66
+ {isExternal && <ExternalIcon label={externalLabel} />}
67
+ </>
68
+ ),
69
+ })}
70
+ </ChakraLink>
71
+ );
72
+ }
73
+
39
74
  return (
40
- <ChakraLink {...props} ref={ref}>
75
+ <ChakraLink href={href} {...props} ref={ref}>
41
76
  {children}
42
- {external && <LinkOutOutline24Icon aria-hidden />}
77
+ {isExternal && <ExternalIcon label={externalLabel} />}
43
78
  </ChakraLink>
44
79
  );
45
80
  },
@@ -14,6 +14,7 @@ export const buttonRecipe = defineRecipe({
14
14
  transitionDuration: "normal",
15
15
  cursor: "pointer",
16
16
  textWrap: "wrap",
17
+ width: "fit-content",
17
18
  paddingX: 3,
18
19
  paddingY: 1,
19
20
  _disabled: {
@@ -121,4 +122,8 @@ export const buttonRecipe = defineRecipe({
121
122
  },
122
123
  },
123
124
  },
125
+ defaultVariants: {
126
+ variant: "primary",
127
+ size: "md",
128
+ },
124
129
  });