Skip to content

Commit 89cce76

Browse files
Barsnesoddvernes
andauthored
feat(www): technical implementation of new component pages (#3966)
Co-authored-by: Oddbjørn Øvernes <oddbjorn.overnes@gmail.com>
1 parent 673d2df commit 89cce76

42 files changed

Lines changed: 2496 additions & 17 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/tame-pens-call.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { Table } from '@digdir/designsystemet-react';
2+
import cl from 'clsx';
3+
import { forwardRef } from 'react';
4+
5+
type CssAttributesProps = {
6+
vars: {
7+
[key: string]: string;
8+
};
9+
} & React.HTMLAttributes<HTMLTableElement>;
10+
11+
export const CssAttributes = forwardRef<HTMLTableElement, CssAttributesProps>(
12+
function CssAttributes({ vars, className, ...rest }, ref) {
13+
if (Object.keys(vars).length === 0) return null;
14+
return (
15+
<Table
16+
className={cl('component-table', className)}
17+
data-color='accent'
18+
zebra
19+
border
20+
style={{
21+
tableLayout: 'fixed',
22+
}}
23+
{...rest}
24+
ref={ref}
25+
>
26+
<Table.Head>
27+
<Table.Row>
28+
<Table.HeaderCell>Name</Table.HeaderCell>
29+
<Table.HeaderCell>Value(s)</Table.HeaderCell>
30+
</Table.Row>
31+
</Table.Head>
32+
<Table.Body>
33+
{Object.entries(vars).map(([name, value]) => (
34+
<Table.Row key={name}>
35+
<Table.Cell>data-{name}</Table.Cell>
36+
<Table.Cell>{value}</Table.Cell>
37+
</Table.Row>
38+
))}
39+
</Table.Body>
40+
</Table>
41+
);
42+
},
43+
);
44+
45+
/* returns data-attributes and their possible values as key value pairs*/
46+
export function getAttributes(css: string) {
47+
const res: { [key: string]: Set<unknown> | string } = {};
48+
//filter out global attributes referenced locally
49+
const globals = ['color', 'size', 'color-scheme'];
50+
51+
const allAttrs = Array.from(
52+
css.matchAll(/\[data-([^=\]]+)(?:=([^\]]+))?\]/g),
53+
).map((matches) => ({ [matches[1]]: matches[2] }));
54+
55+
for (const attr of allAttrs) {
56+
for (const [key, value] of Object.entries(attr)) {
57+
if (globals.includes(key)) continue;
58+
if (!res[key]) {
59+
res[key] = new Set();
60+
}
61+
if (value) (res[key] as Set<unknown>).add(value);
62+
}
63+
}
64+
for (const key of Object.keys(res)) {
65+
res[key] = Array.from(res[key]).join(', ');
66+
}
67+
68+
return res as { [key: string]: string };
69+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { Table } from '@digdir/designsystemet-react';
2+
import cl from 'clsx';
3+
import { forwardRef } from 'react';
4+
5+
type CssVariablesProps = {
6+
vars: {
7+
[key: string]: string;
8+
};
9+
} & React.HTMLAttributes<HTMLTableElement>;
10+
11+
export const CssVariables = forwardRef<HTMLTableElement, CssVariablesProps>(
12+
function CssVariables({ vars, className, ...rest }, ref) {
13+
return (
14+
<Table
15+
zebra
16+
className={cl('component-table', className)}
17+
data-color='accent'
18+
border
19+
style={{
20+
tableLayout: 'fixed',
21+
}}
22+
{...rest}
23+
ref={ref}
24+
>
25+
<Table.Head>
26+
<Table.Row>
27+
<Table.HeaderCell>Name</Table.HeaderCell>
28+
<Table.HeaderCell>Value</Table.HeaderCell>
29+
</Table.Row>
30+
</Table.Head>
31+
<Table.Body>
32+
{Object.entries(vars).map(([name, value]) => (
33+
<Table.Row key={name}>
34+
<Table.Cell>{name}</Table.Cell>
35+
<Table.Cell>{value}</Table.Cell>
36+
</Table.Row>
37+
))}
38+
</Table.Body>
39+
</Table>
40+
);
41+
},
42+
);
43+
44+
/* get variables and its value from css file */
45+
export function getCssVariables(css: string) {
46+
const res: { [key: string]: string } = {};
47+
48+
// temporarily remove inline strings, as they may contain ; and } characters
49+
// and thus ruin the matching for property declarations
50+
const stringsRemovedFromCss = Array.from(css.matchAll(/"[^"]*"/g)).map(
51+
(x) => x[0],
52+
);
53+
const cssWithRemovedStrings = stringsRemovedFromCss.reduce(
54+
(prev, curr, idx) => prev.replace(curr, `<placeholder-${idx}>`),
55+
css,
56+
);
57+
// get all --dsc-* property declarations
58+
const cssVars = Array.from(
59+
cssWithRemovedStrings.matchAll(/(?<!var\()(--dsc-[^;}]+)[;}]/g),
60+
).map((matches) => matches[1]);
61+
62+
/* Iterate over the CSS properties */
63+
for (const declaration of cssVars) {
64+
const [name, value] = declaration.split(':');
65+
// Choose the earliest declaration of the property.
66+
// We assume later declarations are part of a sub-selector.
67+
if (!res[name]) {
68+
// Return the original inline string from the value, if it was removed earlier
69+
const valueWithOriginalString = value.replace(
70+
/<placeholder-(\d+)>/,
71+
(_, p1: string) => stringsRemovedFromCss[parseInt(p1, 10)],
72+
);
73+
res[name] = valueWithOriginalString;
74+
}
75+
}
76+
77+
return res;
78+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
.dodont {
2+
border: var(--ds-border-width-default) solid var(--ds-color-neutral-border-subtle);
3+
border-radius: var(--ds-border-radius-md);
4+
width: fit-content;
5+
display: grid;
6+
overflow: hidden;
7+
grid-template-rows: auto 1fr auto;
8+
margin-top: var(--ds-size-6);
9+
10+
.preview {
11+
padding: var(--ds-size-6);
12+
}
13+
.preview {
14+
background-color: var(--ds-color-neutral-surface-default);
15+
}
16+
.live-preview {
17+
/*@TODO: layout selectors?*/
18+
display: flex;
19+
align-items: center;
20+
justify-content: center;
21+
gap: var(--ds-size-6);
22+
flex-wrap: wrap;
23+
}
24+
.header {
25+
border-bottom: var(--ds-border-width-default) solid var(--ds-color-neutral-border-subtle);
26+
padding: var(--ds-size-4) var(--ds-size-6);
27+
background-color: var(--ds-color-surface-tinted);
28+
color: var(--ds-color-text-default);
29+
display: flex;
30+
align-items: center;
31+
gap: var(--ds-size-2);
32+
p {
33+
margin: 0;
34+
font-weight: 500;
35+
}
36+
svg {
37+
color: var(--ds-color-base-default);
38+
font-size: 2rem;
39+
}
40+
}
41+
.footer {
42+
background-color: var(--ds-color-neutral-surface-default);
43+
border-top: var(--ds-border-width-default) solid var(--ds-color-neutral-border-subtle);
44+
padding: var(--ds-size-4) var(--ds-size-6);
45+
color: var(--ds-color-neutral-text-subtle);
46+
contain: inline-size; /*maybe/maybe not do this (footer text does not contribute to card-width but is constrained by content above and maybe a min-width)*/
47+
> p {
48+
margin: 0;
49+
font-weight: 400;
50+
max-width: 65ch;
51+
text-wrap: pretty;
52+
}
53+
}
54+
}
55+
/*TODO: needs more work*/
56+
:global(.dodont-row) {
57+
column-gap: var(--ds-size-6);
58+
display: grid;
59+
grid-template-columns: repeat(auto-fit, minmax(min(100%, 350px), 1fr));
60+
margin-bottom: var(--ds-size-8);
61+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import * as ds from '@digdir/designsystemet-react';
2+
import * as aksel from '@navikt/aksel-icons';
3+
import type { HTMLAttributes, ReactNode } from 'react';
4+
import { useTranslation } from 'react-i18next';
5+
import { LiveError, LivePreview, LiveProvider } from 'react-live';
6+
import classes from './do-dont.module.css';
7+
8+
const scopes = {
9+
...ds,
10+
...aksel,
11+
};
12+
13+
type doDontProps = {
14+
code: string;
15+
variant: 'do' | 'dont';
16+
children?: ReactNode;
17+
layout?: 'row' | 'column' | 'centered';
18+
} & HTMLAttributes<HTMLDivElement>;
19+
20+
export const DoDont = ({
21+
code,
22+
variant,
23+
layout = 'row',
24+
children,
25+
...rest
26+
}: doDontProps) => {
27+
const { t } = useTranslation();
28+
return (
29+
<div
30+
className={classes.dodont}
31+
data-variant={variant}
32+
data-layout={layout}
33+
{...rest}
34+
>
35+
<div
36+
className={classes.header}
37+
data-color={variant === 'do' ? 'success' : 'danger'}
38+
>
39+
{variant === 'do' ? (
40+
<>
41+
<aksel.CheckmarkCircleFillIcon />
42+
<p className='ds-paragraph'>{t('do')}</p>
43+
</>
44+
) : (
45+
<>
46+
<aksel.XMarkOctagonFillIcon />
47+
<p className='ds-paragraph'>{t('dont')}</p>
48+
</>
49+
)}
50+
</div>
51+
<LiveProvider code={code} scope={scopes} noInline disabled>
52+
<div className={classes.preview}>
53+
<LivePreview
54+
className={classes['live-preview']}
55+
data-layout={layout}
56+
/>
57+
<LiveError className='ds-alert' />
58+
</div>
59+
</LiveProvider>
60+
<div className={classes.footer}>{children}</div>
61+
</div>
62+
);
63+
};

0 commit comments

Comments
 (0)