Update hint block (#2840)

Co-authored-by: Valentino Hudhra <v.hudhra@gmail.com>
This commit is contained in:
Zeno Kapitein
2025-02-21 15:32:35 +01:00
committed by GitHub
parent 05e1d8cd96
commit 66d0fc0683
10 changed files with 285 additions and 146 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'gitbook': patch
---
Update design for hint block: use semantic colors (info, warning, danger, success) and add alternative styling for hints with headings
+6 -6
View File
@@ -24,7 +24,7 @@
},
"packages/colors": {
"name": "@gitbook/colors",
"version": "0.1.0",
"version": "0.2.0",
"devDependencies": {
"typescript": "^5.5.3",
},
@@ -38,9 +38,9 @@
},
"packages/gitbook": {
"name": "gitbook",
"version": "0.6.1",
"version": "0.6.2",
"dependencies": {
"@gitbook/api": "^0.94.0",
"@gitbook/api": "^0.95.0",
"@gitbook/cache-do": "workspace:*",
"@gitbook/colors": "workspace:*",
"@gitbook/emoji-codepoints": "workspace:*",
@@ -155,7 +155,7 @@
},
"packages/openapi-parser": {
"name": "@gitbook/openapi-parser",
"version": "1.0.0",
"version": "1.0.1",
"dependencies": {
"@scalar/openapi-parser": "^0.10.4",
"@scalar/openapi-types": "^0.1.6",
@@ -211,7 +211,7 @@
},
"packages/react-openapi": {
"name": "@gitbook/react-openapi",
"version": "1.0.1",
"version": "1.0.2",
"dependencies": {
"@gitbook/openapi-parser": "workspace:*",
"@scalar/api-client-react": "1.0.87",
@@ -4651,7 +4651,7 @@
"gaxios/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
"gitbook/@gitbook/api": ["@gitbook/api@0.94.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-jOvqUSdyXeuPpBiujkQLb14uVQA5A0XL+P89MmC/53hV7v/8gR8WlJN9RJVDrP0LX51dsLT+/zYN8xWp19nPwA=="],
"gitbook/@gitbook/api": ["@gitbook/api@0.95.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-9KAbt27Ile6cqAch7QEbiJHALQHojYlhsPzilgdQ5wpHgLwsrd7Smd58A3/8bWBKq4KV0vP4rh3oYhIw+LlWFw=="],
"gitbook-v2/next": ["next@15.2.0-canary.45", "", { "dependencies": { "@next/env": "15.2.0-canary.45", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.2.0-canary.45", "@next/swc-darwin-x64": "15.2.0-canary.45", "@next/swc-linux-arm64-gnu": "15.2.0-canary.45", "@next/swc-linux-arm64-musl": "15.2.0-canary.45", "@next/swc-linux-x64-gnu": "15.2.0-canary.45", "@next/swc-linux-x64-musl": "15.2.0-canary.45", "@next/swc-win32-arm64-msvc": "15.2.0-canary.45", "@next/swc-win32-x64-msvc": "15.2.0-canary.45", "sharp": "^0.33.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-UsneTQn9tntbiAaXpvoXhhsTBb58Q2XIs2Dfka+qWA8motBz0ZvW297YHLxhdur4xN0IJvknnZKl5Bs7wAGlOg=="],
+5 -1
View File
@@ -14,7 +14,7 @@ import {
CustomizationThemeMode,
SiteCustomizationSettings,
} from '@gitbook/api';
import { test, expect, Page, BrowserContext } from '@playwright/test';
import { BrowserContext, expect, Page, test } from '@playwright/test';
import deepMerge from 'deepmerge';
import rison from 'rison';
import { DeepPartial } from 'ts-essentials';
@@ -222,6 +222,10 @@ export function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettin
styling: {
theme: newTheme,
primaryColor: { light: '#346DDB', dark: '#346DDB' },
infoColor: { light: '#787878', dark: '#787878' },
warningColor: { light: '#FE9A00', dark: '#FE9A00' },
dangerColor: { light: '#FB2C36', dark: '#FB2C36' },
successColor: { light: '#00C950', dark: '#00C950' },
corners: CustomizationCorners.Rounded,
font: CustomizationFont.Inter,
background: CustomizationBackground.Plain,
+1 -1
View File
@@ -17,7 +17,7 @@
"clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static/icons && rm -rf ./public/~gitbook/static/math"
},
"dependencies": {
"@gitbook/api": "^0.94.0",
"@gitbook/api": "^0.95.0",
"@gitbook/cache-do": "workspace:*",
"@gitbook/colors": "workspace:*",
"@gitbook/emoji-codepoints": "workspace:*",
@@ -1,9 +1,9 @@
import { DocumentBlockHeading } from '@gitbook/api';
import type { DocumentBlockHeading } from '@gitbook/api';
import { Icon } from '@gitbook/icons';
import { tcls } from '@/lib/tailwind';
import { BlockProps } from './Block';
import type { BlockProps } from './Block';
import { Inlines } from './Inlines';
import { getBlockTextStyle } from './spacing';
@@ -24,6 +24,7 @@ export function Heading(props: BlockProps<DocumentBlockHeading>) {
>
<div
className={tcls(
'hash',
'grid',
'grid-area-1-1',
'relative',
@@ -41,20 +42,14 @@ export function Heading(props: BlockProps<DocumentBlockHeading>) {
<a
href={`#${id}`}
aria-label="Direct link to heading"
className={tcls(
'inline-flex',
'h-full',
'items-start',
'dark:shadow-none',
'dark:ring-0',
textStyle.lineHeight,
)}
className={tcls('inline-flex', 'h-full', 'items-start', textStyle.lineHeight)}
>
<Icon
icon="hashtag"
className={tcls(
'w-3.5',
'h-[1lh]',
'h-[1em]',
'mt-0.5',
'transition-colors',
'text-transparent',
'group-hover:text-tint-subtle',
@@ -1,10 +1,10 @@
import { DocumentBlockHint } from '@gitbook/api';
import { Icon, IconName } from '@gitbook/icons';
import type { DocumentBlockHeading, DocumentBlockHint } from '@gitbook/api';
import { Icon, type IconName } from '@gitbook/icons';
import React from 'react';
import { ClassValue, tcls } from '@/lib/tailwind';
import { type ClassValue, tcls } from '@/lib/tailwind';
import { BlockProps } from './Block';
import { Block, type BlockProps } from './Block';
import { Blocks } from './Blocks';
import { getBlockTextStyle } from './spacing';
@@ -13,41 +13,73 @@ export function Hint(props: BlockProps<DocumentBlockHint>) {
const hintStyle = HINT_STYLES[block.data.style] ?? HINT_STYLES.info;
const firstLine = getBlockTextStyle(block.nodes[0]);
const firstNode = block.nodes[0];
const hasHeading = ['heading-1', 'heading-2', 'heading-3'].includes(block.nodes[0].type);
return (
<div
className={tcls(
'hint',
'p-4',
'transition-colors',
'rounded-md',
hasHeading ? 'rounded-l' : null,
'straight-corners:rounded-none',
hintStyle.style,
'overflow-hidden',
hasHeading ? ['border-l-2', hintStyle.containerWithHeader] : hintStyle.container,
'text-sm',
'grid',
'grid-cols-[auto_1fr]',
hasHeading ? 'grid-rows-[auto_auto]' : '',
style,
)}
>
<div className={tcls('flex', 'flex-row')}>
<div
className={tcls(
'py-3',
'pl-3',
hasHeading ? hintStyle.header : null,
hintStyle.iconColor,
)}
>
<Icon
icon={hintStyle.icon}
className={tcls(
'size-5',
'mr-4',
'mt-0.5',
firstLine.lineHeight,
hintStyle.iconColor,
)}
/>
<Blocks
{...contextProps}
ancestorBlocks={[...ancestorBlocks, block]}
nodes={block.nodes}
blockStyle={tcls(
hintStyle.bodyColor,
// render hash icon on the other side of the heading
'flip-heading-hash',
)}
style={['flex-1', 'space-y-4', '[&_.hint]:border', '[&_pre]:border']}
className={tcls('size-[1.2em]', 'mt-0.5', firstLine.lineHeight)}
/>
</div>
{hasHeading ? (
<Block
style={tcls(
'text-[1em] *:mt-0 p-3 flip-heading-hash',
hasHeading ? hintStyle.header : null,
)}
ancestorBlocks={[...ancestorBlocks, block]}
{...contextProps}
block={firstNode}
/>
) : null}
<Blocks
{...contextProps}
ancestorBlocks={[...ancestorBlocks, block]}
nodes={hasHeading ? block.nodes.slice(1) : block.nodes}
blockStyle={tcls(
hintStyle.body,
// render hash icon on the other side of the heading
'flip-heading-hash',
)}
style={[
'p-3',
'empty:p-0',
'-row-end-1',
'-col-end-1',
'space-y-4',
'[&_.hint]:border',
'[&_pre]:border',
'[&_pre]:border-neutral',
]}
/>
</div>
);
}
@@ -55,77 +87,69 @@ export function Hint(props: BlockProps<DocumentBlockHint>) {
const HINT_STYLES: {
[style in DocumentBlockHint['data']['style']]: {
icon: IconName;
iconColor: ClassValue;
bodyColor: ClassValue;
style: ClassValue;
iconColor?: ClassValue;
body?: ClassValue;
header?: ClassValue;
container?: ClassValue;
containerWithHeader?: ClassValue;
};
} = {
info: {
icon: 'circle-info',
iconColor: ['text-primary'],
bodyColor: ['[&_a]:text-primary', '[&_a:hover]:text-primary-strong'],
style: [
'bg-tint',
'print-mode:!bg-tint',
'theme-muted:bg-tint-base',
'theme-bold-tint:bg-tint-base',
'theme-gradient:bg-tint-12/1',
'border-tint',
'[&_.can-override-bg]:bg-tint-active',
'[&_.can-override-text]:text-tint-strong',
iconColor: 'text-info-subtle contrast-more:text-info',
header: 'bg-info-active',
body: [
'text-neutral-strong',
'[&_.can-override-bg]:bg-neutral-active',
'[&_.can-override-text]:text-neutral-strong',
],
container:
'bg-neutral theme-muted:bg-neutral-base theme-bold-tint:bg-neutral-base theme-gradient:bg-neutral-12/1 border-neutral',
containerWithHeader: 'border-info-solid bg-neutral-subtle theme-gradient:bg-neutral-12/1',
},
warning: {
icon: 'circle-exclamation',
iconColor: ['text-amber-500', 'dark:text-orange-400'], // Darker shades of orange-* mismatch with lighter shades, so in light mode we use amber text on top of orange bg.
bodyColor: [
'text-orange-950',
'dark:text-orange-50',
'[&_a]:text-orange-800',
'[&_a:hover]:text-orange-900',
'dark:[&_a]:text-orange-400',
'dark:[&_a:hover]:text-orange-300',
'[&_.can-override-bg]:bg-orange-500/3',
'[&_.can-override-text]:text-orange-800',
'dark:[&_.can-override-text]:text-orange-400',
'decoration-orange-800/6',
'dark:decoration-orange-400/6',
iconColor: 'text-warning-subtle contrast-more:text-warning',
header: 'bg-warning-active',
body: [
'text-neutral-strong',
'[&_a]:text-warning',
'[&_a:hover]:text-warning-strong',
'[&_.can-override-bg]:bg-warning-active',
'[&_.can-override-text]:text-warning-strong',
'decoration-warning/6',
],
style: ['bg-orange-500/2', 'border-orange-500/4'],
container: 'bg-warning border-warning',
containerWithHeader: 'border-warning-solid bg-warning-subtle',
},
danger: {
icon: 'triangle-exclamation',
iconColor: ['text-red-500', 'dark:text-red-400'],
bodyColor: [
'text-red-950',
'dark:text-red-50',
'[&_a]:text-red-800',
'[&_a:hover]:text-red-900',
'dark:[&_a]:text-red-400',
'dark:[&_a:hover]:text-red-300',
'[&_.can-override-bg]:bg-red-500/3',
'[&_.can-override-text]:text-red-400',
'decoration-red-800/6',
'dark:decoration-red-400/6',
iconColor: 'text-danger-subtle contrast-more:text-danger',
header: 'bg-danger-active',
body: [
'text-neutral-strong',
'[&_a]:text-danger',
'[&_a:hover]:text-danger-strong',
'[&_.can-override-bg]:bg-danger-active',
'[&_.can-override-text]:text-danger-strong',
'decoration-danger/6',
],
style: ['bg-red-500/2', 'border-red-500/4'],
container: 'bg-danger border-danger',
containerWithHeader: 'border-danger-solid bg-danger-subtle',
},
success: {
icon: 'circle-check',
iconColor: ['text-green-500', 'dark:text-green-400'],
bodyColor: [
'text-green-950',
'dark:text-green-50',
'[&_a]:text-green-800',
'[&_a:hover]:text-green-900',
'dark:[&_a]:text-green-400',
'dark:[&_a:hover]:text-green-300',
'[&_.can-override-bg]:bg-green-500/3',
'[&_.can-override-text]:text-green-800',
'dark:[&_.can-override-text]:text-green-400',
'decoration-green-800/6',
'dark:decoration-green-400/6',
iconColor: 'text-success-subtle contrast-more:text-success',
header: 'bg-success-active',
body: [
'text-neutral-strong',
'[&_a]:text-success',
'[&_a:hover]:text-success-strong',
'[&_.can-override-bg]:bg-success-active',
'[&_.can-override-text]:text-success-strong',
'decoration-success/6',
],
style: ['bg-green-500/2', 'border-green-500/4'],
container: 'bg-success border-success',
containerWithHeader: 'border-success-solid bg-success-subtle',
},
};
@@ -14,6 +14,10 @@ import {
colorScale,
type ColorScaleOptions,
DEFAULT_TINT_COLOR,
DEFAULT_HINT_INFO_COLOR,
DEFAULT_HINT_SUCCESS_COLOR,
DEFAULT_HINT_DANGER_COLOR,
DEFAULT_HINT_WARNING_COLOR,
hexToRgb,
} from '@gitbook/colors';
import { IconsProvider, IconStyle } from '@gitbook/icons';
@@ -42,6 +46,7 @@ export async function CustomizationRootLayout(props: {
const tintColor = getTintColor(customization);
const mixColor = getTintMixColor(customization.styling.primaryColor, tintColor);
const sidebarStyles = getSidebarStyles(customization);
const { infoColor, successColor, warningColor, dangerColor } = getSemanticColors(customization);
return (
<html
@@ -85,6 +90,11 @@ export async function CustomizationRootLayout(props: {
)
};
--header-link: ${hexToRgb(customization.header.linkColor?.light ?? colorContrast(tintColor?.light ?? customization.styling.primaryColor.light))};
${generateColorVariable('info', infoColor.light)}
${generateColorVariable('warning', warningColor.light)}
${generateColorVariable('danger', dangerColor.light)}
${generateColorVariable('success', successColor.light)}
}
.dark {
@@ -94,6 +104,11 @@ export async function CustomizationRootLayout(props: {
--header-background: ${hexToRgb(customization.header.backgroundColor?.dark ?? tintColor?.dark ?? customization.styling.primaryColor.dark)};
--header-link: ${hexToRgb(customization.header.linkColor?.dark ?? colorContrast(tintColor?.dark ?? customization.styling.primaryColor.dark))};
${generateColorVariable('info', infoColor.dark, { darkMode: true })}
${generateColorVariable('warning', warningColor.dark, { darkMode: true })}
${generateColorVariable('danger', dangerColor.dark, { darkMode: true })}
${generateColorVariable('success', successColor.dark, { darkMode: true })}
}
`}</style>
</head>
@@ -198,6 +213,45 @@ function getSidebarStyles(
};
}
/**
* Get the semnatic color customization settings.
* If it is a space customization, it will return the default styles.
*/
function getSemanticColors(
customization: CustomizationSettings | SiteCustomizationSettings,
): Pick<
SiteCustomizationSettings['styling'],
'infoColor' | 'successColor' | 'warningColor' | 'dangerColor'
> {
if ('infoColor' in customization.styling) {
return {
infoColor: customization.styling.infoColor,
successColor: customization.styling.successColor,
warningColor: customization.styling.warningColor,
dangerColor: customization.styling.dangerColor,
};
}
return {
infoColor: {
light: DEFAULT_HINT_INFO_COLOR,
dark: DEFAULT_HINT_INFO_COLOR,
},
successColor: {
light: DEFAULT_HINT_SUCCESS_COLOR,
dark: DEFAULT_HINT_SUCCESS_COLOR,
},
warningColor: {
light: DEFAULT_HINT_WARNING_COLOR,
dark: DEFAULT_HINT_WARNING_COLOR,
},
dangerColor: {
light: DEFAULT_HINT_DANGER_COLOR,
dark: DEFAULT_HINT_DANGER_COLOR,
},
};
}
type ColorInput = string;
function generateColorVariable(
name: string,
-36
View File
@@ -208,42 +208,6 @@ export const getUserById = cache({
},
});
/**
* Get the latest version of an OpenAPI spec by its slug.
*/
export const getLatestOpenAPISpecVersion = cache({
name: 'api.getLatestOpenApiSpecVersion',
tag: (organization, openAPISpec) =>
getAPICacheTag({
tag: 'openapi',
organization,
openAPISpec,
}),
get: async (organizationId: string, slug: string, options: CacheFunctionOptions) => {
try {
const apiCtx = await api();
const response = await apiCtx.client.orgs.getLatestOpenApiSpecVersion(
organizationId,
slug,
{
...noCacheFetchOptions,
signal: options.signal,
},
);
return cacheResponse(response, { revalidateBefore: 60 * 60 });
} catch (error) {
if (checkHasErrorCode(error, 404)) {
return {
revalidateBefore: 5,
data: null,
};
}
throw error;
}
},
});
/**
* Get the latest version of an OpenAPI spec by its slug.
*/
+7 -8
View File
@@ -18,7 +18,6 @@ import {
SpaceContentPointer,
getCollection,
getDocument,
getLatestOpenAPISpecVersion,
getLatestOpenAPISpecVersionContent,
getPageDocument,
getPublishedContentSite,
@@ -282,19 +281,19 @@ export async function resolveContentRef(
return null;
}
const { organizationId } = siteContext;
const [openAPISpecVersion, openAPISpecVersionContent] = await Promise.all([
getLatestOpenAPISpecVersion(organizationId, contentRef.spec),
getLatestOpenAPISpecVersionContent(organizationId, contentRef.spec),
]);
const openAPISpecVersionContent = await getLatestOpenAPISpecVersionContent(
organizationId,
contentRef.spec,
);
if (!openAPISpecVersion || !openAPISpecVersionContent) {
if (!openAPISpecVersionContent) {
return null;
}
return {
href: openAPISpecVersion.url,
href: openAPISpecVersionContent.url,
text: contentRef.spec,
active: false,
openAPIFilesystem: openAPISpecVersionContent as Filesystem,
openAPIFilesystem: openAPISpecVersionContent.filesystem as Filesystem,
};
}
+97 -3
View File
@@ -8,6 +8,8 @@ import { ColorCategory, hexToRgb, scale, shadesOfColor } from '@gitbook/colors';
export const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
export const opacities = [0, 4, 8, 12, 16, 24, 40, 64, 72, 88, 96, 100];
export const semanticColors = ['info', 'warning', 'danger', 'success'];
/**
* Generate a Tailwind color shades from a variable.
*/
@@ -100,6 +102,18 @@ const config: Config = {
'header-background': 'rgb(var(--header-background))',
'header-link': 'rgb(var(--header-link))',
// Add each semantic color
...Object.fromEntries(
semanticColors.map((color) => [color, generateVarShades(color)]),
),
...Object.fromEntries(
semanticColors.map((color) => [
`contrast-${color}`,
generateVarShades(`contrast-${color}`),
]),
),
yellow: generateShades('#f4e28d'),
teal: generateShades('#3f89a1'),
pomegranate: generateShades('#f25b3a'),
@@ -127,6 +141,17 @@ const config: Config = {
ColorCategory.components,
ColorCategory.accents,
]),
// Semantic colors
...Object.fromEntries(
semanticColors.map((color) => [
color,
generateVarShades(color, [
ColorCategory.backgrounds,
ColorCategory.components,
ColorCategory.accents,
]),
]),
),
},
gradientColorStops: {
primary: generateVarShades('primary', [
@@ -144,26 +169,71 @@ const config: Config = {
ColorCategory.components,
ColorCategory.accents,
]),
// Semantic colors
...Object.fromEntries(
semanticColors.map((color) => [
color,
generateVarShades(color, [
ColorCategory.backgrounds,
ColorCategory.components,
ColorCategory.accents,
]),
]),
),
},
borderColor: {
primary: generateVarShades('primary', [ColorCategory.borders]),
tint: generateVarShades('tint', [ColorCategory.borders]),
neutral: generateVarShades('neutral', [ColorCategory.borders]),
primary: generateVarShades('primary', [
ColorCategory.borders,
ColorCategory.accents,
]),
tint: generateVarShades('tint', [ColorCategory.borders, ColorCategory.accents]),
neutral: generateVarShades('neutral', [
ColorCategory.borders,
ColorCategory.accents,
]),
// Semantic colors
...Object.fromEntries(
semanticColors.map((color) => [
color,
generateVarShades(color, [ColorCategory.borders, ColorCategory.accents]),
]),
),
},
ringColor: {
primary: generateVarShades('primary', [ColorCategory.borders]),
tint: generateVarShades('tint', [ColorCategory.borders]),
neutral: generateVarShades('neutral', [ColorCategory.borders]),
// Semantic colors
...Object.fromEntries(
semanticColors.map((color) => [
color,
generateVarShades(color, [ColorCategory.borders]),
]),
),
},
outlineColor: {
primary: generateVarShades('primary', [ColorCategory.borders]),
tint: generateVarShades('tint', [ColorCategory.borders]),
neutral: generateVarShades('neutral', [ColorCategory.borders]),
// Semantic colors
...Object.fromEntries(
semanticColors.map((color) => [
color,
generateVarShades(color, [ColorCategory.borders]),
]),
),
},
boxShadowColor: {
primary: generateVarShades('primary', [ColorCategory.borders]),
tint: generateVarShades('tint', [ColorCategory.borders]),
neutral: generateVarShades('neutral', [ColorCategory.borders]),
// Semantic colors
...Object.fromEntries(
semanticColors.map((color) => [
color,
generateVarShades(color, [ColorCategory.borders]),
]),
),
},
textColor: {
primary: generateVarShades('primary', [ColorCategory.text]),
@@ -181,6 +251,18 @@ const config: Config = {
ColorCategory.backgrounds,
ColorCategory.accents,
]),
...Object.fromEntries(
semanticColors.flatMap((color) => [
[color, generateVarShades(color, [ColorCategory.text])],
[
`contrast-${color}`,
generateVarShades(`contrast-${color}`, [
ColorCategory.backgrounds,
ColorCategory.accents,
]),
],
]),
),
},
textDecorationColor: {
primary: generateVarShades('primary', [ColorCategory.text]),
@@ -198,6 +280,18 @@ const config: Config = {
ColorCategory.backgrounds,
ColorCategory.accents,
]),
...Object.fromEntries(
semanticColors.flatMap((color) => [
[color, generateVarShades(color, [ColorCategory.text])],
[
`contrast-${color}`,
generateVarShades(`contrast-${color}`, [
ColorCategory.backgrounds,
ColorCategory.accents,
]),
],
]),
),
},
animation: {
present: 'present .5s ease-out both',