diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 507acfc76..067016fb8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1828,6 +1828,9 @@ importers: '@nl-design-system-unstable/theme-toolkit': specifier: workspace:* version: link:../../packages/theme-toolkit + '@nl-design-system-unstable/tokens-lib': + specifier: workspace:* + version: link:../../packages/tokens-lib '@storybook/react': specifier: 8.2.7 version: 8.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.7(@babel/preset-env@7.24.5(@babel/core@7.26.0)))(typescript@5.5.4) @@ -1837,6 +1840,9 @@ importers: '@types/react': specifier: 18.3.3 version: 18.3.3 + '@utrecht/color-sample-css': + specifier: 1.4.0 + version: 1.4.0 '@utrecht/component-library-react': specifier: 7.1.0 version: 7.1.0(@babel/runtime@7.25.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4402,6 +4408,9 @@ packages: '@utrecht/color-sample-css@1.1.0': resolution: {integrity: sha512-BdRKfVlx+CJppLmVev9qQ2C2nyOA0qhDdQTn0KI+Q68OeGz44Vlc8007JjHXNwd8tMWqLMeWeuexNxKpFhw5PQ==} + '@utrecht/color-sample-css@1.4.0': + resolution: {integrity: sha512-AMVUEyg+7CkEh8xuI/hHt0QJ9p27wUi3BQOIsEvrl5Ah3Pfphomarg4uP99iS+goPjTsM0H+3hiJ6VxG0K6KaQ==} + '@utrecht/column-layout-css@1.1.0': resolution: {integrity: sha512-/umRVD5Mq3o7MLetTp164yqsNa6iLVzLsnq3XoBRYNF8uF2sVZJ/r4ZI19MuAtk9q2s87KpYfh/3qZNDqJU4sw==} @@ -11852,6 +11861,8 @@ snapshots: '@utrecht/color-sample-css@1.1.0': {} + '@utrecht/color-sample-css@1.4.0': {} + '@utrecht/column-layout-css@1.1.0': {} '@utrecht/combobox-css@1.2.0': {} diff --git a/proprietary/purmerend-design-tokens/documentation/ColorSampleList.css b/proprietary/purmerend-design-tokens/documentation/ColorSampleList.css new file mode 100644 index 000000000..186e28514 --- /dev/null +++ b/proprietary/purmerend-design-tokens/documentation/ColorSampleList.css @@ -0,0 +1,55 @@ +.color-sample-list { + display: inline-flex; + padding-block: 0; + padding-inline: 0; + border: 1px solid silver; + flex-wrap: nowrap; +} + +.color-sample-list__item { + margin-block: 0; + margin-inline: 0; + padding-block: 0; + padding-inline: 0; +} + +.component-button, +.color-sample-list__button, +.color-sample-button { + /* `inline-size: min-intrinsic` size makes sense, but it is specifically neccessary for WebKit. + * The button should be no larger than its content, but WebKit uses a larger default width than other engines. + */ + border-width: 0; + padding-block: 0; + padding-inline: 0; + line-height: 1; + display: flex; + inline-size: min-intrinsic; + block-size: min-intrinsic; + min-block-size: 44px; + min-inline-size: 44px; + + --utrecht-color-sample-inline-size: 100%; + --utrecht-color-sample-block-size: 44px; +} + +.component-button, +.component-button:hover, +.color-sample-list__button:hover, +.color-sample-button:hover { + outline: 2px solid black; + z-index: 1; +} + +.component-button:focus { + outline: 4px solid blue; + z-index: 1; +} + +.color-sample-grid { + --utrecht-color-sample-inline-size: 66px; +} +.color-sample-grid, +.color-contrast-table { + inline-size: fit-content; +} diff --git a/proprietary/purmerend-design-tokens/documentation/ColorSampleList.tsx b/proprietary/purmerend-design-tokens/documentation/ColorSampleList.tsx new file mode 100644 index 000000000..f85857cf9 --- /dev/null +++ b/proprietary/purmerend-design-tokens/documentation/ColorSampleList.tsx @@ -0,0 +1,261 @@ +import { + Button, + Code, + Heading, + HeadingGroup, + Paragraph, + Table, + TableBody, + TableCell, + TableHeader, + TableHeaderCell, + TableRow, +} from '@utrecht/component-library-react/dist/css-module'; +import { + calculateContrastRequirement, + getColorContrast, +} from '@nl-design-system-unstable/tokens-lib/dist/contrast-checker/check-colors'; +import './ColorSampleList.css'; +import { DesignToken } from '@nl-design-system-unstable/tokens-lib/dist/design-tokens'; +import { Fragment, PropsWithChildren } from 'react'; +import { ColorContrastCanvasElement } from './example-rendering.mjs'; +import { ColorSampleElement } from './color-sample.mjs'; + +ColorContrastCanvasElement.define(); +ColorSampleElement.define(); + +export const ColorSample = ({ color }: { color: string }) => ( + +); + +export const StorybookIsolation = ({ children }: PropsWithChildren) =>
{children}
; + +export const ComponentButton = ({ children }: PropsWithChildren) => ( + +); + +interface ColorSampleListProps { + tokens: DesignToken[]; + ordered?: boolean; +} + +export const ColorSampleList = ({ tokens, ordered }: ColorSampleListProps) => { + console.log(tokens); + const items = Object.values(tokens).map((token, index) => ( +
  • + {/* console.log(123)}> */} + + {/* */} +
  • + )); + const listProps = { + className: 'color-sample-list', + role: 'list', + style: {}, + }; + return ordered ?
      {items}
    : ; +}; + +export const ColorSampleGrid = ({ children }: PropsWithChildren) => { + return ( + + + + + Background + Interactive components + Borders and separators + Solid colors + Accessible text + + + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + + + {children} +
    + ); +}; + +export const ColorSampleGridRow = ({ name, tokens }: { name: string; tokens: DesignToken[] }) => { + const items = Object.values(tokens).map((token, index) => ( + + + + + + )); + const listProps = { + role: 'list', + style: {}, + }; + return ( + + {name} + {items} + + + + + ); +}; + +export const ColorContrastTable = ({ tokens }: DesignToken[]) => { + const map = [ + { type: 'background', vs: 11 }, + { type: 'background', vs: 12 }, + { type: 'inactive', vs: 1 }, + { type: 'inactive', vs: 1 }, + { type: 'inactive', vs: 1 }, + { type: 'border', vs: 1 }, + { type: 'border', vs: 1 }, + { type: 'border', vs: 1 }, + { type: 'solid', vs: 1 }, + { type: 'solid', vs: 1 }, + { type: 'text', vs: 1 }, + { type: 'text', vs: 1 }, + ]; + const tokensArray = Object.values(tokens); + const pairs = tokensArray + .map((token, index) => { + const data = map[index]; + const contrastToken = tokensArray[data.vs - 1]; + let pair = [token['$value'], contrastToken['$value']]; + + if (['border', 'solid', 'text'].includes(data.type)) { + pair.reverse(); + } + + return { + foreground: '', + background: '', + fontSize: 16, + foregroundColor: pair[1], + backgroundColor: pair[0], + type: ['border', 'inactive'].includes(data.type) ? 'non-functional' : 'functional', + }; + }) + .map(getColorContrast) + .map((x) => ({ + ...x, + aaaContrast: calculateContrastRequirement('', '', x.fontSize, 'AAA', false), + aaContrast: calculateContrastRequirement('', '', x.fontSize, 'AA', false), + aaa: x.contrast >= calculateContrastRequirement('', '', x.fontSize, 'AAA', false), + aa: x.contrast >= calculateContrastRequirement('', '', x.fontSize, 'AA', false), + })); + console.log(pairs); + return ( + <> + + + + name + color + contrast + result + + + + {Object.values(tokens).map((token, index) => ( + + {token['name']} + + console.log(123)}> + + + + {pairs[index].contrast.toPrecision(2)} + +
    + {pairs[index].aaa ? '🥇 AAA' : pairs[index].aa ? '👍 AA' : '🔴 FAIL'} +
    +
    +
    Voorgrond:
    +
    + + {String(pairs[index].foregroundColor)} +
    +
    +
    +
    Achtergrond:
    +
    + + {String(pairs[index].backgroundColor)} +
    +
    +
    + + + 16px: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut + labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris + nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit + esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt + in culpa qui officia deserunt mollit anim id est laborum. + + +
    +
    +
    + ))} +
    +
    + {Object.values(tokens).map((token, index) => ( +
    + + {token['name']} + {index === 0 ? Shade 1 is bedoeld als : null} + + {index === 0 ? Shade 1 is bedoeld als : null} +
    + ))} + + ); +}; + +interface ColorSamplePageProps { + scales: { + id: string; + label: string; + tokens: DesignToken[]; + }[]; +} + +export const ColorSamplePage = ({ scales }: ColorSamplePageProps) => { + return ( + + + {scales.map(({ id, label, tokens }) => ( + + ))} + + {scales.map(({ id, label, tokens }) => ( + + {label} + + + + ))} + + ); +}; diff --git a/proprietary/purmerend-design-tokens/documentation/basis-tokens.mdx b/proprietary/purmerend-design-tokens/documentation/basis-tokens.mdx new file mode 100644 index 000000000..99b56cfb2 --- /dev/null +++ b/proprietary/purmerend-design-tokens/documentation/basis-tokens.mdx @@ -0,0 +1,60 @@ +import { Meta } from "@storybook/blocks"; +import tokens from "../dist/tokens.json"; +import colorSchemeDark from "../dist/color-scheme-dark/tokens.json"; +import { ColorSamplePage } from "./ColorSampleList"; + + + +# Basis Tokens + + + +## Color scheme "dark" + + diff --git a/proprietary/purmerend-design-tokens/documentation/color-sample.mjs b/proprietary/purmerend-design-tokens/documentation/color-sample.mjs new file mode 100644 index 000000000..dd3c17b33 --- /dev/null +++ b/proprietary/purmerend-design-tokens/documentation/color-sample.mjs @@ -0,0 +1,69 @@ +import css from '@utrecht/color-sample-css/dist/index.mjs'; + +const sheet = new CSSStyleSheet(); +sheet.replaceSync(css); + +const extrasheet = new CSSStyleSheet(); +extrasheet.replaceSync(`:host { + display: inline-block; + block-size: var(--utrecht-color-sample-block-size, 2em); + inline-size: var(--utrecht-color-sample-inline-size, 2em); +} + svg { + display: block; + block-size: var(--utrecht-color-sample-block-size, 2em); + inline-size: var(--utrecht-color-sample-inline-size, 2em); +}`); + +export class ColorSampleElement extends HTMLElement { + static define() { + const name = 'nldesignsystem-color-sample'; + + if (customElements.get(name)) { + console.warn(`Custom element already registered: ${name}`); + } else { + customElements.define(name, ColorSampleElement); + } + } + + connectedCallback() { + if (this.shadowRoot) { + return; + } + + const color = this.getAttribute('color'); + const data = this.ownerDocument.createElement('data'); + data.value = color; + data.className = 'utrecht-color-sample'; + + // Create a hidden `span` HTML element for the label text, + // that we will associate with the image using `aria-labelledby`. + const labelId = crypto.randomUUID(); + const span = this.ownerDocument.createElement('span'); + span.id = labelId; + span.hidden = true; + span.textContent = this.hasAttribute('label') ? this.getAttribute('label') : color; + + // Create a `svg` element that will be an image completely filled with one color. + const svg = this.ownerDocument.createElementNS('http://www.w3.org/2000/svg', 'svg'); + svg.setAttribute('fill', color || 'var(--utrecht-color-sample-color, currentColor)'); + svg.setAttribute('width', '1em'); + svg.setAttribute('height', '1em'); + svg.setAttribute('role', 'image'); + svg.setAttribute('viewBox', '0 0 1 1'); + svg.setAttribute('aria-labelledby', labelId); + + // TODO: Make sure if the SVG is stretched outside of the viewbox, there is a rect outside the viewbox to fill the entire SVG. + const rect = this.ownerDocument.createElementNS('http://www.w3.org/2000/svg', 'rect'); + rect.setAttribute('height', '1'); + rect.setAttribute('width', '1'); + svg.appendChild(rect); + + const shadow = this.attachShadow({ mode: 'open' }); + shadow.adoptedStyleSheets = [sheet, extrasheet]; + shadow.appendChild(svg); + shadow.appendChild(span); + + this.appendChild(data); + } +} diff --git a/proprietary/purmerend-design-tokens/documentation/color.mdx b/proprietary/purmerend-design-tokens/documentation/color.mdx index e187ab816..560fc38ef 100644 --- a/proprietary/purmerend-design-tokens/documentation/color.mdx +++ b/proprietary/purmerend-design-tokens/documentation/color.mdx @@ -6,7 +6,7 @@ import config from "../src/config.json"; -# Color +# Color xxx ## Find a color diff --git a/proprietary/purmerend-design-tokens/documentation/example-rendering.mjs b/proprietary/purmerend-design-tokens/documentation/example-rendering.mjs new file mode 100644 index 000000000..9142af269 --- /dev/null +++ b/proprietary/purmerend-design-tokens/documentation/example-rendering.mjs @@ -0,0 +1,36 @@ +const sheet = new CSSStyleSheet(); +sheet.replaceSync(`.nldesignsystem-example-rendering { + -webkit-user-select: none; + cursor: default; + user-select: none; + forced-color-adjust: none; +}`); + +export class ColorContrastCanvasElement extends HTMLElement { + static define() { + const name = 'nldesignsystem-example-rendering'; + + if (customElements.get(name)) { + console.warn(`Custom element already registered: ${name}`); + } else { + customElements.define(name, ColorContrastCanvasElement); + } + } + + connectedCallback() { + if (this.shadowRoot) { + return; + } + + const div = this.ownerDocument.createElement('div'); + div.role = 'img'; + div.inert = true; + div.className = 'nldesignsystem-example-rendering'; + div.setAttribute('aria-label', this.getAttribute('label')); + div.appendChild(this.ownerDocument.createElement('slot')); + + const shadow = this.attachShadow({ mode: 'open' }); + shadow.adoptedStyleSheets = [sheet]; + shadow.appendChild(div); + } +} diff --git a/proprietary/purmerend-design-tokens/package.json b/proprietary/purmerend-design-tokens/package.json index a2254145d..8e393ef22 100644 --- a/proprietary/purmerend-design-tokens/package.json +++ b/proprietary/purmerend-design-tokens/package.json @@ -33,9 +33,11 @@ "@nl-design-system-unstable/basis-design-tokens": "workspace:*", "@nl-design-system-unstable/design-tokens-table-react": "workspace:*", "@nl-design-system-unstable/theme-toolkit": "workspace:*", + "@nl-design-system-unstable/tokens-lib": "workspace:*", "@storybook/react": "8.2.7", "@tokens-studio/sd-transforms": "1.2.3", "@types/react": "18.3.3", + "@utrecht/color-sample-css": "1.4.0", "@utrecht/component-library-react": "7.1.0", "@utrecht/components": "6.2.0", "chokidar-cli": "3.0.0",