Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | 2x 15136x 15136x 15136x 11418x 11418x 792x 11418x 11418x 11418x 11418x 11418x 11418x 1980x 1980x 11418x 11418x 11418x 12155x 1100x 19558x 16753x 4752x 4752x 4752x 12155x 22x 22x 22x 15719x 11418x 11418x 11418x 15719x 15719x 220x 22x 22x 1100x 1100x 1100x 1100x 1100x 1100x 11x 22x 25526x 21x 21x 1050x 210x 210x 210x 1050x 21x 21x 1050x 1050x 7219x 1050x 1050x 10904x 10904x 10904x 10904x 1050x 21x | import {
cssDeclaration,
firstReference,
formatPx,
formatRem,
getThemeTokens,
hasModes,
hookFromReference,
type FlatToken,
type Mode,
modeDeclaration,
paletteAliasFor,
paletteHookFromAlias,
paletteNameFromAlias,
resolveValue,
type Theme,
THEMES,
type TokenMap,
toPx,
TOKENS_SOURCE,
} from '@/lib/design-tokens';
import { type ChipId, CHIPS, groupForHook, HOOK_GROUPS, type HookDisplay, type HookGroupConfig } from '@/lib/styling-hook-groups';
/**
* Build-time data for the Styling Hook Index page, read from
* the `@salesforce-ux/design-tokens` flat JSON. Every display string is
* computed here so the client components only filter and render.
*/
// ── Styling Hook Index ──
export interface ModeCell {
/** Fully resolved CSS value for this mode, e.g. `#fff` or a box-shadow list. */
value: string;
/** Palette step the value comes from, e.g. `Neutral 100`. */
paletteName?: string;
/** The hook this mode's declaration points at, e.g. `--slds-r-color-brand-40`. */
references?: string;
/** The palette hook the value ends at, e.g. `--slds-g-color-palette-neutral-100`. */
resolves?: string;
}
export interface DensityValues {
comfy: string;
compact: string;
/** The hook the comfy (default) value references. */
reference?: string;
}
export interface HookRow {
name: string;
description?: string;
deprecated: boolean;
/** The declaration as the token CSS writes it. */
declaration: string;
light: ModeCell;
/** Only when the hook has a distinct dark value. */
dark?: ModeCell;
px?: string;
rem?: string;
density?: DensityValues;
}
/** A config group with its rows; `match` is dropped so the data stays serializable. */
export interface HookGroup extends Omit<HookGroupConfig, 'match'> {
rows: HookRow[];
}
export interface ThemeIndex {
groups: HookGroup[];
total: number;
chipCounts: Record<ChipId, number>;
}
export interface StylingHookIndexData {
source: string;
themes: Record<Theme, ThemeIndex>;
}
const LENGTH_DISPLAYS = new Set<HookDisplay>(['spacing', 'dimension', 'density', 'radius', 'border-width', 'font-scale']);
function modeCell(tokens: TokenMap, token: FlatToken, mode: Mode): ModeCell {
const alias = paletteAliasFor(tokens, token, mode);
const resolves = paletteHookFromAlias(alias);
return {
value: resolveValue(tokens, `var(--${token.name})`, mode),
paletteName: paletteNameFromAlias(alias),
references: firstReference(modeDeclaration(token, mode)),
resolves: resolves === `--${token.name}` ? undefined : resolves,
};
}
function densityValues(token: FlatToken): DensityValues | undefined {
const density = token.$extensions?.['com.salesforce-ux.density'];
if (!density?.comfy || !density.compact) return undefined;
return {
comfy: density.comfy.$value,
compact: density.compact.$value,
reference: hookFromReference(density.comfy.reference),
};
}
function hookRow(tokens: TokenMap, token: FlatToken, display: HookDisplay): HookRow {
const light = modeCell(tokens, token, 'light');
const hasDark = hasModes(token) || resolveValue(tokens, `var(--${token.name})`).includes('light-dark(');
const row: HookRow = {
name: `--${token.name}`,
description: token.description,
deprecated: Boolean(token.deprecated),
declaration: cssDeclaration(token),
light,
};
if (hasDark) row.dark = modeCell(tokens, token, 'dark');
const px = LENGTH_DISPLAYS.has(display) ? toPx(light.value) : undefined;
if (px !== undefined) {
row.px = formatPx(px);
row.rem = formatRem(px);
}
const density = densityValues(token);
if (density) row.density = density;
return row;
}
const byName = (a: HookRow, b: HookRow) => a.name.localeCompare(b.name, 'en', { numeric: true });
/** Current hooks first, then deprecated; lengths by size, everything else by name. */
function compareRows(display: HookDisplay) {
return (a: HookRow, b: HookRow): number => {
if (a.deprecated !== b.deprecated) return a.deprecated ? 1 : -1;
if (LENGTH_DISPLAYS.has(display)) {
const pa = a.px === undefined ? Infinity : parseFloat(a.px);
const pb = b.px === undefined ? Infinity : parseFloat(b.px);
if (pa !== pb) return pa - pb;
}
return byName(a, b);
};
}
function buildThemeIndex(theme: Theme): ThemeIndex {
const tokens = getThemeTokens(theme);
const rowsByGroup = new Map<string, HookRow[]>();
for (const token of Object.values(tokens)) {
if (token.scope !== 'global') continue;
const group = groupForHook(token.name);
Iif (!group) continue;
const rows = rowsByGroup.get(group.id) ?? [];
rows.push(hookRow(tokens, token, group.display));
rowsByGroup.set(group.id, rows);
}
const chipCounts = Object.fromEntries(CHIPS.map((c) => [c.id, 0])) as Record<ChipId, number>;
const groups: HookGroup[] = [];
for (const { match: _match, ...config } of HOOK_GROUPS) {
const rows = rowsByGroup.get(config.id);
Iif (!rows?.length) continue;
rows.sort(compareRows(config.display));
chipCounts[config.chip] += rows.length;
groups.push({ ...config, rows });
}
return { groups, total: groups.reduce((n, g) => n + g.rows.length, 0), chipCounts };
}
export function getStylingHookIndexData(): StylingHookIndexData {
return {
source: TOKENS_SOURCE,
themes: Object.fromEntries(THEMES.map(({ value }) => [value, buildThemeIndex(value)])) as Record<
Theme,
ThemeIndex
>,
};
}
const escapeCell = (text: string) => text.replace(/\|/g, '\\|').replace(/\n/g, ' ');
/** The index as Markdown tables, one per group, for the page's Copy Markdown action. */
export function stylingHookIndexMarkdown(index: ThemeIndex, title: string): string {
const lines = [`# ${title}`, ''];
let section: string | undefined;
let subsection: string | undefined;
for (const group of index.groups) {
if (group.section !== section) {
section = group.section;
subsection = undefined;
lines.push(`## ${section}`, '');
}
if (group.subsection && group.subsection !== subsection) {
subsection = group.subsection;
lines.push(`### ${subsection}`, '');
}
lines.push(`${group.subsection ? '####' : '###'} ${group.heading}`, '');
if (group.description) lines.push(group.description, '');
const withDark = group.rows.some((r) => r.dark);
lines.push(
withDark ? '| Styling Hook | Description | Light | Dark |' : '| Styling Hook | Description | Value |',
withDark ? '| --- | --- | --- | --- |' : '| --- | --- | --- |',
);
for (const row of group.rows) {
const name = `\`${row.name}\`${row.deprecated ? ' (deprecated)' : ''}`;
const cells = [name, escapeCell(row.description ?? ''), `\`${escapeCell(row.light.value)}\``];
if (withDark) cells.push(row.dark ? `\`${escapeCell(row.dark.value)}\`` : '');
lines.push(`| ${cells.join(' | ')} |`);
}
lines.push('');
}
return lines.join('\n').trimEnd() + '\n';
}
|