@dannote/figma-use 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,138 @@
1
+ /**
2
+ * ComponentSet (variants) support
3
+ *
4
+ * ## How ComponentSet works in Figma's multiplayer protocol
5
+ *
6
+ * ComponentSet is NOT a separate node type - it's a FRAME with special fields:
7
+ * - type = FRAME (4)
8
+ * - isStateGroup = true (field 225)
9
+ * - stateGroupPropertyValueOrders (field 238) = [{property: "variant", values: ["A", "B"]}]
10
+ *
11
+ * Children are SYMBOL (Component) nodes with names like "variant=Primary, size=Large".
12
+ * Figma auto-generates componentPropertyDefinitions from these names.
13
+ *
14
+ * ## Why Instances are created via Plugin API
15
+ *
16
+ * IMPORTANT: Instance nodes with symbolData.symbolID CANNOT be created via multiplayer
17
+ * when linking to components in the same batch. Figma reassigns GUIDs on receive,
18
+ * breaking the symbolID references.
19
+ *
20
+ * Example: We send SYMBOL with localID=100, Instance with symbolData.symbolID=100.
21
+ * Figma receives and assigns new IDs: SYMBOL becomes 200, but Instance still
22
+ * references 100 → broken link.
23
+ *
24
+ * This works for defineComponent() because Component and first Instance are adjacent
25
+ * in the node tree, but fails for ComponentSet where variant components are created
26
+ * first, then instances are created as siblings of the ComponentSet.
27
+ *
28
+ * Solution: Create the ComponentSet and variant Components via multiplayer (fast),
29
+ * then create Instances via Plugin API in trigger-layout (correct linking).
30
+ * The pendingComponentSetInstances array passes instance specs to the plugin.
31
+ *
32
+ * Discovered through protocol sniffing - see scripts/sniff-ws.ts
33
+ */
34
+
35
+ import * as React from 'react'
36
+
37
+ type VariantDef = Record<string, readonly string[]>
38
+ type VariantProps<V extends VariantDef> = { [K in keyof V]: V[K][number] }
39
+
40
+ interface ComponentSetDef<V extends VariantDef> {
41
+ name: string
42
+ variants: V
43
+ render: (props: VariantProps<V>) => React.ReactElement
44
+ symbol: symbol
45
+ }
46
+
47
+ const componentSetRegistry = new Map<symbol, ComponentSetDef<VariantDef>>()
48
+
49
+ export function resetComponentSetRegistry() {
50
+ componentSetRegistry.clear()
51
+ }
52
+
53
+ export function getComponentSetRegistry() {
54
+ return componentSetRegistry
55
+ }
56
+
57
+ /**
58
+ * Define a component with variants (ComponentSet)
59
+ *
60
+ * @example
61
+ * const Button = defineComponentSet('Button', {
62
+ * variant: ['Primary', 'Secondary'] as const,
63
+ * size: ['Small', 'Large'] as const,
64
+ * }, ({ variant, size }) => (
65
+ * <Frame style={{ padding: size === 'Large' ? 16 : 8 }}>
66
+ * <Text>{variant}</Text>
67
+ * </Frame>
68
+ * ))
69
+ *
70
+ * <Button variant="Primary" size="Large" />
71
+ */
72
+ export function defineComponentSet<V extends VariantDef>(
73
+ name: string,
74
+ variants: V,
75
+ render: (props: VariantProps<V>) => React.ReactElement
76
+ ): React.FC<Partial<VariantProps<V>> & { style?: Record<string, unknown> }> {
77
+ const sym = Symbol(name)
78
+ componentSetRegistry.set(sym, { name, variants, render, symbol: sym } as ComponentSetDef<VariantDef>)
79
+
80
+ const VariantInstance: React.FC<Partial<VariantProps<V>> & { style?: Record<string, unknown> }> = (props) => {
81
+ const { style, ...variantProps } = props
82
+ return React.createElement('__component_set_instance__', {
83
+ __componentSetSymbol: sym,
84
+ __componentSetName: name,
85
+ __variantProps: variantProps,
86
+ style,
87
+ })
88
+ }
89
+ VariantInstance.displayName = name
90
+
91
+ return VariantInstance
92
+ }
93
+
94
+ /**
95
+ * Generate all variant combinations
96
+ */
97
+ export function generateVariantCombinations<V extends VariantDef>(
98
+ variants: V
99
+ ): Array<VariantProps<V>> {
100
+ const keys = Object.keys(variants) as (keyof V)[]
101
+ if (keys.length === 0) return [{}] as Array<VariantProps<V>>
102
+
103
+ const result: Array<VariantProps<V>> = []
104
+
105
+ function combine(index: number, current: Partial<VariantProps<V>>) {
106
+ if (index === keys.length) {
107
+ result.push(current as VariantProps<V>)
108
+ return
109
+ }
110
+
111
+ const key = keys[index]
112
+ for (const value of variants[key]) {
113
+ combine(index + 1, { ...current, [key]: value })
114
+ }
115
+ }
116
+
117
+ combine(0, {})
118
+ return result
119
+ }
120
+
121
+ /**
122
+ * Build variant name string for Figma component (e.g., "variant=Primary, size=Large")
123
+ */
124
+ export function buildVariantName(props: Record<string, string>): string {
125
+ return Object.entries(props)
126
+ .map(([k, v]) => `${k}=${v}`)
127
+ .join(', ')
128
+ }
129
+
130
+ /**
131
+ * Build stateGroupPropertyValueOrders for ComponentSet
132
+ */
133
+ export function buildStateGroupPropertyValueOrders(variants: VariantDef): Array<{property: string, values: string[]}> {
134
+ return Object.entries(variants).map(([property, values]) => ({
135
+ property,
136
+ values: [...values]
137
+ }))
138
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Figma-like React components
3
+ *
4
+ * API inspired by react-figma but outputs JSON instead of Figma nodes
5
+ */
6
+
7
+ import * as React from 'react'
8
+
9
+ // Re-export defineVars for use in .figma.tsx files
10
+ export {
11
+ defineVars,
12
+ figmaVar,
13
+ isVariable,
14
+ loadVariablesIntoRegistry,
15
+ isRegistryLoaded,
16
+ type FigmaVariable
17
+ } from './vars.ts'
18
+
19
+ // Component registry - tracks defined components and their instances
20
+ interface ComponentDef {
21
+ name: string
22
+ element: React.ReactElement
23
+ guid?: string // Set after first render creates the component
24
+ }
25
+
26
+ const componentRegistry = new Map<symbol, ComponentDef>()
27
+
28
+ export function resetComponentRegistry() {
29
+ componentRegistry.clear()
30
+ }
31
+
32
+ export function getComponentRegistry() {
33
+ return componentRegistry
34
+ }
35
+
36
+ /**
37
+ * Define a reusable Figma component
38
+ *
39
+ * @example
40
+ * const Button = defineComponent('Button',
41
+ * <Frame style={{ padding: 12, backgroundColor: "#3B82F6" }}>
42
+ * <Text style={{ color: "#FFF" }}>Click</Text>
43
+ * </Frame>
44
+ * )
45
+ *
46
+ * export default () => (
47
+ * <Frame>
48
+ * <Button />
49
+ * <Button />
50
+ * </Frame>
51
+ * )
52
+ */
53
+ export function defineComponent<P extends BaseProps = BaseProps>(
54
+ name: string,
55
+ element: React.ReactElement
56
+ ): React.FC<P> {
57
+ const sym = Symbol(name)
58
+ componentRegistry.set(sym, { name, element })
59
+
60
+ // Return a component that renders as a special marker
61
+ const ComponentInstance: React.FC<P> = (props) => {
62
+ return React.createElement('__component_instance__', {
63
+ __componentSymbol: sym,
64
+ __componentName: name,
65
+ ...props,
66
+ })
67
+ }
68
+ ComponentInstance.displayName = name
69
+
70
+ return ComponentInstance
71
+ }
72
+
73
+ // Style types
74
+ interface Style {
75
+ width?: number | string
76
+ height?: number | string
77
+ x?: number
78
+ y?: number
79
+ flexDirection?: 'row' | 'column'
80
+ justifyContent?: 'flex-start' | 'center' | 'flex-end' | 'space-between'
81
+ alignItems?: 'flex-start' | 'center' | 'flex-end' | 'stretch'
82
+ gap?: number
83
+ padding?: number
84
+ paddingTop?: number
85
+ paddingRight?: number
86
+ paddingBottom?: number
87
+ paddingLeft?: number
88
+ backgroundColor?: string
89
+ opacity?: number
90
+ borderRadius?: number
91
+ borderTopLeftRadius?: number
92
+ borderTopRightRadius?: number
93
+ borderBottomLeftRadius?: number
94
+ borderBottomRightRadius?: number
95
+ borderWidth?: number
96
+ borderColor?: string
97
+ }
98
+
99
+ interface TextStyle extends Style {
100
+ fontSize?: number
101
+ fontFamily?: string
102
+ fontWeight?: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'
103
+ fontStyle?: 'normal' | 'italic'
104
+ color?: string
105
+ textAlign?: 'left' | 'center' | 'right'
106
+ lineHeight?: number
107
+ letterSpacing?: number
108
+ }
109
+
110
+ interface BaseProps {
111
+ name?: string
112
+ style?: Style
113
+ children?: React.ReactNode
114
+ }
115
+
116
+ interface TextProps extends Omit<BaseProps, 'style'> {
117
+ style?: TextStyle
118
+ }
119
+
120
+ interface StarProps extends BaseProps {
121
+ pointCount?: number
122
+ }
123
+
124
+ interface PolygonProps extends BaseProps {
125
+ pointCount?: number
126
+ }
127
+
128
+ interface InstanceProps extends BaseProps {
129
+ componentId?: string
130
+ }
131
+
132
+ // Component factory - creates intrinsic element wrapper
133
+ const c = <T extends BaseProps>(type: string): React.FC<T> =>
134
+ (props) => React.createElement(type, props)
135
+
136
+ // Components
137
+ export const Frame = c<BaseProps>('frame')
138
+ export const Rectangle = c<BaseProps>('rectangle')
139
+ export const Ellipse = c<BaseProps>('ellipse')
140
+ export const Text = c<TextProps>('text')
141
+ export const Line = c<BaseProps>('line')
142
+ export const Star = c<StarProps>('star')
143
+ export const Polygon = c<PolygonProps>('polygon')
144
+ export const Vector = c<BaseProps>('vector')
145
+ export const Component = c<BaseProps>('component')
146
+ export const Instance = c<InstanceProps>('instance')
147
+ export const Group = c<BaseProps>('group')
148
+ export const Page = c<BaseProps>('page')
149
+ export const View = Frame
150
+
151
+ // All component names for JSX transform
152
+ export const INTRINSIC_ELEMENTS = [
153
+ 'Frame', 'Rectangle', 'Ellipse', 'Text', 'Line', 'Star',
154
+ 'Polygon', 'Vector', 'Component', 'Instance', 'Group', 'Page', 'View'
155
+ ] as const
@@ -0,0 +1,47 @@
1
+ /**
2
+ * React → Figma Renderer
3
+ */
4
+
5
+ export {
6
+ renderToNodeChanges,
7
+ resetRenderedComponents,
8
+ getPendingComponentSetInstances,
9
+ clearPendingComponentSetInstances,
10
+ type RenderOptions,
11
+ type RenderResult,
12
+ type PendingComponentSetInstance,
13
+ } from './reconciler.ts'
14
+ export {
15
+ Frame,
16
+ Rectangle,
17
+ Ellipse,
18
+ Text,
19
+ Line,
20
+ Star,
21
+ Polygon,
22
+ Vector,
23
+ Component,
24
+ Instance,
25
+ Group,
26
+ Page,
27
+ View,
28
+ INTRINSIC_ELEMENTS,
29
+ // Variable bindings (StyleX-inspired)
30
+ defineVars,
31
+ figmaVar,
32
+ isVariable,
33
+ loadVariablesIntoRegistry,
34
+ isRegistryLoaded,
35
+ type FigmaVariable,
36
+ // Component definitions
37
+ defineComponent,
38
+ resetComponentRegistry,
39
+ getComponentRegistry,
40
+ } from './components.tsx'
41
+
42
+ export {
43
+ // ComponentSet (variants)
44
+ defineComponentSet,
45
+ resetComponentSetRegistry,
46
+ getComponentSetRegistry,
47
+ } from './component-set.tsx'