Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 8 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,14 @@ export default tseslint.config(
'jest/expect-expect': 'off',
},
},
// Reassure perf benchmarks use `measurePerformance` as their assertion; they intentionally
// have no `expect()` calls, so jest/expect-expect does not apply.
{
files: ['**/*.perf-test.{ts,tsx}'],
rules: {
'jest/expect-expect': 'off',
},
},
{
files: ['packages/migrator/src/transforms/**/*.ts'],
ignores: ['packages/migrator/src/transforms/**/__tests__/**'],
Expand Down
3 changes: 2 additions & 1 deletion figma.config.mobile.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"label": "CDS Mobile",
"language": "jsx",
"include": [
"packages/mobile/src/**/*.figma.ts"
"packages/mobile/src/**/*.figma.ts",
"packages/mobile/src/**/*.figma.batch.json"
],
"exclude": [
"**/__tests__/**",
Expand Down
3 changes: 2 additions & 1 deletion figma.config.web.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"label": "CDS Web",
"language": "jsx",
"include": [
"packages/web/src/**/*.figma.ts"
"packages/web/src/**/*.figma.ts",
"packages/web/src/**/*.figma.batch.json"
],
"exclude": [
"**/__tests__/**",
Expand Down
2 changes: 1 addition & 1 deletion libs/docusaurus-plugin-kbar/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"projectType": "library",
"targets": {
"build": {
"command": "rm -rf cjs && babel ./src --out-dir cjs --extensions .ts,.tsx,.js,.jsx --copy-files --no-copy-ignored"
"command": "rm -rf cjs && babel ./src --out-dir cjs --extensions .ts,.tsx,.js,.jsx"
},
"lint": {
"executor": "@nx/eslint:lint"
Expand Down
2 changes: 1 addition & 1 deletion libs/docusaurus-plugin-llm-dev-server/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"projectType": "library",
"targets": {
"build": {
"command": "rm -rf cjs && babel ./src --out-dir cjs --extensions .ts,.tsx,.js,.jsx --copy-files --no-copy-ignored"
"command": "rm -rf cjs && babel ./src --out-dir cjs --extensions .ts,.tsx,.js,.jsx"
},
"lint": {
"executor": "@nx/eslint:lint"
Expand Down
2 changes: 1 addition & 1 deletion libs/figma-api/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"projectType": "library",
"targets": {
"build": {
"command": "rm -rf cjs && babel ./src --out-dir cjs --extensions .ts,.tsx,.js,.jsx --copy-files --no-copy-ignored"
"command": "rm -rf cjs && babel ./src --out-dir cjs --extensions .ts,.tsx,.js,.jsx"
},
"lint": {
"executor": "@nx/eslint:lint"
Expand Down
2 changes: 1 addition & 1 deletion libs/web-utils/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"tags": [],
"targets": {
"build": {
"command": "rm -rf cjs && babel ./src --out-dir cjs --extensions .ts,.tsx,.js,.jsx --copy-files --no-copy-ignored"
"command": "rm -rf cjs && babel ./src --out-dir cjs --extensions .ts,.tsx,.js,.jsx"
},
"lint": {
"executor": "@nx/eslint:lint"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"code-connect:publish": "yarn code-connect:publish:web && yarn code-connect:publish:mobile",
"code-connect:publish:web": "figma connect publish --config figma.config.web.json --exit-on-unreadable-files --force --batch-size 50",
"code-connect:publish:mobile": "figma connect publish --config figma.config.mobile.json --exit-on-unreadable-files --force --batch-size 50",
"perf:component-config": "yarn exec jest --config packages/web/jest.config.js --runTestsByPath packages/web/src/perf/component-config/Button.component-config.perf-test.tsx packages/web/src/perf/component-config/ComponentConfigProvider.perf-test.tsx packages/web/src/perf/component-config/ComponentConfigStickerSheet.perf-test.tsx --testMatch='**/*.perf-test.tsx' && yarn exec jest --config packages/mobile/jest.config.js --runTestsByPath packages/mobile/src/perf/component-config/Button.component-config.perf-test.tsx packages/mobile/src/perf/component-config/ComponentConfigProvider.perf-test.tsx packages/mobile/src/perf/component-config/ComponentConfigStickerSheet.perf-test.tsx --testMatch='**/*.perf-test.tsx'"
"perf:component-config": "yarn exec jest --config packages/web/jest.config.js --runTestsByPath packages/web/src/__tests__/perf/component-config/Button.component-config.perf-test.tsx packages/web/src/__tests__/perf/component-config/ComponentConfigProvider.perf-test.tsx packages/web/src/__tests__/perf/component-config/ComponentConfigStickerSheet.perf-test.tsx --testMatch='**/*.perf-test.tsx' && yarn exec jest --config packages/mobile/jest.config.js --runTestsByPath packages/mobile/src/__tests__/perf/component-config/Button.component-config.perf-test.tsx packages/mobile/src/__tests__/perf/component-config/ComponentConfigProvider.perf-test.tsx packages/mobile/src/__tests__/perf/component-config/ComponentConfigStickerSheet.perf-test.tsx --testMatch='**/*.perf-test.tsx'"
},
"resolutions": {
"@types/react": "19.1.2",
Expand Down
4 changes: 2 additions & 2 deletions packages/common/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
"defaultConfiguration": "dev",
"configurations": {
"dev": {
"command": "rm -rf esm && babel ./src --out-dir esm --extensions .ts,.tsx,.js,.jsx --copy-files --no-copy-ignored"
"command": "rm -rf esm && babel ./src --out-dir esm --extensions .ts,.tsx,.js,.jsx"
},
"prod": {
"commands": [
"rm -rf esm && babel ./src --out-dir esm --extensions .ts,.tsx,.js,.jsx --copy-files --no-copy-ignored"
"rm -rf esm && babel ./src --out-dir esm --extensions .ts,.tsx,.js,.jsx"
],
"parallel": false
}
Expand Down
2 changes: 1 addition & 1 deletion packages/icons/DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ yarn install
yarn nx run icons:sync-icons
```

> As part of the sync, `sync-icons` also regenerates the Figma Code Connect mappings by calling `sync-icon-code-connect`. This writes one parser-less template file per icon (`<iconName>.figma.ts`) into `packages/web/src/icons/__figma__/` and `packages/mobile/src/icons/__figma__/`, replacing any previously generated templates. It can also be run on its own:
> As part of the sync, `sync-icons` also regenerates the Figma Code Connect mappings by calling `sync-icon-code-connect`. This writes a single [Code Connect batch integration](https://developers.figma.com/docs/code-connect/batch-files/) per package — a shared `icons.figma.batch.ts` template plus an `icons.figma.batch.json` listing every icon — into `packages/web/src/icons/__figma__/` and `packages/mobile/src/icons/__figma__/`, replacing any previously generated files. It can also be run on its own:
>
> ```sh
> yarn nx run icons:sync-icon-code-connect
Expand Down
111 changes: 63 additions & 48 deletions packages/icons/scripts/sync-icon-code-connect/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,21 @@
* Generates Icon Code Connect files for both web and mobile packages, driven
* entirely by the icon component sets in the Figma CDS Components file.
*
* Each icon component set (prefixed "ui/" or "nav/") gets its own parser-less
* Code Connect template file (`<iconName>.figma.ts`) in the package's
* `icons/__figma__/` directory. Active/Inactive-suffixed variants
* (e.g. "ui/bellActive") are legacy names — they collapse onto the base icon's
* single template (<Icon name="bell" />), since the active state is expressed
* via the shared `active` prop instead of a distinct name.
* Rather than one Code Connect template file per icon, each package gets a single
* Code Connect *batch* integration (https://developers.figma.com/docs/code-connect/batch-files/)
* in its `icons/__figma__/` directory:
*
* - `icons.figma.batch.ts` A shared, parser-less template. It has no `// url=`
* metadata comments and reads per-icon data (the icon
* name) from `figma.batch`, which the Figma runtime
* populates from each entry in the JSON file below.
* - `icons.figma.batch.json` Lists every icon as a `{ name, url, source, component }`
* entry. Each entry publishes as its own Code Connect
* document, exactly as the old per-icon files did.
*
* Active/Inactive-suffixed variants (e.g. "ui/bellActive") are legacy names — they
* collapse onto the base icon's single entry (<Icon name="bell" />), since the
* active state is expressed via the shared `active` prop instead of a distinct name.
*
* Called automatically by `icons:sync-icons`, but can also be run standalone:
*
Expand All @@ -26,9 +35,9 @@ import path from 'node:path';
const CODE_CONNECT_FIGMA_FILE_KEY = 'k5CtyJccNQUGMI5bI4lJ2g';
const FIGMA_BASE_URL = `https://www.figma.com/design/${CODE_CONNECT_FIGMA_FILE_KEY}/CDS-Components`;

// Files in each icons/__figma__ directory that are NOT generated per-icon
// templates and must never be deleted by the cleanup step.
const PRESERVE = new Set<string>([]);
// Names of the two batch files written into each icons/__figma__ directory.
const BATCH_TEMPLATE_FILE = 'icons.figma.batch.ts';
const BATCH_JSON_FILE = 'icons.figma.batch.json';

// ─── Name helpers ─────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -77,21 +86,16 @@ type ComponentSet = { name: string; node_id: string };

type Icon = { iconName: string; nodeId: string };

// Builds the full content of one parser-less Code Connect template file.
// The `example` line embeds a `figma.code` literal whose `${...}` placeholders are
// evaluated by the Figma runtime (not here), so it's assembled via string
// concatenation to keep those placeholders literal in the output.
export function renderIconTemplate(target: Target, iconName: string, nodeId: string): string {
const url = `${FIGMA_BASE_URL}?node-id=${nodeId.replace(':', '-')}`;
const exampleLine =
' example: figma.code`<Icon name="' +
iconName +
"\" size=\"${size}\"${active ? ' active' : ''} />`,";

// Builds the shared batch template (`icons.figma.batch.ts`) for a target. Unlike
// a per-icon raw template, it carries no `// url=`/`// source=`/`// component=`
// metadata (those come from the JSON entries) and substitutes the icon name via
// `figma.batch.name`, which the Figma runtime resolves per entry at render time.
//
// The `example`/`id` lines embed `figma.code` and template-literal placeholders
// (`${figma.batch.name}`, `${size}`, …) that must remain literal in the output, so
// the file is assembled from single-quoted strings to avoid interpolating them here.
export function renderBatchTemplate(target: Target): string {
return (
`// url=${url}\n` +
`// source=${target.sourcePath}\n` +
`// component=Icon\n` +
`import figma from 'figma';\n` +
`\n` +
`const instance = figma.selectedInstance;\n` +
Expand All @@ -107,36 +111,47 @@ export function renderIconTemplate(target: Target, iconName: string, nodeId: str
`\n` +
`// eslint-disable-next-line no-restricted-exports\n` +
`export default {\n` +
`${exampleLine}\n` +
' example: figma.code`<Icon name="${figma.batch.name}" size="${size}"' +
"${active ? ' active' : ''} />`,\n" +
` imports: ['import { Icon } from "${target.importPackage}"'],\n` +
` id: 'icon-${iconName}${target.idSuffix}',\n` +
' id: `icon-${figma.batch.name}' +
target.idSuffix +
'`,\n' +
` metadata: { nestable: true },\n` +
`};\n`
);
}

// Removes previously-generated per-icon template files (and the legacy
// single-file Icon.figma.tsx) from a target dir, preserving anything in PRESERVE.
function cleanTargetDir(outDir: string): void {
if (!fs.existsSync(outDir)) {
fs.mkdirSync(outDir, { recursive: true });
return;
}
for (const file of fs.readdirSync(outDir)) {
if (PRESERVE.has(file)) continue;
if (file.endsWith('.figma.ts') || file === 'Icon.figma.tsx') {
fs.unlinkSync(path.join(outDir, file));
}
}
// Builds the batch JSON (`icons.figma.batch.json`) for a target: one entry per
// icon, each pointing the shared template at a Figma node URL. `name` is the
// custom field consumed by the template via `figma.batch.name`.
export function renderBatchJson(target: Target, icons: Icon[]): string {
const data = {
templateFile: `./${BATCH_TEMPLATE_FILE}`,
components: icons.map(({ iconName, nodeId }) => ({
name: iconName,
url: `${FIGMA_BASE_URL}?node-id=${nodeId.replace(':', '-')}`,
source: target.sourcePath,
component: 'Icon',
})),
};
return `${JSON.stringify(data, null, 2)}\n`;
}

function generateFiles(target: Target, icons: Icon[]): number {
cleanTargetDir(target.outDir);
for (const { iconName, nodeId } of icons) {
const content = renderIconTemplate(target, iconName, nodeId);
fs.writeFileSync(path.join(target.outDir, `${iconName}.figma.ts`), content, 'utf-8');
}
console.log(` [${target.label}] ${icons.length} template files written.`);
// The two batch files are always overwritten, so we only need the dir to exist.
fs.mkdirSync(target.outDir, { recursive: true });
fs.writeFileSync(
path.join(target.outDir, BATCH_TEMPLATE_FILE),
renderBatchTemplate(target),
'utf-8',
);
fs.writeFileSync(
path.join(target.outDir, BATCH_JSON_FILE),
renderBatchJson(target, icons),
'utf-8',
);
console.log(` [${target.label}] batch template + JSON written (${icons.length} icons).`);
return icons.length;
}

Expand Down Expand Up @@ -180,14 +195,14 @@ export function makeTargets(repoRoot: string): Target[] {
{
label: 'web',
outDir: path.join(repoRoot, 'packages/web/src/icons/__figma__'),
importPackage: '@coinbase/cds-web/icons/Icon',
importPackage: '@coinbase/cds-web/icons',
sourcePath: 'packages/web/src/icons/Icon.tsx',
idSuffix: '',
},
{
label: 'mobile',
outDir: path.join(repoRoot, 'packages/mobile/src/icons/__figma__'),
importPackage: '@coinbase/cds-mobile/icons/Icon',
importPackage: '@coinbase/cds-mobile/icons',
sourcePath: 'packages/mobile/src/icons/Icon.tsx',
idSuffix: '-mobile',
},
Expand Down Expand Up @@ -216,13 +231,13 @@ export async function syncIconCodeConnect(repoRoot: string) {
}
}

console.log('\nGenerating Code Connect template files…');
console.log('\nGenerating Code Connect batch files…');
for (const target of makeTargets(repoRoot)) {
generateFiles(target, icons);
console.log(` Written to ${path.relative(repoRoot, target.outDir)}`);
}

console.log('\n✅ Code Connect template files generated.');
console.log('\n✅ Code Connect batch files generated.');
}

// ─── CLI entry point ──────────────────────────────────────────────────────────
Expand Down
4 changes: 2 additions & 2 deletions packages/mobile-visualization/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
"defaultConfiguration": "dev",
"configurations": {
"dev": {
"command": "rm -rf esm && babel ./src --out-dir esm --extensions .ts,.tsx,.js,.jsx --copy-files --no-copy-ignored"
"command": "rm -rf esm && babel ./src --out-dir esm --extensions .ts,.tsx,.js,.jsx"
},
"prod": {
"commands": [
"rm -rf esm && babel ./src --out-dir esm --extensions .ts,.tsx,.js,.jsx --copy-files --no-copy-ignored"
"rm -rf esm && babel ./src --out-dir esm --extensions .ts,.tsx,.js,.jsx"
],
"parallel": false
}
Expand Down
9 changes: 5 additions & 4 deletions packages/mobile/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,21 @@
"defaultConfiguration": "dev",
"configurations": {
"dev": {
"command": "rm -rf esm && babel ./src --out-dir esm --extensions .ts,.tsx,.js,.jsx --copy-files --no-copy-ignored"
"command": "rm -rf esm && babel ./src --out-dir esm --extensions .ts,.tsx,.js,.jsx"
},
"prod": {
"commands": [
"rm -rf esm && babel ./src --out-dir esm --extensions .ts,.tsx,.js,.jsx --copy-files --no-copy-ignored"
"rm -rf esm && babel ./src --out-dir esm --extensions .ts,.tsx,.js,.jsx"
],
"parallel": false
}
}
},
"test": {
"executor": "@nx/jest:jest",
"executor": "nx:run-commands",
"options": {
"jestConfig": "{projectRoot}/jest.config.js"
"command": "jest --maxWorkers=75%",
"cwd": "{projectRoot}"
}
},
"lint": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { measurePerformance } from 'reassure';

import { Button } from '../../buttons/Button';
import { ComponentConfigProvider } from '../../system/ComponentConfigProvider';
import { DefaultThemeProvider } from '../../utils/testHelpers';
import { Button } from '../../../buttons/Button';
import { ComponentConfigProvider } from '../../../system/ComponentConfigProvider';
import { DefaultThemeProvider } from '../../../utils/testHelpers';

const buttonCount = 1000;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { Pressable, Text } from 'react-native';
import { fireEvent, screen } from '@testing-library/react-native';
import { measurePerformance } from 'reassure';

import type { ComponentConfig } from '../../core/componentConfig';
import { useComponentConfig } from '../../hooks/useComponentConfig';
import { ComponentConfigProvider } from '../../system/ComponentConfigProvider';
import type { ComponentConfig } from '../../../core/componentConfig';
import { useComponentConfig } from '../../../hooks/useComponentConfig';
import { ComponentConfigProvider } from '../../../system/ComponentConfigProvider';

const consumerCount = 1000;
const updateIterations = 50;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@ import { Pressable } from 'react-native';
import { fireEvent, screen } from '@testing-library/react-native';
import { measurePerformance } from 'reassure';

import { Button } from '../../buttons/Button';
import { IconButton } from '../../buttons/IconButton';
import { ListCell } from '../../cells/ListCell';
import { Chip } from '../../chips/Chip';
import { SearchInput } from '../../controls/SearchInput';
import { TextInput } from '../../controls/TextInput';
import type { ComponentConfig } from '../../core/componentConfig';
import type { ThemeConfig } from '../../core/theme';
import { DotCount } from '../../dots/DotCount';
import { Icon } from '../../icons/Icon';
import { HStack } from '../../layout/HStack';
import { VStack } from '../../layout/VStack';
import { Avatar } from '../../media/Avatar';
import { ComponentConfigProvider } from '../../system/ComponentConfigProvider';
import { ThemeProvider } from '../../system/ThemeProvider';
import { Tag } from '../../tag/Tag';
import { defaultTheme } from '../../themes/defaultTheme';
import { Text } from '../../typography/Text';
import { Button } from '../../../buttons/Button';
import { IconButton } from '../../../buttons/IconButton';
import { ListCell } from '../../../cells/ListCell';
import { Chip } from '../../../chips/Chip';
import { SearchInput } from '../../../controls/SearchInput';
import { TextInput } from '../../../controls/TextInput';
import type { ComponentConfig } from '../../../core/componentConfig';
import type { ThemeConfig } from '../../../core/theme';
import { DotCount } from '../../../dots/DotCount';
import { Icon } from '../../../icons/Icon';
import { HStack } from '../../../layout/HStack';
import { VStack } from '../../../layout/VStack';
import { Avatar } from '../../../media/Avatar';
import { ComponentConfigProvider } from '../../../system/ComponentConfigProvider';
import { ThemeProvider } from '../../../system/ThemeProvider';
import { Tag } from '../../../tag/Tag';
import { defaultTheme } from '../../../themes/defaultTheme';
import { Text } from '../../../typography/Text';

const updateIterations = 50;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable jest/expect-expect */
import { nux } from '@coinbase/cds-lottie-files/nux';
import { measurePerformance } from 'reassure';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable jest/expect-expect */
import { measurePerformance } from 'reassure';

import { LottieStatusAnimation } from '../LottieStatusAnimation';
Expand Down
1 change: 0 additions & 1 deletion packages/mobile/src/buttons/__tests__/Button.perf-test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable jest/expect-expect */
import { NoopFn } from '@coinbase/cds-common/utils/mockUtils';
import { fireEvent, screen } from '@testing-library/react-native';
import { measurePerformance } from 'reassure';
Expand Down
Loading
Loading