@navikt/ds-react 7.23.2 → 7.25.1
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/cjs/chat/Chat.js +4 -3
- package/cjs/chat/Chat.js.map +1 -1
- package/cjs/form/file-upload/parts/item/Item.d.ts +1 -1
- package/cjs/form/file-upload/parts/item/Item.js +10 -2
- package/cjs/form/file-upload/parts/item/Item.js.map +1 -1
- package/cjs/form/search/Search.js +5 -3
- package/cjs/form/search/Search.js.map +1 -1
- package/cjs/form/switch/Switch.js +23 -2
- package/cjs/form/switch/Switch.js.map +1 -1
- package/cjs/index.d.ts +1 -0
- package/cjs/index.js +3 -1
- package/cjs/index.js.map +1 -1
- package/cjs/link-card/LinkCard.d.ts +126 -0
- package/cjs/link-card/LinkCard.js +141 -0
- package/cjs/link-card/LinkCard.js.map +1 -0
- package/cjs/link-card/index.d.ts +2 -0
- package/cjs/link-card/index.js +13 -0
- package/cjs/link-card/index.js.map +1 -0
- package/cjs/link-panel/LinkPanel.d.ts +3 -11
- package/cjs/link-panel/LinkPanel.js +3 -11
- package/cjs/link-panel/LinkPanel.js.map +1 -1
- package/cjs/modal/ModalUtils.d.ts +2 -1
- package/cjs/modal/ModalUtils.js +21 -12
- package/cjs/modal/ModalUtils.js.map +1 -1
- package/cjs/table/Table.d.ts +5 -0
- package/cjs/table/Table.js +2 -1
- package/cjs/table/Table.js.map +1 -1
- package/cjs/util/link-anchor/LinkAnchor.d.ts +26 -0
- package/cjs/util/link-anchor/LinkAnchor.js +110 -0
- package/cjs/util/link-anchor/LinkAnchor.js.map +1 -0
- package/cjs/util/link-anchor/index.d.ts +2 -0
- package/cjs/util/link-anchor/index.js +9 -0
- package/cjs/util/link-anchor/index.js.map +1 -0
- package/esm/chat/Chat.js +4 -3
- package/esm/chat/Chat.js.map +1 -1
- package/esm/form/file-upload/parts/item/Item.d.ts +1 -1
- package/esm/form/file-upload/parts/item/Item.js +10 -2
- package/esm/form/file-upload/parts/item/Item.js.map +1 -1
- package/esm/form/search/Search.js +5 -3
- package/esm/form/search/Search.js.map +1 -1
- package/esm/form/switch/Switch.js +23 -2
- package/esm/form/switch/Switch.js.map +1 -1
- package/esm/index.d.ts +1 -0
- package/esm/index.js +1 -0
- package/esm/index.js.map +1 -1
- package/esm/link-card/LinkCard.d.ts +126 -0
- package/esm/link-card/LinkCard.js +105 -0
- package/esm/link-card/LinkCard.js.map +1 -0
- package/esm/link-card/index.d.ts +2 -0
- package/esm/link-card/index.js +3 -0
- package/esm/link-card/index.js.map +1 -0
- package/esm/link-panel/LinkPanel.d.ts +3 -11
- package/esm/link-panel/LinkPanel.js +3 -11
- package/esm/link-panel/LinkPanel.js.map +1 -1
- package/esm/modal/ModalUtils.d.ts +2 -1
- package/esm/modal/ModalUtils.js +20 -11
- package/esm/modal/ModalUtils.js.map +1 -1
- package/esm/table/Table.d.ts +5 -0
- package/esm/table/Table.js +2 -1
- package/esm/table/Table.js.map +1 -1
- package/esm/util/link-anchor/LinkAnchor.d.ts +26 -0
- package/esm/util/link-anchor/LinkAnchor.js +72 -0
- package/esm/util/link-anchor/LinkAnchor.js.map +1 -0
- package/esm/util/link-anchor/index.d.ts +2 -0
- package/esm/util/link-anchor/index.js +3 -0
- package/esm/util/link-anchor/index.js.map +1 -0
- package/package.json +13 -3
- package/src/chat/Chat.tsx +15 -13
- package/src/form/file-upload/parts/item/Item.tsx +20 -6
- package/src/form/search/Search.tsx +5 -1
- package/src/form/switch/Switch.tsx +79 -26
- package/src/index.ts +10 -0
- package/src/link-card/LinkCard.tsx +317 -0
- package/src/link-card/index.tsx +20 -0
- package/src/link-panel/LinkPanel.tsx +3 -11
- package/src/modal/Modal.test.tsx +12 -4
- package/src/modal/ModalUtils.ts +24 -7
- package/src/table/Table.tsx +7 -0
- package/src/util/link-anchor/LinkAnchor.tsx +153 -0
- package/src/util/link-anchor/index.tsx +7 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { MouseEvent, forwardRef } from "react";
|
|
2
|
+
import { Spacer } from "../../../../layout/stack";
|
|
2
3
|
import { useRenameCSS } from "../../../../theme/Theme";
|
|
3
4
|
import { BodyShort, ErrorMessage } from "../../../../typography";
|
|
4
5
|
import { OverridableComponent } from "../../../../util";
|
|
@@ -51,11 +52,13 @@ export interface FileUploadItemProps
|
|
|
51
52
|
/**
|
|
52
53
|
* Props for the action button.
|
|
53
54
|
*/
|
|
54
|
-
button?:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
button?:
|
|
56
|
+
| {
|
|
57
|
+
action: "delete" | "retry";
|
|
58
|
+
onClick: (event: MouseEvent<HTMLButtonElement>) => void;
|
|
59
|
+
id?: string;
|
|
60
|
+
}
|
|
61
|
+
| React.ReactNode;
|
|
59
62
|
/**
|
|
60
63
|
* i18n-API for customizing texts and labels
|
|
61
64
|
*/
|
|
@@ -100,6 +103,9 @@ export const Item: OverridableComponent<FileUploadItemProps, HTMLDivElement> =
|
|
|
100
103
|
return description ?? formatFileSize(file);
|
|
101
104
|
}
|
|
102
105
|
|
|
106
|
+
const renderButton = status === "idle" && button;
|
|
107
|
+
const renderCustomButton = isCustomButton(button);
|
|
108
|
+
|
|
103
109
|
return (
|
|
104
110
|
<Component
|
|
105
111
|
ref={ref}
|
|
@@ -131,8 +137,9 @@ export const Item: OverridableComponent<FileUploadItemProps, HTMLDivElement> =
|
|
|
131
137
|
)}
|
|
132
138
|
</div>
|
|
133
139
|
</div>
|
|
140
|
+
{renderButton && <Spacer />}
|
|
134
141
|
|
|
135
|
-
{
|
|
142
|
+
{renderButton && !renderCustomButton && (
|
|
136
143
|
<ItemButton
|
|
137
144
|
{...button}
|
|
138
145
|
title={translate(
|
|
@@ -142,10 +149,17 @@ export const Item: OverridableComponent<FileUploadItemProps, HTMLDivElement> =
|
|
|
142
149
|
)}
|
|
143
150
|
/>
|
|
144
151
|
)}
|
|
152
|
+
{renderButton && renderCustomButton && button}
|
|
145
153
|
</div>
|
|
146
154
|
</Component>
|
|
147
155
|
);
|
|
148
156
|
},
|
|
149
157
|
);
|
|
150
158
|
|
|
159
|
+
function isCustomButton(
|
|
160
|
+
button: FileUploadItemProps["button"],
|
|
161
|
+
): button is React.ReactNode {
|
|
162
|
+
return React.isValidElement(button);
|
|
163
|
+
}
|
|
164
|
+
|
|
151
165
|
export default Item;
|
|
@@ -121,6 +121,7 @@ export const Search = forwardRef<HTMLInputElement, SearchProps>(
|
|
|
121
121
|
onChange,
|
|
122
122
|
onSearchClick,
|
|
123
123
|
htmlSize,
|
|
124
|
+
"data-color": dataColor,
|
|
124
125
|
...rest
|
|
125
126
|
} = props;
|
|
126
127
|
|
|
@@ -170,6 +171,7 @@ export const Search = forwardRef<HTMLInputElement, SearchProps>(
|
|
|
170
171
|
"navds-search--with-size": htmlSize,
|
|
171
172
|
},
|
|
172
173
|
)}
|
|
174
|
+
data-color={dataColor}
|
|
173
175
|
>
|
|
174
176
|
<Label
|
|
175
177
|
htmlFor={inputProps.id}
|
|
@@ -233,7 +235,9 @@ export const Search = forwardRef<HTMLInputElement, SearchProps>(
|
|
|
233
235
|
handleClick,
|
|
234
236
|
}}
|
|
235
237
|
>
|
|
236
|
-
{children
|
|
238
|
+
{children
|
|
239
|
+
? children
|
|
240
|
+
: variant !== "simple" && <SearchButton data-color={dataColor} />}
|
|
237
241
|
</SearchContext.Provider>
|
|
238
242
|
</div>
|
|
239
243
|
<div
|
|
@@ -117,32 +117,7 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
|
|
|
117
117
|
/>
|
|
118
118
|
<span className={cn("navds-switch__track")}>
|
|
119
119
|
<span className={cn("navds-switch__thumb")}>
|
|
120
|
-
{loading
|
|
121
|
-
<Loader
|
|
122
|
-
size="xsmall"
|
|
123
|
-
aria-live="polite"
|
|
124
|
-
variant={checked ? "interaction" : "inverted"}
|
|
125
|
-
/>
|
|
126
|
-
) : (
|
|
127
|
-
<svg
|
|
128
|
-
width="12"
|
|
129
|
-
height="10"
|
|
130
|
-
viewBox="0 0 12 10"
|
|
131
|
-
fill="none"
|
|
132
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
133
|
-
focusable={false}
|
|
134
|
-
role="img"
|
|
135
|
-
aria-hidden
|
|
136
|
-
className={cn("navds-switch__checkmark")}
|
|
137
|
-
>
|
|
138
|
-
<path
|
|
139
|
-
fillRule="evenodd"
|
|
140
|
-
clipRule="evenodd"
|
|
141
|
-
d="M11.2674 0.647802C11.8762 1.20971 11.9141 2.1587 11.3522 2.76743L5.35221 9.26743C5.07531 9.56739 4.68813 9.74155 4.27998 9.74971C3.87184 9.75787 3.478 9.59933 3.18934 9.31067L0.68934 6.81067C0.103553 6.22488 0.103553 5.27513 0.68934 4.68935C1.27513 4.10356 2.22487 4.10356 2.81066 4.68935L4.20673 6.08541L9.14779 0.732587C9.7097 0.123856 10.6587 0.0858967 11.2674 0.647802Z"
|
|
142
|
-
fill="currentColor"
|
|
143
|
-
/>
|
|
144
|
-
</svg>
|
|
145
|
-
)}
|
|
120
|
+
<SwitchIcon size={size} checked={checked} loading={loading} />
|
|
146
121
|
</span>
|
|
147
122
|
</span>
|
|
148
123
|
<label
|
|
@@ -181,4 +156,82 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
|
|
|
181
156
|
},
|
|
182
157
|
);
|
|
183
158
|
|
|
159
|
+
const SwitchIcon = ({
|
|
160
|
+
size,
|
|
161
|
+
checked,
|
|
162
|
+
loading,
|
|
163
|
+
}: {
|
|
164
|
+
size: SwitchProps["size"];
|
|
165
|
+
checked: SwitchProps["checked"];
|
|
166
|
+
loading: SwitchProps["loading"];
|
|
167
|
+
}) => {
|
|
168
|
+
if (loading) {
|
|
169
|
+
let baseSize = 16;
|
|
170
|
+
|
|
171
|
+
if (size === "small") {
|
|
172
|
+
baseSize = 12;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (checked) {
|
|
176
|
+
baseSize += 2;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<Loader
|
|
181
|
+
size="small"
|
|
182
|
+
aria-live="polite"
|
|
183
|
+
variant={checked ? "interaction" : "inverted"}
|
|
184
|
+
width={`${baseSize / 16}rem`}
|
|
185
|
+
height={`${baseSize / 16}rem`}
|
|
186
|
+
/>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!checked) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (size === "small") {
|
|
195
|
+
return (
|
|
196
|
+
<svg
|
|
197
|
+
width="11"
|
|
198
|
+
height="8"
|
|
199
|
+
viewBox="0 0 11 8"
|
|
200
|
+
fill="none"
|
|
201
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
202
|
+
focusable={false}
|
|
203
|
+
role="img"
|
|
204
|
+
aria-hidden
|
|
205
|
+
>
|
|
206
|
+
<path
|
|
207
|
+
fillRule="evenodd"
|
|
208
|
+
clipRule="evenodd"
|
|
209
|
+
d="M9.62013 0.530226C10.1194 0.952686 10.1817 1.6999 9.7592 2.19917L5.4171 7.33075C5.20318 7.58356 4.89318 7.73525 4.5623 7.74901C4.23142 7.76277 3.90989 7.63735 3.67572 7.40318L1.3073 5.03476C0.844833 4.5723 0.844833 3.8225 1.3073 3.36003C1.76976 2.89757 2.51956 2.89757 2.98202 3.36003L4.4404 4.81841L7.95118 0.669304C8.37364 0.170033 9.12085 0.107765 9.62013 0.530226Z"
|
|
210
|
+
fill="currentColor"
|
|
211
|
+
/>
|
|
212
|
+
</svg>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<svg
|
|
218
|
+
width="12"
|
|
219
|
+
height="10"
|
|
220
|
+
viewBox="0 0 12 10"
|
|
221
|
+
fill="none"
|
|
222
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
223
|
+
focusable={false}
|
|
224
|
+
role="img"
|
|
225
|
+
aria-hidden
|
|
226
|
+
>
|
|
227
|
+
<path
|
|
228
|
+
fillRule="evenodd"
|
|
229
|
+
clipRule="evenodd"
|
|
230
|
+
d="M11.2674 0.647802C11.8762 1.20971 11.9141 2.1587 11.3522 2.76743L5.35221 9.26743C5.07531 9.56739 4.68813 9.74155 4.27998 9.74971C3.87184 9.75787 3.478 9.59933 3.18934 9.31067L0.68934 6.81067C0.103553 6.22488 0.103553 5.27513 0.68934 4.68935C1.27513 4.10356 2.22487 4.10356 2.81066 4.68935L4.20673 6.08541L9.14779 0.732587C9.7097 0.123856 10.6587 0.0858967 11.2674 0.647802Z"
|
|
231
|
+
fill="currentColor"
|
|
232
|
+
/>
|
|
233
|
+
</svg>
|
|
234
|
+
);
|
|
235
|
+
};
|
|
236
|
+
|
|
184
237
|
export default Switch;
|
package/src/index.ts
CHANGED
|
@@ -150,6 +150,16 @@ export { Select, type SelectProps } from "./form/select";
|
|
|
150
150
|
export { Switch, type SwitchProps } from "./form/switch";
|
|
151
151
|
export { Textarea, type TextareaProps } from "./form/textarea";
|
|
152
152
|
export { TextField, type TextFieldProps } from "./form/textfield";
|
|
153
|
+
export {
|
|
154
|
+
LinkCard,
|
|
155
|
+
type LinkCardProps,
|
|
156
|
+
type LinkCardTitleProps,
|
|
157
|
+
type LinkCardDescriptionProps,
|
|
158
|
+
type LinkCardFooterProps,
|
|
159
|
+
type LinkCardAnchorProps,
|
|
160
|
+
type LinkCardIconProps,
|
|
161
|
+
type LinkCardImageProps,
|
|
162
|
+
} from "./link-card";
|
|
153
163
|
|
|
154
164
|
/**
|
|
155
165
|
* Theming
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import React, { HTMLAttributes, forwardRef } from "react";
|
|
2
|
+
import { useRenameCSS } from "../theme/Theme";
|
|
3
|
+
import { BodyLong, Heading } from "../typography";
|
|
4
|
+
import { createContext } from "../util/create-context";
|
|
5
|
+
import {
|
|
6
|
+
LinkAnchor,
|
|
7
|
+
LinkAnchorArrow,
|
|
8
|
+
LinkAnchorOverlay,
|
|
9
|
+
LinkAnchorProps,
|
|
10
|
+
} from "../util/link-anchor";
|
|
11
|
+
|
|
12
|
+
/* ------------------------------ LinkCard Root ----------------------------- */
|
|
13
|
+
interface LinkCardProps extends HTMLAttributes<HTMLDivElement> {
|
|
14
|
+
/**
|
|
15
|
+
* @default true
|
|
16
|
+
*/
|
|
17
|
+
arrow?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Changes padding and typo sizes.
|
|
20
|
+
* @default "medium"
|
|
21
|
+
*/
|
|
22
|
+
size?: "small" | "medium";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type LinkCardContextProps = {
|
|
26
|
+
size: LinkCardProps["size"];
|
|
27
|
+
arrow: LinkCardProps["arrow"];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const [LinkCardContextProvider, useLinkCardContext] =
|
|
31
|
+
createContext<LinkCardContextProps>({
|
|
32
|
+
name: "LinkCardContextProvider",
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
interface LinkCardComponent
|
|
36
|
+
extends React.ForwardRefExoticComponent<
|
|
37
|
+
LinkCardProps & React.RefAttributes<HTMLDivElement>
|
|
38
|
+
> {
|
|
39
|
+
/**
|
|
40
|
+
* @see 🏷️ {@link LinkCardTitleProps}
|
|
41
|
+
*/
|
|
42
|
+
Title: typeof LinkCardTitle;
|
|
43
|
+
/**
|
|
44
|
+
* @see 🏷️ {@link LinkCardAnchorProps}
|
|
45
|
+
*/
|
|
46
|
+
Anchor: typeof LinkCardAnchor;
|
|
47
|
+
/**
|
|
48
|
+
* @see 🏷️ {@link LinkCardDescriptionProps}
|
|
49
|
+
*/
|
|
50
|
+
Description: typeof LinkCardDescription;
|
|
51
|
+
/**
|
|
52
|
+
* @see 🏷️ {@link LinkCardFooterProps}
|
|
53
|
+
*/
|
|
54
|
+
Footer: typeof LinkCardFooter;
|
|
55
|
+
/**
|
|
56
|
+
* @see 🏷️ {@link LinkCardIconProps}
|
|
57
|
+
*/
|
|
58
|
+
Icon: typeof LinkCardIcon;
|
|
59
|
+
/**
|
|
60
|
+
* @see 🏷️ {@link LinkCardImageProps}
|
|
61
|
+
*/
|
|
62
|
+
Image: typeof LinkCardImage;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Accessible clickable card as a link.
|
|
67
|
+
*
|
|
68
|
+
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/linkcard)
|
|
69
|
+
* @see 🏷️ {@link LinkCardProps}
|
|
70
|
+
*
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```tsx
|
|
74
|
+
* <LinkCard>
|
|
75
|
+
* <LinkCard.Icon>
|
|
76
|
+
* <IconOrPictogram />
|
|
77
|
+
* </LinkCard.Icon>
|
|
78
|
+
* <LinkCard.Title>
|
|
79
|
+
* <LinkCard.Anchor href="/href">
|
|
80
|
+
* LinkCard title
|
|
81
|
+
* </LinkCard.Anchor>
|
|
82
|
+
* </LinkCard.Title>
|
|
83
|
+
* <LinkCard.Description>
|
|
84
|
+
* This is a description of the link card.
|
|
85
|
+
* </LinkCard.Description>
|
|
86
|
+
* <LinkCard.Footer>Footer content</LinkCard.Footer>
|
|
87
|
+
* </LinkCard>
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export const LinkCard = forwardRef<HTMLDivElement, LinkCardProps>(
|
|
91
|
+
(
|
|
92
|
+
{
|
|
93
|
+
children,
|
|
94
|
+
className,
|
|
95
|
+
arrow = true,
|
|
96
|
+
size = "medium",
|
|
97
|
+
...restProps
|
|
98
|
+
}: LinkCardProps,
|
|
99
|
+
forwardedRef,
|
|
100
|
+
) => {
|
|
101
|
+
const { cn } = useRenameCSS();
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<LinkCardContextProvider size={size} arrow={arrow}>
|
|
105
|
+
<LinkAnchorOverlay asChild>
|
|
106
|
+
<BodyLong
|
|
107
|
+
as="div"
|
|
108
|
+
size={size}
|
|
109
|
+
ref={forwardedRef}
|
|
110
|
+
data-color="neutral"
|
|
111
|
+
className={cn(
|
|
112
|
+
"navds-link-card",
|
|
113
|
+
className,
|
|
114
|
+
`navds-link-card--${size}`,
|
|
115
|
+
)}
|
|
116
|
+
{...restProps}
|
|
117
|
+
>
|
|
118
|
+
{children}
|
|
119
|
+
</BodyLong>
|
|
120
|
+
</LinkAnchorOverlay>
|
|
121
|
+
</LinkCardContextProvider>
|
|
122
|
+
);
|
|
123
|
+
},
|
|
124
|
+
) as LinkCardComponent;
|
|
125
|
+
|
|
126
|
+
/* ---------------------------- LinkCard Title ---------------------------- */
|
|
127
|
+
type LinkCardTitleProps = HTMLAttributes<HTMLHeadingElement> & {
|
|
128
|
+
children: React.ReactNode;
|
|
129
|
+
/**
|
|
130
|
+
* Heading tag. Use "span" if you want a non header defining card
|
|
131
|
+
* (eg. you have a lot of them all at once, such as in a grid)
|
|
132
|
+
* @default "span"
|
|
133
|
+
*/
|
|
134
|
+
as?: "span" | "h2" | "h3" | "h4" | "h5" | "h6";
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @see 🏷️ {@link LinkCardTitleProps}
|
|
139
|
+
*/
|
|
140
|
+
export const LinkCardTitle = forwardRef<HTMLHeadingElement, LinkCardTitleProps>(
|
|
141
|
+
(
|
|
142
|
+
{ children, as = "span", className, ...restProps }: LinkCardTitleProps,
|
|
143
|
+
forwardedRef,
|
|
144
|
+
) => {
|
|
145
|
+
const { cn } = useRenameCSS();
|
|
146
|
+
|
|
147
|
+
const context = useLinkCardContext();
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<Heading
|
|
151
|
+
ref={forwardedRef}
|
|
152
|
+
as={as}
|
|
153
|
+
size={context.size === "medium" ? "small" : "xsmall"}
|
|
154
|
+
className={cn("navds-link-card__title", className)}
|
|
155
|
+
{...restProps}
|
|
156
|
+
>
|
|
157
|
+
{children}
|
|
158
|
+
{context.arrow && (
|
|
159
|
+
<LinkAnchorArrow
|
|
160
|
+
fontSize={context.size === "medium" ? "1.75rem" : "1.5rem"}
|
|
161
|
+
/>
|
|
162
|
+
)}
|
|
163
|
+
</Heading>
|
|
164
|
+
);
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
/* ---------------------------- LinkCard Anchor ---------------------------- */
|
|
169
|
+
type LinkCardAnchorProps = LinkAnchorProps;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @see 🏷️ {@link LinkCardAnchorProps}
|
|
173
|
+
*/
|
|
174
|
+
export const LinkCardAnchor = LinkAnchor;
|
|
175
|
+
|
|
176
|
+
/* ---------------------------- LinkCard Description ---------------------------- */
|
|
177
|
+
interface LinkCardDescriptionProps extends HTMLAttributes<HTMLDivElement> {
|
|
178
|
+
children: React.ReactNode;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @see 🏷️ {@link LinkCardDescriptionProps}
|
|
183
|
+
*/
|
|
184
|
+
export const LinkCardDescription = forwardRef<
|
|
185
|
+
HTMLDivElement,
|
|
186
|
+
LinkCardDescriptionProps
|
|
187
|
+
>(
|
|
188
|
+
(
|
|
189
|
+
{ children, className, ...restProps }: LinkCardDescriptionProps,
|
|
190
|
+
forwardedRef,
|
|
191
|
+
) => {
|
|
192
|
+
const { cn } = useRenameCSS();
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<div
|
|
196
|
+
ref={forwardedRef}
|
|
197
|
+
className={cn("navds-link-card__description", className)}
|
|
198
|
+
{...restProps}
|
|
199
|
+
>
|
|
200
|
+
{children}
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
203
|
+
},
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
/* ---------------------------- LinkCard Footer ---------------------------- */
|
|
207
|
+
interface LinkCardFooterProps extends HTMLAttributes<HTMLDivElement> {
|
|
208
|
+
children: React.ReactNode;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @see 🏷️ {@link LinkCardFooterProps}
|
|
213
|
+
*/
|
|
214
|
+
export const LinkCardFooter = forwardRef<HTMLDivElement, LinkCardFooterProps>(
|
|
215
|
+
(
|
|
216
|
+
{ children, className, ...restProps }: LinkCardFooterProps,
|
|
217
|
+
forwardedRef,
|
|
218
|
+
) => {
|
|
219
|
+
const { cn } = useRenameCSS();
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<div
|
|
223
|
+
ref={forwardedRef}
|
|
224
|
+
className={cn("navds-link-card__footer", className)}
|
|
225
|
+
{...restProps}
|
|
226
|
+
>
|
|
227
|
+
{children}
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
},
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
/* ---------------------------- LinkCard Icon ---------------------------- */
|
|
234
|
+
interface LinkCardIconProps extends HTMLAttributes<HTMLDivElement> {
|
|
235
|
+
children: React.ReactNode;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* @see 🏷️ {@link LinkCardIconProps}
|
|
240
|
+
*/
|
|
241
|
+
export const LinkCardIcon = forwardRef<HTMLDivElement, LinkCardIconProps>(
|
|
242
|
+
({ children, className, ...restProps }: LinkCardIconProps, forwardedRef) => {
|
|
243
|
+
const { cn } = useRenameCSS();
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<div
|
|
247
|
+
ref={forwardedRef}
|
|
248
|
+
aria-hidden
|
|
249
|
+
className={cn("navds-link-card__icon", className)}
|
|
250
|
+
{...restProps}
|
|
251
|
+
>
|
|
252
|
+
{children}
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
|
+
},
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
/* ---------------------------- LinkCard Image ---------------------------- */
|
|
259
|
+
type ImageAspectRatio = "1/1" | "16/9" | "16/10" | "4/3" | (string & {});
|
|
260
|
+
|
|
261
|
+
interface LinkCardImageProps extends HTMLAttributes<HTMLDivElement> {
|
|
262
|
+
children: React.ReactNode;
|
|
263
|
+
/**
|
|
264
|
+
* The aspect-ratio CSS property allows you to define the desired width-to-height ratio of an element's box.
|
|
265
|
+
* This means that even if the parent container or viewport size changes, the browser will adjust the element's dimensions to maintain the specified width-to-height ratio.
|
|
266
|
+
*/
|
|
267
|
+
aspectRatio?: ImageAspectRatio;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* @see 🏷️ {@link LinkCardImageProps}
|
|
272
|
+
*/
|
|
273
|
+
export const LinkCardImage = forwardRef<HTMLDivElement, LinkCardImageProps>(
|
|
274
|
+
(
|
|
275
|
+
{
|
|
276
|
+
children,
|
|
277
|
+
className,
|
|
278
|
+
aspectRatio,
|
|
279
|
+
style,
|
|
280
|
+
...restProps
|
|
281
|
+
}: LinkCardImageProps,
|
|
282
|
+
forwardedRef,
|
|
283
|
+
) => {
|
|
284
|
+
const { cn } = useRenameCSS();
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
<div
|
|
288
|
+
ref={forwardedRef}
|
|
289
|
+
className={cn("navds-link-card__image-container", className)}
|
|
290
|
+
style={{
|
|
291
|
+
...style,
|
|
292
|
+
aspectRatio,
|
|
293
|
+
}}
|
|
294
|
+
{...restProps}
|
|
295
|
+
>
|
|
296
|
+
{children}
|
|
297
|
+
</div>
|
|
298
|
+
);
|
|
299
|
+
},
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
LinkCard.Title = LinkCardTitle;
|
|
303
|
+
LinkCard.Anchor = LinkCardAnchor;
|
|
304
|
+
LinkCard.Description = LinkCardDescription;
|
|
305
|
+
LinkCard.Footer = LinkCardFooter;
|
|
306
|
+
LinkCard.Icon = LinkCardIcon;
|
|
307
|
+
LinkCard.Image = LinkCardImage;
|
|
308
|
+
|
|
309
|
+
export type {
|
|
310
|
+
LinkCardAnchorProps,
|
|
311
|
+
LinkCardDescriptionProps,
|
|
312
|
+
LinkCardFooterProps,
|
|
313
|
+
LinkCardIconProps,
|
|
314
|
+
LinkCardImageProps,
|
|
315
|
+
LinkCardProps,
|
|
316
|
+
LinkCardTitleProps,
|
|
317
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
export {
|
|
3
|
+
LinkCard,
|
|
4
|
+
LinkCardTitle,
|
|
5
|
+
LinkCardDescription,
|
|
6
|
+
LinkCardFooter,
|
|
7
|
+
LinkCardAnchor,
|
|
8
|
+
LinkCardIcon,
|
|
9
|
+
LinkCardImage,
|
|
10
|
+
} from "./LinkCard";
|
|
11
|
+
|
|
12
|
+
export type {
|
|
13
|
+
LinkCardProps,
|
|
14
|
+
LinkCardTitleProps,
|
|
15
|
+
LinkCardDescriptionProps,
|
|
16
|
+
LinkCardFooterProps,
|
|
17
|
+
LinkCardAnchorProps,
|
|
18
|
+
LinkCardIconProps,
|
|
19
|
+
LinkCardImageProps,
|
|
20
|
+
} from "./LinkCard";
|
|
@@ -38,21 +38,13 @@ interface LinkPanelComponentType
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
*
|
|
41
|
+
* @deprecated Use `LinkCard` instead. Migrations should be straightforward as the API is similar.
|
|
42
|
+
* @see [📝 LinkCard documentation](https://aksel.nav.no/komponenter/core/linkcard)
|
|
42
43
|
*
|
|
43
|
-
* @see [📝 Documentation](https://aksel.nav.no/komponenter/
|
|
44
|
+
* @see [📝 Documentation](https://aksel.nav.no/komponenter/legacy/linkpanel)
|
|
44
45
|
* @see 🏷️ {@link LinkPanelProps}
|
|
45
46
|
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
|
|
46
47
|
*
|
|
47
|
-
* @example
|
|
48
|
-
* ```jsx
|
|
49
|
-
* <LinkPanel href="#" border>
|
|
50
|
-
* <LinkPanel.Title>Arbeidssøker eller permittert</LinkPanel.Title>
|
|
51
|
-
* <LinkPanel.Description>
|
|
52
|
-
* Om jobb, registrering, CV, dagpenger og feriepenger av dagpenger
|
|
53
|
-
* </LinkPanel.Description>
|
|
54
|
-
* </LinkPanel>
|
|
55
|
-
* ```
|
|
56
48
|
*/
|
|
57
49
|
export const LinkPanelComponent: OverridableComponent<
|
|
58
50
|
LinkPanelProps,
|
package/src/modal/Modal.test.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
|
2
2
|
import React, { useState } from "react";
|
|
3
3
|
import { describe, expect, test } from "vitest";
|
|
4
4
|
import { Button, Modal } from "..";
|
|
5
|
-
import { BODY_CLASS } from "./ModalUtils";
|
|
5
|
+
import { BODY_CLASS, BODY_CLASS_LEGACY } from "./ModalUtils";
|
|
6
6
|
|
|
7
7
|
const Test = () => {
|
|
8
8
|
const [open, setOpen] = useState(true);
|
|
@@ -32,11 +32,15 @@ describe("Modal", () => {
|
|
|
32
32
|
test("should toggle body class", async () => {
|
|
33
33
|
render(<Test />);
|
|
34
34
|
expect(document.body.classList).toContain(BODY_CLASS);
|
|
35
|
+
expect(document.body.classList).toContain(BODY_CLASS_LEGACY);
|
|
35
36
|
|
|
36
37
|
fireEvent.click(screen.getByText("Close"));
|
|
37
|
-
await waitFor(() =>
|
|
38
|
-
expect(document.body.classList).not.toContain(BODY_CLASS)
|
|
39
|
-
);
|
|
38
|
+
await waitFor(() => {
|
|
39
|
+
expect(document.body.classList).not.toContain(BODY_CLASS);
|
|
40
|
+
});
|
|
41
|
+
await waitFor(() => {
|
|
42
|
+
expect(document.body.classList).not.toContain(BODY_CLASS_LEGACY);
|
|
43
|
+
});
|
|
40
44
|
});
|
|
41
45
|
|
|
42
46
|
test("should toggle body class when using portal", async () => {
|
|
@@ -46,10 +50,14 @@ describe("Modal", () => {
|
|
|
46
50
|
</Modal>,
|
|
47
51
|
);
|
|
48
52
|
expect(document.body.classList).toContain(BODY_CLASS);
|
|
53
|
+
expect(document.body.classList).toContain(BODY_CLASS_LEGACY);
|
|
49
54
|
|
|
50
55
|
fireEvent.click(screen.getByRole("button"));
|
|
51
56
|
await waitFor(() =>
|
|
52
57
|
expect(document.body.classList).not.toContain(BODY_CLASS),
|
|
53
58
|
);
|
|
59
|
+
await waitFor(() =>
|
|
60
|
+
expect(document.body.classList).not.toContain(BODY_CLASS_LEGACY),
|
|
61
|
+
);
|
|
54
62
|
});
|
|
55
63
|
});
|
package/src/modal/ModalUtils.ts
CHANGED
|
@@ -27,7 +27,8 @@ export function getCloseHandler(
|
|
|
27
27
|
return () => modalRef.current?.close();
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
export const
|
|
30
|
+
export const BODY_CLASS_LEGACY = "navds-modal__document-body";
|
|
31
|
+
export const BODY_CLASS = "aksel-modal__document-body";
|
|
31
32
|
|
|
32
33
|
export function useBodyScrollLock(
|
|
33
34
|
modalRef: React.RefObject<HTMLDialogElement>,
|
|
@@ -35,21 +36,37 @@ export function useBodyScrollLock(
|
|
|
35
36
|
isNested: boolean,
|
|
36
37
|
) {
|
|
37
38
|
React.useEffect(() => {
|
|
38
|
-
if (isNested)
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
if (isNested) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// We check both to avoid running this twice when not using portal
|
|
44
|
+
if (!modalRef.current || !portalNode) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// In case `open` is true initially
|
|
49
|
+
if (modalRef.current.open) {
|
|
50
|
+
document.body.classList.add(BODY_CLASS, BODY_CLASS_LEGACY);
|
|
51
|
+
}
|
|
41
52
|
|
|
42
53
|
const observer = new MutationObserver(() => {
|
|
43
|
-
if (modalRef.current?.open)
|
|
44
|
-
|
|
54
|
+
if (modalRef.current?.open) {
|
|
55
|
+
document.body.classList.add(BODY_CLASS, BODY_CLASS_LEGACY);
|
|
56
|
+
} else {
|
|
57
|
+
document.body.classList.remove(BODY_CLASS, BODY_CLASS_LEGACY);
|
|
58
|
+
}
|
|
45
59
|
});
|
|
60
|
+
|
|
46
61
|
observer.observe(modalRef.current, {
|
|
47
62
|
attributes: true,
|
|
48
63
|
attributeFilter: ["open"],
|
|
49
64
|
});
|
|
65
|
+
|
|
50
66
|
return () => {
|
|
51
67
|
observer.disconnect();
|
|
52
|
-
|
|
68
|
+
// In case modal is unmounted before it's closed
|
|
69
|
+
document.body.classList.remove(BODY_CLASS, BODY_CLASS_LEGACY);
|
|
53
70
|
};
|
|
54
71
|
}, [modalRef, portalNode, isNested]);
|
|
55
72
|
}
|