Skip to content

Commit 76e325a

Browse files
authored
[Appdir] Thumbnail recoloring (#3776)
* ✨ Introduce ImageAsThemedSvg for SVG handling in ImageAsSvg.tsx * 💄 Update background color for dark mode in DesignsystemetOverview.module.css * 🎉 Use 'themed' thumbnails for ds-pages * 💄 Change background color to var(--ax-bg-neutral-soft) in DesignsystemetOverview.module.css * 💄 Move tag to top of overview-card * 📝 yarn lock sync * 💄 Tone down overview thumbnail preview * 🔥 Remove arrow from overview cards * ♻️ Show arrow on LinkCard
1 parent ff3ee5e commit 76e325a

File tree

8 files changed

+143
-153
lines changed

8 files changed

+143
-153
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/* stylelint-disable csstools/value-no-unknown-custom-properties */
2+
.imageAsModule {
3+
width: var(--website-svg-size);
4+
height: var(--website-svg-size);
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import DOMPurify from "isomorphic-dompurify";
2+
import styles from "./ImageAsSvg.module.css";
3+
4+
/**
5+
* Utility function to fetch and format a image as SVG with recoloring.
6+
*/
7+
async function ImageAsThemedSvg({
8+
url,
9+
size,
10+
className,
11+
}: {
12+
url?: string;
13+
size: number;
14+
className?: string;
15+
}) {
16+
if (!url) {
17+
return null;
18+
}
19+
20+
const svgString = await fetch(url)
21+
.then((res) => res.text())
22+
.catch(() => null);
23+
24+
if (!svgString) {
25+
return null;
26+
}
27+
28+
const formattedSvgString = formatSvgString(svgString);
29+
30+
const cleanedSvg = DOMPurify.sanitize(formattedSvgString, {
31+
USE_PROFILES: { svg: true },
32+
});
33+
34+
return (
35+
<div
36+
// biome-ignore lint/security/noDangerouslySetInnerHtml: We purify the SVG string, so its safer to use locally here.
37+
dangerouslySetInnerHTML={{ __html: cleanedSvg }}
38+
aria-hidden
39+
style={{ "--website-svg-size": `${size}px` }}
40+
className={className}
41+
/>
42+
);
43+
}
44+
45+
const colorReplacements = {
46+
"#99F6E4": "var(--ax-bg-moderate-hoverA)",
47+
"#AEF4E4": "var(--ax-bg-moderate-hoverA)",
48+
"#003453": "var(--ax-text-subtle)",
49+
"#262626": "var(--ax-text-subtle)",
50+
"#23262A": "var(--ax-text-subtle)",
51+
white: "var(--ax-bg-default)",
52+
"#417DA0": "var(--ax-bg-strong)",
53+
"#D7E6F0": "var(--ax-bg-moderate-hover)",
54+
"#C0D6E4": "var(--ax-bg-moderate-pressed)",
55+
"#E3EFF7": "var(--ax-bg-moderate)",
56+
"#005A92": "var(--ax-bg-strong)",
57+
};
58+
59+
/**
60+
* Formats the SVG string by replacing colors and adding classes.
61+
*/
62+
function formatSvgString(svgString: string) {
63+
let formattedSvgString = svgString;
64+
65+
formattedSvgString = formattedSvgString.replace(
66+
"<svg",
67+
`<svg class="${styles.imageAsModule}" aria-hidden`,
68+
);
69+
70+
Object.keys(colorReplacements).forEach((color) => {
71+
const replacement = colorReplacements[color];
72+
73+
formattedSvgString = formattedSvgString.replace(
74+
new RegExp(color, "g"),
75+
replacement,
76+
);
77+
});
78+
79+
return formattedSvgString;
80+
}
81+
82+
export { ImageAsThemedSvg };

aksel.nav.no/website/app/dev/(designsystemet)/_ui/Designsystemet.module.css

+2-5
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,13 @@
7272
align-items: center;
7373
aspect-ratio: 24 / 9;
7474
border-radius: 16px;
75-
background-color: var(--ax-bg-brand-blue-moderate);
75+
background-color: var(--ax-bg-moderate);
7676
margin-block-start: var(--ax-space-28);
7777
overflow: hidden;
7878
}
7979

8080
.thumbnailImage {
81-
position: absolute;
82-
inset: 0;
83-
width: 100%;
84-
height: 100%;
81+
z-index: 10;
8582
}
8683

8784
.thumbnailCube {

aksel.nav.no/website/app/dev/(designsystemet)/_ui/Designsystemet.thumbnail.tsx

+14-83
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,20 @@
1+
import { ImageAsThemedSvg } from "@/app/_ui/image-as-svg/ImageAsSvg";
12
import styles from "./Designsystemet.module.css";
23

3-
function DesignsystemetThumbnail() {
4+
function DesignsystemetThumbnail({ thumbnailUrl }: { thumbnailUrl?: string }) {
5+
if (!thumbnailUrl) {
6+
return null;
7+
}
8+
49
return (
510
<div className={styles.thumbnailContainer}>
611
<CubeShape />
7-
<Thumbnail />
8-
</div>
9-
);
10-
}
11-
12-
function Thumbnail() {
13-
return (
14-
<svg
15-
viewBox="0 0 289 289"
16-
fill="none"
17-
xmlns="http://www.w3.org/2000/svg"
18-
aria-hidden
19-
className={styles.thumbnailImage}
20-
>
21-
<path
22-
d="M35.06 91.7645C35.06 82.6549 42.4449 75.27 51.5545 75.27H237.445C246.555 75.27 253.94 82.6549 253.94 91.7646V197.016C253.94 206.125 246.555 213.51 237.445 213.51H51.5546C42.4449 213.51 35.06 206.125 35.06 197.015V91.7645Z"
23-
fill="white"
24-
/>
25-
<mask id="path-3-inside-1_1364_1724" fill="white">
26-
<path d="M35.06 78.0191C35.06 76.5008 36.2908 75.27 37.8091 75.27H251.191C252.709 75.27 253.94 76.5008 253.94 78.0191V118.601C253.94 120.119 252.709 121.35 251.191 121.35H37.8091C36.2908 121.35 35.06 120.119 35.06 118.601V78.0191Z" />
27-
</mask>
28-
<path
29-
d="M35.06 75.27H253.94H35.06ZM253.94 118.601C253.94 121.71 251.42 124.23 248.311 124.23H40.6891C37.5802 124.23 35.06 121.71 35.06 118.601V118.47C35.06 118.47 36.2908 118.47 37.8091 118.47H251.191C252.709 118.47 253.94 118.47 253.94 118.47V118.601ZM35.06 121.35V75.27V121.35ZM253.94 75.27V121.35V75.27Z"
30-
fill="#C0D6E4"
31-
mask="url(#path-3-inside-1_1364_1724)"
12+
<ImageAsThemedSvg
13+
size={280}
14+
url={thumbnailUrl}
15+
className={styles.thumbnailImage}
3216
/>
33-
<path
34-
d="M51.1398 92.23L59.0598 100.15L66.9798 92.23"
35-
stroke="#417DA0"
36-
strokeWidth="2.88"
37-
strokeLinecap="round"
38-
strokeLinejoin="round"
39-
/>
40-
<rect
41-
x="83.62"
42-
y="90.79"
43-
width="122.4"
44-
height="11.52"
45-
rx="5.76"
46-
fill="#417DA0"
47-
/>
48-
<mask id="path-7-inside-2_1364_1724" fill="white">
49-
<path d="M35.06 124.099C35.06 122.581 36.2908 121.35 37.8091 121.35H251.191C252.709 121.35 253.94 122.581 253.94 124.099V164.681C253.94 166.199 252.709 167.43 251.191 167.43H37.8091C36.2908 167.43 35.06 166.199 35.06 164.681V124.099Z" />
50-
</mask>
51-
<path
52-
d="M35.06 121.35H253.94H35.06ZM253.94 164.681C253.94 167.79 251.42 170.31 248.311 170.31H40.6891C37.5802 170.31 35.06 167.79 35.06 164.681V164.55C35.06 164.55 36.2908 164.55 37.8091 164.55H251.191C252.709 164.55 253.94 164.55 253.94 164.55V164.681ZM35.06 167.43V121.35V167.43ZM253.94 121.35V167.43V121.35Z"
53-
fill="#C0D6E4"
54-
mask="url(#path-7-inside-2_1364_1724)"
55-
/>
56-
<path
57-
d="M51.1398 138.31L59.0598 146.23L66.9798 138.31"
58-
stroke="#417DA0"
59-
strokeWidth="2.88"
60-
strokeLinecap="round"
61-
strokeLinejoin="round"
62-
/>
63-
<rect
64-
x="83.62"
65-
y="136.87"
66-
width="136.8"
67-
height="11.52"
68-
rx="5.76"
69-
fill="#417DA0"
70-
/>
71-
<path
72-
d="M51.1398 184.39L59.0598 192.31L66.9798 184.39"
73-
stroke="#417DA0"
74-
strokeWidth="2.88"
75-
strokeLinecap="round"
76-
strokeLinejoin="round"
77-
/>
78-
<rect
79-
x="83.62"
80-
y="182.95"
81-
width="109.44"
82-
height="11.52"
83-
rx="5.76"
84-
fill="#417DA0"
85-
/>
86-
</svg>
17+
</div>
8718
);
8819
}
8920

@@ -102,19 +33,19 @@ function CubeShape() {
10233
fillRule="evenodd"
10334
clipRule="evenodd"
10435
d="M778.342 -296.311C776.611 -298.043 773.803 -298.043 772.071 -296.311L533.962 -58.2022C532.231 -56.4706 532.231 -53.663 533.962 -51.9313L772.071 186.178C773.803 187.909 776.611 187.909 778.342 186.178L1016.45 -51.9313C1018.18 -53.6629 1018.18 -56.4705 1016.45 -58.2022L778.342 -296.311ZM775.207 -286.905L1007.04 -55.0667L775.207 176.771L543.369 -55.0668L775.207 -286.905Z"
105-
fill="#EEF6FC"
36+
fill="var(--ax-bg-brand-blue-soft)"
10637
/>
10738
<path
10839
fillRule="evenodd"
10940
clipRule="evenodd"
11041
d="M302.748 -62.907C301.572 -62.907 300.445 -62.4398 299.613 -61.6082L61.5029 176.502C60.2347 177.77 59.8554 179.677 60.5417 181.334C61.228 182.991 62.8449 184.072 64.6384 184.072L302.391 184.072C303.567 184.072 304.695 183.604 305.527 182.773L543.637 -55.3373C544.905 -56.6055 545.285 -58.5127 544.598 -60.1697C543.912 -61.8266 542.295 -62.907 540.502 -62.907L302.748 -62.907ZM304.585 -54.0386L529.797 -54.0386L300.555 175.203H75.3435L304.585 -54.0386Z"
111-
fill="#EEF6FC"
42+
fill="var(--ax-bg-brand-blue-soft)"
11243
/>
11344
<path
11445
fillRule="evenodd"
11546
clipRule="evenodd"
11647
d="M71.1787 173.097C69.447 171.365 66.6394 171.365 64.9078 173.097L-173.201 411.206C-174.933 412.938 -174.933 415.745 -173.201 417.477L64.9077 655.586C66.6394 657.318 69.447 657.318 71.1786 655.586L309.288 417.477C311.019 415.745 311.019 412.938 309.288 411.206L71.1787 173.097ZM68.0432 182.503L299.881 414.341L68.0432 646.18L-163.795 414.341L68.0432 182.503Z"
117-
fill="#EEF6FC"
48+
fill="var(--ax-bg-brand-blue-soft)"
11849
/>
11950
</svg>
12051
);

aksel.nav.no/website/app/dev/(designsystemet)/_ui/DesignsystemetPage.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { PortableTextBlock } from "next-sanity";
2+
import { Image } from "sanity";
23
import { BodyShort, Box, HStack, Heading, Tag } from "@navikt/ds-react";
34
import {
45
GRUNNLEGGENDE_BY_SLUG_QUERYResult,
56
KOMPONENT_BY_SLUG_QUERYResult,
67
MONSTER_MALER_BY_SLUG_QUERYResult,
78
} from "@/app/_sanity/query-types";
9+
import { urlForImage } from "@/app/_sanity/utils";
810
import { CustomPortableText } from "@/app/_ui/portable-text/CustomPortableText";
911
import { getStatusTag } from "@/app/_ui/theming/theme-config";
1012
import { DesignsystemetEyebrow } from "@/app/dev/(designsystemet)/_ui/Designsystemet.eyebrow";
@@ -51,8 +53,10 @@ async function DesignsystemetPageHeader({ data }: DesignsystemetPageT) {
5153

5254
const isComponentPage = data?._type === "komponent_artikkel";
5355

56+
const imageUrl = urlForImage(data?.status?.bilde as Image)?.url();
57+
5458
return (
55-
<Box marginBlock="space-0 space-28">
59+
<Box marginBlock="space-0 space-28" data-color-role={statusTag?.colorRole}>
5660
<DesignsystemetEyebrow type={data?._type} />
5761

5862
<Box marginBlock="space-0 space-8" asChild>
@@ -87,7 +91,7 @@ async function DesignsystemetPageHeader({ data }: DesignsystemetPageT) {
8791
</HStack>
8892
{isComponentPage && <KomponentLinks data={data} />}
8993

90-
<DesignsystemetThumbnail />
94+
<DesignsystemetThumbnail thumbnailUrl={imageUrl} />
9195
</Box>
9296
);
9397
}

aksel.nav.no/website/app/dev/(designsystemet)/_ui/overview/DesignsystemetOverview.module.css

+6-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@
3030
aspect-ratio: 16 / 10;
3131
border-start-start-radius: calc(var(--ax-border-radius-xlarge) - 1px);
3232
border-start-end-radius: calc(var(--ax-border-radius-xlarge) - 1px);
33-
background: var(--ax-bg-moderate);
33+
background: var(--ax-bg-soft);
34+
}
35+
36+
/* stylelint-disable-next-line selector-pseudo-class-no-unknown */
37+
:global(.dark) .overviewImageWrapper {
38+
background: var(--ax-bg-neutral-soft);
3439
}
3540

3641
.overviewCardHeading {

aksel.nav.no/website/app/dev/(designsystemet)/_ui/overview/DesignsystemetOverview.tsx

+20-60
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import NextImage from "next/image";
21
import type { Image } from "sanity";
32
import {
43
Bleed,
@@ -11,6 +10,7 @@ import {
1110
} from "@navikt/ds-react";
1211
import { DESIGNSYSTEM_OVERVIEW_BY_CATEGORY_QUERYResult } from "@/app/_sanity/query-types";
1312
import { urlForImage } from "@/app/_sanity/utils";
13+
import { ImageAsThemedSvg } from "@/app/_ui/image-as-svg/ImageAsSvg";
1414
import { getStatusTag } from "@/app/_ui/theming/theme-config";
1515
import { MarkdownText } from "@/app/_ui/typography/MarkdownText";
1616
import {
@@ -79,77 +79,37 @@ function DesignsystemetOverviewCard({
7979
return (
8080
<LinkCard data-color-role={statusTag?.colorRole} autoLayout={false}>
8181
<VStack gap="space-16">
82-
<Bleed marginInline="space-20" marginBlock="space-16 0">
83-
<span className={styles.overviewImageWrapper}>
84-
{imageUrl ? (
85-
<NextImage
86-
src={imageUrl}
87-
width={200}
88-
height={200}
89-
alt={page?.heading + " thumbnail"}
90-
aria-hidden
91-
/>
92-
) : (
93-
<FallbackSvg />
94-
)}
95-
</span>
96-
</Bleed>
97-
98-
<LinkCardTitle as="h2">
99-
<LinkCardAnchor href={`/${page?.slug}`}>
100-
<HStack as="span" gap="space-8" align="center">
101-
<span>{page?.heading} </span>
102-
{statusTagWithoutStable && (
82+
<Box position="relative" asChild>
83+
<Bleed marginInline="space-20" marginBlock="space-16 0">
84+
<span className={styles.overviewImageWrapper}>
85+
<ImageAsThemedSvg url={imageUrl} size={200} />
86+
</span>
87+
{statusTagWithoutStable && (
88+
<Box asChild position="absolute" bottom="space-8" left="space-8">
10389
<Tag
10490
size="small"
105-
variant="success"
91+
variant="alt1-filled"
10692
data-color-role={statusTag?.colorRole}
10793
>
108-
{/* TODO: Remove underline from tag */}
10994
{statusTagWithoutStable.text}
11095
</Tag>
111-
)}
112-
</HStack>
113-
</LinkCardAnchor>
96+
</Box>
97+
)}
98+
</Bleed>
99+
</Box>
100+
101+
<LinkCardTitle as="h2">
102+
<HStack as="span" gap="space-8" align="center">
103+
<LinkCardAnchor href={`/${page?.slug}`}>
104+
{page?.heading}
105+
</LinkCardAnchor>
106+
</HStack>
114107
</LinkCardTitle>
115108
</VStack>
116109
</LinkCard>
117110
);
118111
}
119112

120-
function FallbackSvg() {
121-
return (
122-
<svg
123-
width="200"
124-
height="200"
125-
viewBox="0 0 200 200"
126-
fill="none"
127-
xmlns="http://www.w3.org/2000/svg"
128-
aria-hidden
129-
>
130-
<path
131-
fillRule="evenodd"
132-
clipRule="evenodd"
133-
d="M100 153C129.271 153 153 129.271 153 100C153 70.7289 129.271 47 100 47C70.7289 47 47 70.7289 47 100C47 129.271 70.7289 153 100 153ZM100 165C135.899 165 165 135.899 165 100C165 64.1015 135.899 35 100 35C64.1015 35 35 64.1015 35 100C35 135.899 64.1015 165 100 165Z"
134-
fill="#001630"
135-
fillOpacity="0.188235"
136-
/>
137-
<path
138-
d="M96.768 67C97.5378 65.6667 99.4622 65.6667 100.232 67L112.789 88.75C113.559 90.0833 112.597 91.75 111.057 91.75H85.9426C84.403 91.75 83.4408 90.0833 84.2106 88.75L96.768 67Z"
139-
fill="#5D6573"
140-
/>
141-
<path
142-
d="M96 114.5C96 121.956 89.9558 128 82.5 128C75.0442 128 69 121.956 69 114.5C69 107.044 75.0442 101 82.5 101C89.9558 101 96 107.044 96 114.5Z"
143-
fill="#5D6573"
144-
/>
145-
<path
146-
d="M105 104C105 102.895 105.895 102 107 102H129C130.105 102 131 102.895 131 104V126C131 127.105 130.105 128 129 128H107C105.895 128 105 127.105 105 126V104Z"
147-
fill="#5D6573"
148-
/>
149-
</svg>
150-
);
151-
}
152-
153113
function sortDesignsystemetOverviewList(
154114
list: DESIGNSYSTEM_OVERVIEW_BY_CATEGORY_QUERYResult,
155115
) {

0 commit comments

Comments
 (0)