Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
635871c
Add real test
owjs3901 Dec 26, 2025
5443586
Add test
owjs3901 Dec 26, 2025
f0725e8
Add radius case
owjs3901 Dec 26, 2025
30c3019
Refactor
owjs3901 Dec 26, 2025
4813bb3
Fix case
owjs3901 Dec 26, 2025
7bb743e
Add case
owjs3901 Dec 26, 2025
beca351
Add all of gradient test
owjs3901 Dec 26, 2025
81e50df
Add all of gradient test
owjs3901 Dec 26, 2025
b178991
Add Text test
owjs3901 Dec 26, 2025
81996ac
Add Text test
owjs3901 Dec 26, 2025
3c5dd15
Add auto layout test
owjs3901 Dec 26, 2025
88c86f6
Fix component
owjs3901 Dec 26, 2025
0bf1cbb
Fix component
owjs3901 Dec 26, 2025
1e26c0d
Fix component
owjs3901 Dec 26, 2025
8476bcd
Fix component
owjs3901 Dec 26, 2025
d6eb8e7
Add case
owjs3901 Dec 28, 2025
91847b4
Add effect test case
owjs3901 Dec 28, 2025
6272ee0
Add absolute
owjs3901 Dec 28, 2025
a1282a9
Add svg test case
owjs3901 Dec 28, 2025
fc8f7c6
Add svg test case
owjs3901 Dec 28, 2025
c868fc0
Add text test case
owjs3901 Dec 28, 2025
66509f1
Add object-fit test case
owjs3901 Dec 28, 2025
540bdb7
Fix typography
owjs3901 Dec 28, 2025
87a6eda
Fix typography
owjs3901 Dec 28, 2025
0b71655
Add testcase
owjs3901 Dec 28, 2025
c2ecfdd
Add testcase
owjs3901 Dec 28, 2025
30d4c7a
Add testcase
owjs3901 Dec 28, 2025
782eed8
Add testcase
owjs3901 Dec 28, 2025
c4f485c
Rm log
owjs3901 Dec 28, 2025
edbe490
Add list testcase
owjs3901 Dec 28, 2025
347834c
Add debug flag
owjs3901 Dec 28, 2025
6ec242d
Fix indentation in conditional rendering blocks
owjs3901 Dec 28, 2025
cfd783e
Fix space
owjs3901 Dec 28, 2025
162f786
Fix space
owjs3901 Dec 28, 2025
1d95773
Implement responsive
owjs3901 Dec 29, 2025
2367653
Implement responsive
owjs3901 Dec 29, 2025
d14a70c
Selector with responsive
owjs3901 Dec 29, 2025
599e287
Selector with responsive
owjs3901 Dec 29, 2025
b888ffa
Selector with responsive
owjs3901 Dec 29, 2025
4256214
Selector with responsive
owjs3901 Dec 29, 2025
21a2cd3
Update selector
owjs3901 Dec 29, 2025
36effcc
Update selector
owjs3901 Dec 29, 2025
a3b11d1
Add cmd
owjs3901 Dec 29, 2025
097ae4e
Add cmd
owjs3901 Dec 29, 2025
673e41c
Add responsive test
owjs3901 Dec 29, 2025
74eeed2
Add responsive test
owjs3901 Dec 29, 2025
cfda41c
Add case
owjs3901 Dec 30, 2025
d25d152
Add case
owjs3901 Dec 30, 2025
5d50b1e
Split
owjs3901 Dec 30, 2025
69e2084
Add case
owjs3901 Dec 30, 2025
4f110aa
Increase coverage
owjs3901 Dec 30, 2025
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
21 changes: 20 additions & 1 deletion src/code-impl.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Codegen } from './codegen/Codegen'
import { ResponsiveCodegen } from './codegen/responsive/ResponsiveCodegen'
import { nodeProxyTracker } from './codegen/utils/node-proxy'
import { exportDevup, importDevup } from './commands/devup'
import { exportAssets } from './commands/exportAssets'
import { exportComponents } from './commands/exportComponents'
Expand Down Expand Up @@ -113,9 +114,12 @@ function generatePowerShellCLI(
return commands.join('\n')
}

const debug = true

export function registerCodegen(ctx: typeof figma) {
if (ctx.editorType === 'dev' && ctx.mode === 'codegen') {
ctx.codegen.on('generate', async ({ node, language }) => {
ctx.codegen.on('generate', async ({ node: n, language }) => {
const node = debug ? nodeProxyTracker.wrap(n) : n
switch (language) {
case 'devup-ui': {
const time = Date.now()
Expand Down Expand Up @@ -161,6 +165,11 @@ export function registerCodegen(ctx: typeof figma) {
console.error('[responsive] Error generating responsive code:', e)
}
}
if (debug) {
console.log(
await nodeProxyTracker.toTestCaseFormatWithVariables(node.id),
)
}

return [
...(node.type === 'COMPONENT' ||
Expand Down Expand Up @@ -202,6 +211,16 @@ export function registerCodegen(ctx: typeof figma) {
.map((code) => code[1])
.join('\n\n'),
},
{
title: `${node.name} - Components Responsive CLI (Bash)`,
language: 'BASH' as const,
code: generateBashCLI(responsiveComponentsCodes),
},
{
title: `${node.name} - Components Responsive CLI (PowerShell)`,
language: 'BASH' as const,
code: generatePowerShellCLI(responsiveComponentsCodes),
},
]
: []),
...responsiveResult,
Expand Down
343 changes: 343 additions & 0 deletions src/codegen/__tests__/codegen-viewport.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,3 +355,346 @@ describe('Codegen viewport variant', () => {
expect(codes.length).toBe(2)
})
})

describe('Codegen effect-only COMPONENT_SET', () => {
test('generates code with pseudo-selectors for effect-only component set', async () => {
// Create effect variants: default, hover, active, disabled
const defaultVariant = createComponentNode(
'effect=default',
{ effect: 'default' },
{
fills: [
{
type: 'SOLID',
visible: true,
color: { r: 0.5, g: 0.3, b: 0.9 },
opacity: 1,
} as unknown as Paint,
],
reactions: [
{
trigger: { type: 'ON_HOVER' },
actions: [
{
type: 'NODE',
transition: {
type: 'SMART_ANIMATE',
duration: 0.3,
easing: { type: 'EASE_OUT' },
},
},
],
},
] as unknown as Reaction[],
},
)

const hoverVariant = createComponentNode(
'effect=hover',
{ effect: 'hover' },
{
fills: [
{
type: 'SOLID',
visible: true,
color: { r: 0.4, g: 0.2, b: 0.8 },
opacity: 1,
} as unknown as Paint,
],
},
)

const activeVariant = createComponentNode(
'effect=active',
{ effect: 'active' },
{
fills: [
{
type: 'SOLID',
visible: true,
color: { r: 0.3, g: 0.1, b: 0.7 },
opacity: 1,
} as unknown as Paint,
],
},
)

const disabledVariant = createComponentNode(
'effect=disabled',
{ effect: 'disabled' },
{
fills: [
{
type: 'SOLID',
visible: true,
color: { r: 0.8, g: 0.8, b: 0.8 },
opacity: 1,
} as unknown as Paint,
],
},
)

const componentSet = createComponentSetNode(
'EffectButton',
{
effect: {
type: 'VARIANT',
defaultValue: 'default',
variantOptions: ['default', 'hover', 'active', 'disabled'],
},
},
[defaultVariant, hoverVariant, activeVariant, disabledVariant],
)

const codes = await ResponsiveCodegen.generateVariantResponsiveComponents(
componentSet,
'EffectButton',
)

expect(codes.length).toBe(1)
const [componentName, generatedCode] = codes[0]
expect(componentName).toBe('EffectButton')

// Should have pseudo-selector props
expect(generatedCode).toContain('_hover')
expect(generatedCode).toContain('_active')
expect(generatedCode).toContain('_disabled')

// Should have transition properties
expect(generatedCode).toContain('transition=')
expect(generatedCode).toContain('transitionProperty=')

// Should NOT have effect as a prop (handled via pseudo-selectors)
expect(generatedCode).not.toContain('effect:')
})

test('generates code with viewport + effect variants', async () => {
// Mobile variants
const mobileDefault = createComponentNode(
'effect=default, viewport=Mobile',
{ effect: 'default', viewport: 'Mobile' },
{
width: 150,
height: 50,
layoutSizingHorizontal: 'FIXED',
layoutSizingVertical: 'FIXED',
fills: [
{
type: 'SOLID',
visible: true,
color: { r: 0.5, g: 0.5, b: 0.5 },
opacity: 1,
} as unknown as Paint,
],
},
)

const mobileHover = createComponentNode(
'effect=hover, viewport=Mobile',
{ effect: 'hover', viewport: 'Mobile' },
{
width: 150,
height: 50,
layoutSizingHorizontal: 'FIXED',
layoutSizingVertical: 'FIXED',
fills: [
{
type: 'SOLID',
visible: true,
color: { r: 0.6, g: 0.6, b: 0.6 },
opacity: 1,
} as unknown as Paint,
],
},
)

// Desktop variants
const desktopDefault = createComponentNode(
'effect=default, viewport=Desktop',
{ effect: 'default', viewport: 'Desktop' },
{
width: 200,
height: 50,
layoutSizingHorizontal: 'FIXED',
layoutSizingVertical: 'FIXED',
fills: [
{
type: 'SOLID',
visible: true,
color: { r: 0.5, g: 0.5, b: 0.5 },
opacity: 1,
} as unknown as Paint,
],
},
)

const desktopHover = createComponentNode(
'effect=hover, viewport=Desktop',
{ effect: 'hover', viewport: 'Desktop' },
{
width: 200,
height: 50,
layoutSizingHorizontal: 'FIXED',
layoutSizingVertical: 'FIXED',
fills: [
{
type: 'SOLID',
visible: true,
color: { r: 0.7, g: 0.7, b: 0.7 },
opacity: 1,
} as unknown as Paint,
],
},
)

const componentSet = createComponentSetNode(
'ResponsiveEffectButton',
{
effect: {
type: 'VARIANT',
defaultValue: 'default',
variantOptions: ['default', 'hover'],
},
viewport: {
type: 'VARIANT',
defaultValue: 'Desktop',
variantOptions: ['Mobile', 'Desktop'],
},
},
[mobileDefault, mobileHover, desktopDefault, desktopHover],
)

const codes = await ResponsiveCodegen.generateVariantResponsiveComponents(
componentSet,
'ResponsiveEffectButton',
)

expect(codes.length).toBe(1)
const [componentName, generatedCode] = codes[0]
expect(componentName).toBe('ResponsiveEffectButton')

// Should have responsive width (different for mobile vs desktop)
expect(generatedCode).toContain('w={')
expect(generatedCode).toContain('"150px"')
expect(generatedCode).toContain('"200px"')

// Should have _hover with responsive bg colors
expect(generatedCode).toContain('_hover')
})

test('generates code with effect + size variants', async () => {
// Size=Md variants
const mdDefault = createComponentNode(
'effect=default, size=Md',
{ effect: 'default', size: 'Md' },
{
width: 100,
height: 50,
layoutSizingHorizontal: 'FIXED',
layoutSizingVertical: 'FIXED',
fills: [
{
type: 'SOLID',
visible: true,
color: { r: 0.2, g: 0.4, b: 0.8 },
opacity: 1,
} as unknown as Paint,
],
},
)

const mdHover = createComponentNode(
'effect=hover, size=Md',
{ effect: 'hover', size: 'Md' },
{
width: 100,
height: 50,
layoutSizingHorizontal: 'FIXED',
layoutSizingVertical: 'FIXED',
fills: [
{
type: 'SOLID',
visible: true,
color: { r: 0.3, g: 0.5, b: 0.9 },
opacity: 1,
} as unknown as Paint,
],
},
)

// Size=Sm variants
const smDefault = createComponentNode(
'effect=default, size=Sm',
{ effect: 'default', size: 'Sm' },
{
width: 80,
height: 40,
layoutSizingHorizontal: 'FIXED',
layoutSizingVertical: 'FIXED',
fills: [
{
type: 'SOLID',
visible: true,
color: { r: 0.2, g: 0.4, b: 0.8 },
opacity: 1,
} as unknown as Paint,
],
},
)

const smHover = createComponentNode(
'effect=hover, size=Sm',
{ effect: 'hover', size: 'Sm' },
{
width: 80,
height: 40,
layoutSizingHorizontal: 'FIXED',
layoutSizingVertical: 'FIXED',
fills: [
{
type: 'SOLID',
visible: true,
color: { r: 0.3, g: 0.5, b: 0.9 },
opacity: 1,
} as unknown as Paint,
],
},
)

const componentSet = createComponentSetNode(
'SizeEffectButton',
{
effect: {
type: 'VARIANT',
defaultValue: 'default',
variantOptions: ['default', 'hover'],
},
size: {
type: 'VARIANT',
defaultValue: 'Md',
variantOptions: ['Md', 'Sm'],
},
},
[mdDefault, mdHover, smDefault, smHover],
)

const codes = await ResponsiveCodegen.generateVariantResponsiveComponents(
componentSet,
'SizeEffectButton',
)

expect(codes.length).toBe(1)
const [componentName, generatedCode] = codes[0]
expect(componentName).toBe('SizeEffectButton')

// Should have size prop in interface
expect(generatedCode).toContain("size: 'Md' | 'Sm'")

// Should have variant-conditional width
expect(generatedCode).toContain('w={')
expect(generatedCode).toContain('[size]')

// Should have _hover props
expect(generatedCode).toContain('_hover')
})
})
Loading