Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
94d3b6e
add code-connect label
noahchoii Sep 2, 2026
5bd4899
correct dialog figma interface
noahchoii Sep 2, 2026
fdb4379
remove undefined prop
noahchoii Sep 2, 2026
7984160
add code-connect extractor
noahchoii Sep 2, 2026
8a620b1
add extract command
noahchoii Sep 2, 2026
010e150
include test target
noahchoii Sep 2, 2026
b5ab7e8
edit dialog.figma.ts
noahchoii Sep 2, 2026
3ab3c7b
chore(extract-code-connect): scaffold TS CLI package with naming utils
noahchoii Sep 2, 2026
cef334c
feat(extract-code-connect): port model types and extract rules to TS
noahchoii Sep 2, 2026
f8dacf9
fix(extract-code-connect): align extract with spec rules (SLOT childr…
noahchoii Sep 2, 2026
b5f810a
feat(extract-code-connect): port Figma URL parser
noahchoii Sep 2, 2026
47a529b
feat(extract-code-connect): port MCP adapter and move Dialog fixture
noahchoii Sep 2, 2026
e7c1d59
feat(extract-code-connect): port Figma REST adapter with typed response
noahchoii Sep 2, 2026
7f5e4af
feat(extract-code-connect): render with injectable utils import and p…
noahchoii Sep 2, 2026
d739c6b
feat(extract-code-connect): add cwd-relative CLI entry, bin, and README
noahchoii Sep 2, 2026
3f6b294
docs(extract-code-connect): add README
noahchoii Sep 2, 2026
7a4549b
refactor(composites): use @vapor-ui/extract-code-connect CLI instead …
noahchoii Sep 3, 2026
46841a7
fix(extract-code-connect): resolve prettier config from cwd and ancho…
noahchoii Sep 3, 2026
bc7839e
style(extract-code-connect): apply prettier formatting
noahchoii Sep 3, 2026
bc5a025
migrate to yargs
noahchoii Sep 3, 2026
cb6f63d
control component property visibility
noahchoii Sep 6, 2026
436e220
update dialog code-connect file
noahchoii Sep 6, 2026
6784633
remove unnecessary undefined
noahchoii Sep 6, 2026
21c169f
Merge branch 'main' into extract-figma-code-connect
noahchoii Sep 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/composites/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"scripts": {
"build": "rimraf dist && rolldown -c rolldown.config.ts",
"clean": "rimraf dist node_modules .turbo",
"figma:gen": "extract-code-connect",
"lint": "eslint ./src",
"test": "vitest --run --passWithNoTests",
"test:coverage": "vitest --coverage --passWithNoTests",
Expand All @@ -79,6 +80,7 @@
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@vapor-ui/core": "workspace:^",
"@vapor-ui/extract-code-connect": "workspace:*",
"@vitest/browser": "^3.2.7",
"@vitest/coverage-v8": "^3.2.7",
"eslint": "catalog:",
Expand Down
6 changes: 2 additions & 4 deletions packages/composites/src/components/dialog/dialog.figma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,21 @@ const popup = getProperties(instance, '(Popup)', {

const header = getProperties(instance, '(Header)', {
title: { kind: 'string', name: 'title' },
description: { kind: 'string', name: 'description' },
description: { kind: 'string', name: 'description', visibleWhen: '(has description)' },
});

const body = getProperties(instance, '(Body)', {
children: { kind: 'slot', name: '(content)' },
});

const footer = getProperties(instance, '(Footer)', {
assistive: { kind: 'instance', name: 'Assistive' },
assistive: { kind: 'instance', name: 'Assistive', visibleWhen: '(has assistive)' },
action: { kind: 'instance', name: 'Action' },
});

export default {
example: figma.code`
<Dialog.Root
// ariaLabels={} 닫기 버튼의 aria-label을 지정하세요.
// trigger={} 트리거 요소를 입력하세요.
${popup.size}
${header.title}
${header.description}
Expand Down
39 changes: 8 additions & 31 deletions packages/composites/src/utils/figma-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,18 @@ import figma from 'figma';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type AnyProp = any;

type PropSpec =
| { kind: 'string'; name: string }
| { kind: 'boolean'; name: string }
| { kind: 'slot'; name: string }
| { kind: 'enum'; name: string; options: Record<string, unknown> }
| { kind: 'instance'; name: string };
| { kind: 'string'; name: string; visibleWhen?: string }
| { kind: 'boolean'; name: string; visibleWhen?: string }
| { kind: 'slot'; name: string; visibleWhen?: string }
| { kind: 'enum'; name: string; options: Record<string, unknown>; visibleWhen?: string }
| { kind: 'instance'; name: string; visibleWhen?: string };

/**
* Descend into a nested INSTANCE child of `parent` by name.
* Returns the INSTANCE ref (chainable into `getProperties` / `findChild`),
* or `undefined` when the child is missing or not an INSTANCE.
*
* Use when a component is wrapped in an anatomy layer (e.g. `(Popup)` around
* `(Header)` / `(Body)` / `(Footer)`) and you need to read properties from
* the inner instances.
*
* @param parent Parent instance/component to search inside.
* @param instanceName Direct-child instance name (e.g. '(Popup)').
* @returns Child INSTANCE ref, or `undefined`.
*/
export function findChild(
parent: AnyProp,
Expand All @@ -41,6 +34,8 @@ export function findChild(
* Returns `undefined` when the value is missing (e.g. instance not found).
*/
function readRawValue(target: AnyProp, spec: PropSpec): AnyProp {
if (spec.visibleWhen && target.getBoolean(spec.visibleWhen) === false) return;

switch (spec.kind) {
case 'string':
return target.getString(spec.name);
Expand All @@ -63,31 +58,13 @@ function readRawValue(target: AnyProp, spec: PropSpec): AnyProp {
* Read multiple properties from a nested INSTANCE inside `parent` and return
* them **pre-rendered as JSX attribute fragments**, keyed by the code prop name.
*
* Each key in `specs` is the code prop name. Every non-`slot` value is rendered
* with `figma.helpers.react.renderProp(key, value)`:
*
* - `undefined` / `''` → `''` (attribute omitted — safe for optional props)
* - `string` → ` key="value"`
* - `boolean` → ` key` or `''`
* - `ResultSection[]` → ` key={<pill>}` (or fragment-wrapped when > 1)
*
* `slot` values are passed through `renderChildren` and stay raw so they can be
* interpolated as element children.
*
* All keys are always present, so a missing target instance yields `''` for
* every attribute instead of the literal string `undefined`.
*
* @example
* const footer = getProperties(instance, '(Footer)', {
* assistive: { kind: 'instance', name: 'Assistive' },
* action: { kind: 'instance', name: 'Action' },
* });
* figma.code`<Dialog.Root${footer.assistive}${footer.action}>${body.children}</Dialog.Root>`;
*
* @param parent Parent instance/component to search inside.
* @param instanceName Direct-child instance name (e.g. '(Header)').
* @param specs Map of code prop name → figma property descriptor.
* @returns Map of code prop name → rendered attribute (or raw slot).
*/
export function getProperties<K extends string>(
parent: AnyProp,
Expand Down
109 changes: 106 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ packages:
- packages/*
- scripts/ts-api-extractor
- scripts/sync-figma
- scripts/extract-code-connect

# Unified React 19 for entire monorepo
# Libraries (packages/*) maintain peerDependencies for React 17/18/19 compatibility
Expand Down
Loading
Loading