@vygruppen/spor-react 12.1.1 → 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/.turbo/turbo-build.log +11 -11
- package/.turbo/turbo-typegen.log +1 -1
- package/CHANGELOG.md +6 -0
- package/dist/index.d.mts +3 -39
- package/dist/index.d.ts +3 -39
- package/dist/index.js +56 -48
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +60 -52
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/button/Button.tsx +65 -53
- package/src/link/TextLink.tsx +44 -9
- package/src/theme/recipes/button.ts +5 -0
package/package.json
CHANGED
package/src/button/Button.tsx
CHANGED
@@ -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
|
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
|
-
(
|
70
|
-
|
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
|
-
}
|
82
|
-
|
109
|
+
},
|
110
|
+
ref,
|
111
|
+
) => {
|
112
|
+
const { t } = useTranslation();
|
83
113
|
|
84
|
-
const
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
{
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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…",
|
package/src/link/TextLink.tsx
CHANGED
@@ -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, {
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
{
|
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
|
});
|