beth-copilot 1.0.0
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.
- package/README.md +224 -0
- package/bin/cli.js +223 -0
- package/package.json +36 -0
- package/templates/.github/agents/beth.agent.md +279 -0
- package/templates/.github/agents/developer.agent.md +493 -0
- package/templates/.github/agents/frontend-engineer.agent.md +556 -0
- package/templates/.github/agents/product-manager.agent.md +253 -0
- package/templates/.github/agents/researcher.agent.md +319 -0
- package/templates/.github/agents/security-reviewer.agent.md +452 -0
- package/templates/.github/agents/tester.agent.md +477 -0
- package/templates/.github/agents/ux-designer.agent.md +374 -0
- package/templates/.github/copilot-instructions.md +191 -0
- package/templates/.github/skills/framer-components/SKILL.md +564 -0
- package/templates/.github/skills/prd/SKILL.md +244 -0
- package/templates/.github/skills/security-analysis/SKILL.md +799 -0
- package/templates/.github/skills/shadcn-ui/SKILL.md +562 -0
- package/templates/.github/skills/vercel-react-best-practices/AGENTS.md +2516 -0
- package/templates/.github/skills/vercel-react-best-practices/SKILL.md +125 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/advanced-use-latest.md +49 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/async-dependencies.md +36 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +57 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/templates/.github/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
- package/templates/.github/skills/web-design-guidelines/SKILL.md +39 -0
- package/templates/AGENTS.md +70 -0
- package/templates/Backlog.md +80 -0
- package/templates/mcp.json.example +9 -0
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: framer-components
|
|
3
|
+
description: 'Build custom React code components and overrides for Framer. Use when creating Framer components, adding property controls, writing code overrides, or extending Framer functionality. Triggers on: framer component, code component, property controls, addPropertyControls, ControlType, code override, withXXX override, framer motion in framer.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Framer Code Components
|
|
7
|
+
|
|
8
|
+
Build custom React components with visual property controls for Framer sites. This skill covers code components, property controls, code overrides, auto-sizing, and Framer Motion integration.
|
|
9
|
+
|
|
10
|
+
## When to Use This Skill
|
|
11
|
+
|
|
12
|
+
- Creating custom code components for Framer
|
|
13
|
+
- Adding property controls to make components configurable in the Framer UI
|
|
14
|
+
- Writing code overrides to modify layer properties and behavior
|
|
15
|
+
- Implementing auto-sizing for responsive components
|
|
16
|
+
- Using Framer Motion animations within components
|
|
17
|
+
- Building reusable component libraries for Framer
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
- React 18 knowledge (components must use React 18 compatible code)
|
|
22
|
+
- Access to Framer's built-in code editor
|
|
23
|
+
- Understanding of TypeScript (optional but recommended)
|
|
24
|
+
|
|
25
|
+
## Code Components
|
|
26
|
+
|
|
27
|
+
Code components are React components that render directly on the Framer canvas, in preview, and on published sites.
|
|
28
|
+
|
|
29
|
+
### Basic Component Structure
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
export default function MyComponent(props) {
|
|
33
|
+
return <div>Hello World</div>
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Component with Styling
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
export default function Button(props) {
|
|
41
|
+
const style = {
|
|
42
|
+
display: "inline-block",
|
|
43
|
+
backgroundColor: "orange",
|
|
44
|
+
padding: 8,
|
|
45
|
+
borderRadius: 4,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return <div style={style}>{props.text}</div>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
Button.defaultProps = {
|
|
52
|
+
text: "Click me",
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Property Controls
|
|
57
|
+
|
|
58
|
+
Property controls allow users to configure component props through the Framer UI.
|
|
59
|
+
|
|
60
|
+
### Adding Property Controls
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { addPropertyControls, ControlType } from "framer"
|
|
64
|
+
|
|
65
|
+
export default function Button(props) {
|
|
66
|
+
return (
|
|
67
|
+
<button style={{ backgroundColor: props.tint, padding: 12 }}>
|
|
68
|
+
{props.text}
|
|
69
|
+
</button>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
Button.defaultProps = {
|
|
74
|
+
text: "Button",
|
|
75
|
+
tint: "#09F",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
addPropertyControls(Button, {
|
|
79
|
+
text: {
|
|
80
|
+
type: ControlType.String,
|
|
81
|
+
title: "Label",
|
|
82
|
+
defaultValue: "Button",
|
|
83
|
+
},
|
|
84
|
+
tint: {
|
|
85
|
+
type: ControlType.Color,
|
|
86
|
+
title: "Background",
|
|
87
|
+
defaultValue: "#09F",
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Available Control Types
|
|
93
|
+
|
|
94
|
+
| ControlType | Description | Value Type |
|
|
95
|
+
|-------------|-------------|------------|
|
|
96
|
+
| `String` | Text input (single or multi-line) | `string` |
|
|
97
|
+
| `Number` | Numeric input with optional range/stepper | `number` |
|
|
98
|
+
| `Boolean` | On/off checkbox | `boolean` |
|
|
99
|
+
| `Enum` | Dropdown or segmented control | `string` |
|
|
100
|
+
| `Color` | Color picker | `string` (rgb/rgba) |
|
|
101
|
+
| `ResponsiveImage` | Image picker with srcSet | `{ src, srcSet, alt }` |
|
|
102
|
+
| `File` | File picker | `string` (URL) |
|
|
103
|
+
| `Date` | Date picker | `string` (ISO 8601) |
|
|
104
|
+
| `Link` | URL input | `string` |
|
|
105
|
+
| `ComponentInstance` | Reference to another component | React node |
|
|
106
|
+
| `Array` | Repeatable values | `array` |
|
|
107
|
+
| `Object` | Grouped properties | `object` |
|
|
108
|
+
| `Transition` | Framer Motion transition editor | `object` |
|
|
109
|
+
| `EventHandler` | Exposes events in Interactions panel | `function` |
|
|
110
|
+
| `Font` | Font picker | `object` |
|
|
111
|
+
| `Padding` | CSS padding input | `string` |
|
|
112
|
+
| `BorderRadius` | CSS border-radius input | `string` |
|
|
113
|
+
| `Border` | Border style editor | `object` |
|
|
114
|
+
| `BoxShadow` | Shadow editor | `string` |
|
|
115
|
+
| `Cursor` | Cursor picker | `string` |
|
|
116
|
+
|
|
117
|
+
### Control Type Examples
|
|
118
|
+
|
|
119
|
+
#### String Control
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
addPropertyControls(MyComponent, {
|
|
123
|
+
title: {
|
|
124
|
+
type: ControlType.String,
|
|
125
|
+
title: "Title",
|
|
126
|
+
defaultValue: "Hello",
|
|
127
|
+
placeholder: "Enter title...",
|
|
128
|
+
displayTextArea: true, // Multi-line input
|
|
129
|
+
maxLength: 100,
|
|
130
|
+
},
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
#### Number Control
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
addPropertyControls(MyComponent, {
|
|
138
|
+
rotation: {
|
|
139
|
+
type: ControlType.Number,
|
|
140
|
+
title: "Rotation",
|
|
141
|
+
defaultValue: 0,
|
|
142
|
+
min: 0,
|
|
143
|
+
max: 360,
|
|
144
|
+
step: 1,
|
|
145
|
+
unit: "deg",
|
|
146
|
+
displayStepper: true,
|
|
147
|
+
},
|
|
148
|
+
})
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### Enum Control
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
addPropertyControls(MyComponent, {
|
|
155
|
+
size: {
|
|
156
|
+
type: ControlType.Enum,
|
|
157
|
+
title: "Size",
|
|
158
|
+
defaultValue: "medium",
|
|
159
|
+
options: ["small", "medium", "large"],
|
|
160
|
+
optionTitles: ["Small", "Medium", "Large"],
|
|
161
|
+
displaySegmentedControl: true,
|
|
162
|
+
},
|
|
163
|
+
})
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### ResponsiveImage Control
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
addPropertyControls(MyComponent, {
|
|
170
|
+
image: {
|
|
171
|
+
type: ControlType.ResponsiveImage,
|
|
172
|
+
title: "Image",
|
|
173
|
+
},
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
// Usage in component:
|
|
177
|
+
function MyComponent(props) {
|
|
178
|
+
return (
|
|
179
|
+
<img
|
|
180
|
+
src={props.image?.src}
|
|
181
|
+
srcSet={props.image?.srcSet}
|
|
182
|
+
alt={props.image?.alt}
|
|
183
|
+
/>
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### Array Control
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
addPropertyControls(MyComponent, {
|
|
192
|
+
items: {
|
|
193
|
+
type: ControlType.Array,
|
|
194
|
+
title: "Items",
|
|
195
|
+
maxCount: 10,
|
|
196
|
+
control: {
|
|
197
|
+
type: ControlType.Object,
|
|
198
|
+
controls: {
|
|
199
|
+
title: { type: ControlType.String, defaultValue: "Item" },
|
|
200
|
+
icon: { type: ControlType.ResponsiveImage },
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
})
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### EventHandler Control
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
import { motion } from "framer-motion"
|
|
211
|
+
|
|
212
|
+
export function MyButton(props) {
|
|
213
|
+
return <motion.div onTap={props.onTap}>Click me</motion.div>
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
addPropertyControls(MyButton, {
|
|
217
|
+
onTap: {
|
|
218
|
+
type: ControlType.EventHandler,
|
|
219
|
+
},
|
|
220
|
+
})
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Hiding Controls Conditionally
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
addPropertyControls(MyComponent, {
|
|
227
|
+
showIcon: {
|
|
228
|
+
type: ControlType.Boolean,
|
|
229
|
+
title: "Show Icon",
|
|
230
|
+
defaultValue: false,
|
|
231
|
+
},
|
|
232
|
+
icon: {
|
|
233
|
+
type: ControlType.ResponsiveImage,
|
|
234
|
+
title: "Icon",
|
|
235
|
+
hidden(props) {
|
|
236
|
+
return props.showIcon === false
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
})
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Adding Control Descriptions
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
addPropertyControls(MyComponent, {
|
|
246
|
+
apiKey: {
|
|
247
|
+
type: ControlType.String,
|
|
248
|
+
title: "API Key",
|
|
249
|
+
description: "Get your key from [dashboard](https://example.com/api)",
|
|
250
|
+
},
|
|
251
|
+
})
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Auto-Sizing
|
|
255
|
+
|
|
256
|
+
Control how your component sizes itself on the canvas.
|
|
257
|
+
|
|
258
|
+
### Layout Annotations
|
|
259
|
+
|
|
260
|
+
```tsx
|
|
261
|
+
/**
|
|
262
|
+
* @framerSupportedLayoutWidth auto
|
|
263
|
+
* @framerSupportedLayoutHeight fixed
|
|
264
|
+
*/
|
|
265
|
+
export function MyComponent(props) {
|
|
266
|
+
return <div style={{ width: "fit-content", height: 50 }}>Content</div>
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**Layout Options:**
|
|
271
|
+
- `auto` — Component dictates its own size based on content
|
|
272
|
+
- `fixed` — Component fills 100% of its container
|
|
273
|
+
- `any` — Users can switch between auto and fixed (default)
|
|
274
|
+
- `any-prefer-fixed` — Same as `any`, but defaults to fixed
|
|
275
|
+
|
|
276
|
+
### Intrinsic Size (Default Insert Size)
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
/**
|
|
280
|
+
* @framerIntrinsicWidth 200
|
|
281
|
+
* @framerIntrinsicHeight 100
|
|
282
|
+
*/
|
|
283
|
+
export function Card(props) {
|
|
284
|
+
return <div style={{ width: "100%", height: "100%" }}>Card</div>
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Supporting Fixed Sizing
|
|
289
|
+
|
|
290
|
+
Spread the `style` prop to allow Framer to control dimensions:
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
export function MyComponent(props) {
|
|
294
|
+
const { style, ...rest } = props
|
|
295
|
+
|
|
296
|
+
return (
|
|
297
|
+
<div style={{
|
|
298
|
+
backgroundColor: "blue",
|
|
299
|
+
...style // Framer passes { width: "100%", height: "100%" } when fixed
|
|
300
|
+
}}>
|
|
301
|
+
Content
|
|
302
|
+
</div>
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Code Overrides
|
|
308
|
+
|
|
309
|
+
Code overrides are Higher Order Components that modify layer properties. They only run in preview and on published sites.
|
|
310
|
+
|
|
311
|
+
### Basic Override Structure
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
import { forwardRef, type ComponentType } from "react"
|
|
315
|
+
|
|
316
|
+
export const withLowerOpacity = (Component): ComponentType => {
|
|
317
|
+
return forwardRef((props, ref) => {
|
|
318
|
+
return (
|
|
319
|
+
<Component
|
|
320
|
+
ref={ref}
|
|
321
|
+
{...props}
|
|
322
|
+
style={{ ...props?.style, opacity: 0.5 }}
|
|
323
|
+
/>
|
|
324
|
+
)
|
|
325
|
+
})
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**Important:** Always:
|
|
330
|
+
1. Use `forwardRef` for React 18 compatibility
|
|
331
|
+
2. Spread `{...props}` to preserve existing functionality
|
|
332
|
+
3. Pass `ref` to the component
|
|
333
|
+
|
|
334
|
+
### Override Examples
|
|
335
|
+
|
|
336
|
+
#### Custom Attributes
|
|
337
|
+
|
|
338
|
+
```tsx
|
|
339
|
+
import { forwardRef, type ComponentType } from "react"
|
|
340
|
+
|
|
341
|
+
export function withDataAttributes(Component): ComponentType {
|
|
342
|
+
return forwardRef((props, ref) => {
|
|
343
|
+
return (
|
|
344
|
+
<Component
|
|
345
|
+
ref={ref}
|
|
346
|
+
{...props}
|
|
347
|
+
id="my-element"
|
|
348
|
+
data-tracking="button-cta"
|
|
349
|
+
/>
|
|
350
|
+
)
|
|
351
|
+
})
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
#### Click Tracking
|
|
356
|
+
|
|
357
|
+
```tsx
|
|
358
|
+
import { forwardRef, type ComponentType } from "react"
|
|
359
|
+
|
|
360
|
+
export function withClickTracking(Component): ComponentType {
|
|
361
|
+
return forwardRef((props, ref) => {
|
|
362
|
+
const handleClick = () => {
|
|
363
|
+
// Track the click
|
|
364
|
+
fetch("https://api.example.com/track", {
|
|
365
|
+
method: "POST",
|
|
366
|
+
body: JSON.stringify({ event: "click" }),
|
|
367
|
+
})
|
|
368
|
+
// Call original onClick if exists
|
|
369
|
+
props?.onClick?.()
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return <Component ref={ref} {...props} onClick={handleClick} />
|
|
373
|
+
})
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
#### Dynamic Text
|
|
378
|
+
|
|
379
|
+
```tsx
|
|
380
|
+
import { forwardRef, type ComponentType } from "react"
|
|
381
|
+
|
|
382
|
+
export function withDynamicText(Component): ComponentType {
|
|
383
|
+
return forwardRef((props, ref) => {
|
|
384
|
+
const text = new Date().toLocaleDateString()
|
|
385
|
+
return <Component ref={ref} {...props} text={text} />
|
|
386
|
+
})
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Shared State Between Overrides
|
|
391
|
+
|
|
392
|
+
Use `createStore` for state that multiple overrides need to access:
|
|
393
|
+
|
|
394
|
+
```tsx
|
|
395
|
+
import { forwardRef, type ComponentType } from "react"
|
|
396
|
+
import { createStore } from "https://framer.com/m/framer/store.js@^1.0.0"
|
|
397
|
+
|
|
398
|
+
const useStore = createStore({ count: 0 })
|
|
399
|
+
|
|
400
|
+
export const withCount = (Component): ComponentType => {
|
|
401
|
+
return forwardRef((props, ref) => {
|
|
402
|
+
const [store] = useStore()
|
|
403
|
+
return <Component ref={ref} {...props} text={`Count: ${store.count}`} />
|
|
404
|
+
})
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export const withIncrement = (Component): ComponentType => {
|
|
408
|
+
return forwardRef((props, ref) => {
|
|
409
|
+
const [store, setStore] = useStore()
|
|
410
|
+
const onTap = () => setStore({ count: store.count + 1 })
|
|
411
|
+
return <Component ref={ref} {...props} onTap={onTap} />
|
|
412
|
+
})
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## Framer Motion Integration
|
|
417
|
+
|
|
418
|
+
Framer Motion is available in all code components:
|
|
419
|
+
|
|
420
|
+
```tsx
|
|
421
|
+
import { motion } from "framer-motion"
|
|
422
|
+
|
|
423
|
+
export function AnimatedBox(props) {
|
|
424
|
+
return (
|
|
425
|
+
<motion.div
|
|
426
|
+
initial={{ opacity: 0, scale: 0.8 }}
|
|
427
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
428
|
+
whileHover={{ scale: 1.05 }}
|
|
429
|
+
whileTap={{ scale: 0.95 }}
|
|
430
|
+
transition={{ type: "spring", stiffness: 400, damping: 25 }}
|
|
431
|
+
style={{ width: 100, height: 100, backgroundColor: props.color }}
|
|
432
|
+
/>
|
|
433
|
+
)
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Transition Property Control
|
|
438
|
+
|
|
439
|
+
```tsx
|
|
440
|
+
import { motion } from "framer-motion"
|
|
441
|
+
import { addPropertyControls, ControlType } from "framer"
|
|
442
|
+
|
|
443
|
+
export function AnimatedElement(props) {
|
|
444
|
+
return (
|
|
445
|
+
<motion.div
|
|
446
|
+
animate={{ scale: props.scale }}
|
|
447
|
+
transition={props.transition}
|
|
448
|
+
style={{ width: 100, height: 100, backgroundColor: "#09F" }}
|
|
449
|
+
/>
|
|
450
|
+
)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
addPropertyControls(AnimatedElement, {
|
|
454
|
+
scale: {
|
|
455
|
+
type: ControlType.Number,
|
|
456
|
+
defaultValue: 1,
|
|
457
|
+
min: 0.5,
|
|
458
|
+
max: 2,
|
|
459
|
+
step: 0.1,
|
|
460
|
+
},
|
|
461
|
+
transition: {
|
|
462
|
+
type: ControlType.Transition,
|
|
463
|
+
defaultValue: { type: "spring", stiffness: 800, damping: 60 },
|
|
464
|
+
},
|
|
465
|
+
})
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## Detecting Render Context
|
|
469
|
+
|
|
470
|
+
Use `RenderTarget` to detect where your component is being rendered:
|
|
471
|
+
|
|
472
|
+
```tsx
|
|
473
|
+
import { RenderTarget } from "framer"
|
|
474
|
+
|
|
475
|
+
export function MyComponent(props) {
|
|
476
|
+
const isCanvas = RenderTarget.current() === RenderTarget.canvas
|
|
477
|
+
const isPreview = RenderTarget.current() === RenderTarget.preview
|
|
478
|
+
|
|
479
|
+
if (isCanvas) {
|
|
480
|
+
return <div>Canvas placeholder</div>
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return <div>Live content</div>
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
**RenderTarget values:**
|
|
488
|
+
- `RenderTarget.canvas` — The Framer canvas
|
|
489
|
+
- `RenderTarget.preview` — Preview or live site
|
|
490
|
+
- `RenderTarget.export` — Export canvas
|
|
491
|
+
- `RenderTarget.thumbnail` — Project thumbnails
|
|
492
|
+
|
|
493
|
+
### Static Renderer Hook (for animations)
|
|
494
|
+
|
|
495
|
+
```tsx
|
|
496
|
+
import { useIsStaticRenderer } from "framer"
|
|
497
|
+
|
|
498
|
+
export function AnimatedComponent(props) {
|
|
499
|
+
const isStatic = useIsStaticRenderer()
|
|
500
|
+
|
|
501
|
+
if (isStatic) {
|
|
502
|
+
return <div>Static preview</div>
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return <motion.div animate={{ rotate: 360 }}>Animated</motion.div>
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
## Localization
|
|
510
|
+
|
|
511
|
+
Access locale info for multi-language sites:
|
|
512
|
+
|
|
513
|
+
```tsx
|
|
514
|
+
import { useLocaleInfo } from "framer"
|
|
515
|
+
|
|
516
|
+
export function LocalePicker(props) {
|
|
517
|
+
const { activeLocale, locales, setLocale } = useLocaleInfo()
|
|
518
|
+
|
|
519
|
+
return (
|
|
520
|
+
<select
|
|
521
|
+
value={activeLocale?.id ?? "default"}
|
|
522
|
+
onChange={(e) => {
|
|
523
|
+
const locale = locales.find((l) => l.id === e.target.value)
|
|
524
|
+
if (locale) setLocale(locale)
|
|
525
|
+
}}
|
|
526
|
+
>
|
|
527
|
+
{locales.map((locale) => (
|
|
528
|
+
<option key={locale.id} value={locale.id}>
|
|
529
|
+
{locale.name}
|
|
530
|
+
</option>
|
|
531
|
+
))}
|
|
532
|
+
</select>
|
|
533
|
+
)
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
## Best Practices
|
|
538
|
+
|
|
539
|
+
1. **Always use defaultProps** — Prevents errors during development and when instances are created
|
|
540
|
+
2. **Spread props and style** — Preserve Framer's built-in functionality
|
|
541
|
+
3. **Use forwardRef in overrides** — Required for React 18 compatibility
|
|
542
|
+
4. **Avoid overriding `class`/`className`** — Framer relies on these for styling
|
|
543
|
+
5. **Use RenderTarget wisely** — Show placeholders on canvas for performance
|
|
544
|
+
6. **Keep components performant** — Avoid expensive operations that run on every render
|
|
545
|
+
7. **Use useLayoutEffect for dynamic sizing** — Works best with Framer's measuring system
|
|
546
|
+
|
|
547
|
+
## Troubleshooting
|
|
548
|
+
|
|
549
|
+
| Issue | Solution |
|
|
550
|
+
|-------|----------|
|
|
551
|
+
| Component not showing | Check for React errors in console, ensure `export default` |
|
|
552
|
+
| Property controls not appearing | Verify `addPropertyControls` is called after component definition |
|
|
553
|
+
| Styling breaks on fixed size | Spread `{...props.style}` in your root element |
|
|
554
|
+
| Override breaks layer | Ensure you spread `{...props}` and pass `ref` |
|
|
555
|
+
| Animation stutters on canvas | Use `useIsStaticRenderer()` to disable animations on canvas |
|
|
556
|
+
| Auto-sizing not working | Add `@framerSupportedLayoutWidth/Height` annotations |
|
|
557
|
+
|
|
558
|
+
## References
|
|
559
|
+
|
|
560
|
+
- [Framer Developers Documentation](https://www.framer.com/developers/)
|
|
561
|
+
- [Property Controls Reference](https://www.framer.com/developers/property-controls)
|
|
562
|
+
- [Code Components Introduction](https://www.framer.com/developers/components-introduction)
|
|
563
|
+
- [Code Overrides Introduction](https://www.framer.com/developers/overrides-introduction)
|
|
564
|
+
- [Framer Motion](https://motion.dev/docs/framer)
|