unframer 0.6.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 +138 -0
- package/bin.mjs +4 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +98 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.test.d.ts +2 -0
- package/dist/cli.test.d.ts.map +1 -0
- package/dist/cli.test.js +37 -0
- package/dist/cli.test.js.map +1 -0
- package/dist/exporter.d.ts +21 -0
- package/dist/exporter.d.ts.map +1 -0
- package/dist/exporter.js +494 -0
- package/dist/exporter.js.map +1 -0
- package/dist/framer.d.ts +2 -0
- package/dist/framer.d.ts.map +1 -0
- package/dist/framer.js +2 -0
- package/dist/framer.js.map +1 -0
- package/dist/react.d.ts +16 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +157 -0
- package/dist/react.js.map +1 -0
- package/framer-fixed/dist/framer.d.ts +4829 -0
- package/framer-fixed/dist/framer.js +38175 -0
- package/package.json +63 -0
- package/src/cli.test.ts +52 -0
- package/src/cli.tsx +114 -0
- package/src/exporter.ts +572 -0
- package/src/framer.ts +1 -0
- package/src/react.tsx +229 -0
package/src/react.tsx
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import { combinedCSSRules } from '../framer-fixed/dist/framer.js'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
ComponentPropsWithoutRef,
|
|
6
|
+
ComponentType,
|
|
7
|
+
ReactNode,
|
|
8
|
+
useEffect,
|
|
9
|
+
useSyncExternalStore,
|
|
10
|
+
} from 'react'
|
|
11
|
+
|
|
12
|
+
function getFonts(component) {
|
|
13
|
+
const fonts = component.fonts
|
|
14
|
+
return fonts || []
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function classNames(...args) {
|
|
18
|
+
return args.filter(Boolean).join(' ')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const defaultBreakpoints = ['Desktop', 'Tablet', 'Mobile'] as const
|
|
22
|
+
|
|
23
|
+
type Breakpoint = (typeof defaultBreakpoints)[number]
|
|
24
|
+
|
|
25
|
+
let defaultMap: Record<Breakpoint, string> = Object.fromEntries(
|
|
26
|
+
defaultBreakpoints.map((x) => [x, x]),
|
|
27
|
+
) as any
|
|
28
|
+
|
|
29
|
+
function getClassMap(breakpoints: Breakpoint[]): Record<Breakpoint, string> {
|
|
30
|
+
const classMap: Record<Breakpoint, string> = {
|
|
31
|
+
Desktop: '',
|
|
32
|
+
Tablet: '',
|
|
33
|
+
Mobile: '',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (breakpoints.length === 1) {
|
|
37
|
+
classMap[breakpoints[0]] = 'FramerDesktop FramerTablet FramerMobile'
|
|
38
|
+
} else if (breakpoints.length === 2) {
|
|
39
|
+
if (breakpoints.includes('Desktop')) {
|
|
40
|
+
classMap.Desktop = 'Desktop'
|
|
41
|
+
classMap[breakpoints.find((b) => b !== 'Desktop')!] =
|
|
42
|
+
'FramerTablet FramerMobile'
|
|
43
|
+
} else if (breakpoints.includes('Tablet')) {
|
|
44
|
+
classMap.Tablet = 'Tablet'
|
|
45
|
+
classMap[breakpoints.find((b) => b !== 'Tablet')!] =
|
|
46
|
+
'FramerDesktop FramerMobile'
|
|
47
|
+
} else {
|
|
48
|
+
classMap.Mobile = 'Mobile'
|
|
49
|
+
classMap[breakpoints.find((b) => b !== 'Mobile')!] =
|
|
50
|
+
'FramerDesktop FramerTablet'
|
|
51
|
+
}
|
|
52
|
+
} else if (breakpoints.length === 3) {
|
|
53
|
+
classMap.Desktop = 'FramerDesktop'
|
|
54
|
+
classMap.Tablet = 'FramerTablet'
|
|
55
|
+
classMap.Mobile = 'FramerMobile'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return classMap
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function deduplicateByKey<T>(arr: T[], key: (k: T) => string) {
|
|
62
|
+
let map = new Map()
|
|
63
|
+
for (let item of arr) {
|
|
64
|
+
let value = item[key(item)]
|
|
65
|
+
if (map.has(value)) {
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
map.set(value, item)
|
|
69
|
+
}
|
|
70
|
+
return Array.from(map.values())
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getFontsStyles(Components) {
|
|
74
|
+
const allFonts = deduplicateByKey<{ family; url; style; weight }>(
|
|
75
|
+
Components.map(getFonts).flat(),
|
|
76
|
+
(x) => x.url,
|
|
77
|
+
).filter((x) => x.url)
|
|
78
|
+
|
|
79
|
+
// console.log(JSON.stringify(fonts, null, 2))
|
|
80
|
+
let str = allFonts
|
|
81
|
+
.map((x) => {
|
|
82
|
+
let str = `@font-face { font-family: '${x.family}'; src: url(${x.url});`
|
|
83
|
+
if (x.style) {
|
|
84
|
+
str += ` font-style: ${x.style};`
|
|
85
|
+
}
|
|
86
|
+
if (x.weight) {
|
|
87
|
+
str += ` font-weight: ${x.weight};`
|
|
88
|
+
}
|
|
89
|
+
str += ` }`
|
|
90
|
+
return str
|
|
91
|
+
})
|
|
92
|
+
.join('\n')
|
|
93
|
+
|
|
94
|
+
return str
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const breakpointSizes: Record<(typeof defaultBreakpoints)[number], number> = {
|
|
98
|
+
Desktop: 1024,
|
|
99
|
+
Tablet: 768,
|
|
100
|
+
Mobile: 0,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getBreakpointNameFromWindowWidth(windowWidth: number) {
|
|
104
|
+
return defaultBreakpoints.find(
|
|
105
|
+
(name) => windowWidth >= breakpointSizes[name],
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const breakpointsStyles = `
|
|
110
|
+
|
|
111
|
+
.FramerTablet,
|
|
112
|
+
.FramerMobile,
|
|
113
|
+
.FramerDesktop {
|
|
114
|
+
display: none;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@media (min-width: ${breakpointSizes.Desktop}px) {
|
|
118
|
+
.FramerDesktop {
|
|
119
|
+
display: contents;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@media (min-width: ${breakpointSizes.Tablet}px) and (max-width: ${breakpointSizes.Desktop}px) {
|
|
124
|
+
.FramerTablet {
|
|
125
|
+
display: contents;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@media (max-width: ${breakpointSizes.Tablet}px) {
|
|
130
|
+
.FramerMobile {
|
|
131
|
+
display: contents;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.contents {
|
|
136
|
+
display: contents;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
`
|
|
140
|
+
|
|
141
|
+
export function FramerStyles({ Components = [] as any[] }) {
|
|
142
|
+
return (
|
|
143
|
+
<>
|
|
144
|
+
<style
|
|
145
|
+
dangerouslySetInnerHTML={{ __html: getFontsStyles(Components) }}
|
|
146
|
+
suppressHydrationWarning
|
|
147
|
+
hidden
|
|
148
|
+
/>
|
|
149
|
+
<style
|
|
150
|
+
dangerouslySetInnerHTML={{
|
|
151
|
+
__html: combinedCSSRules.join('\n'),
|
|
152
|
+
}}
|
|
153
|
+
suppressHydrationWarning
|
|
154
|
+
hidden
|
|
155
|
+
/>
|
|
156
|
+
<style
|
|
157
|
+
dangerouslySetInnerHTML={{ __html: breakpointsStyles }}
|
|
158
|
+
suppressHydrationWarning
|
|
159
|
+
hidden
|
|
160
|
+
/>
|
|
161
|
+
</>
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function WithFramerBreakpoints<
|
|
166
|
+
T extends ComponentType<{ variant?: any; className?: string }>,
|
|
167
|
+
>({
|
|
168
|
+
Component,
|
|
169
|
+
variants: breakpointsMap = defaultMap,
|
|
170
|
+
...rest
|
|
171
|
+
}: {
|
|
172
|
+
Component: T
|
|
173
|
+
variants?: Record<Breakpoint, ComponentPropsWithoutRef<T>['variant']>
|
|
174
|
+
} & Omit<ComponentPropsWithoutRef<T>, 'variant'>) {
|
|
175
|
+
const controls = Component['propertyControls']
|
|
176
|
+
|
|
177
|
+
const variantControls = controls?.['variant']
|
|
178
|
+
if (!variantControls) {
|
|
179
|
+
// @ts-expect-error
|
|
180
|
+
return <Component variant={undefined} {...rest} />
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const options = variantControls?.optionTitles
|
|
184
|
+
|
|
185
|
+
const currentBreakpoint = useSyncExternalStore(
|
|
186
|
+
onResize,
|
|
187
|
+
() => {
|
|
188
|
+
// console.log('window.innerWidth', window.innerWidth)
|
|
189
|
+
const breakpoint = getBreakpointNameFromWindowWidth(
|
|
190
|
+
window.innerWidth,
|
|
191
|
+
)
|
|
192
|
+
return breakpoint
|
|
193
|
+
},
|
|
194
|
+
() => {
|
|
195
|
+
// on server
|
|
196
|
+
return ''
|
|
197
|
+
},
|
|
198
|
+
)
|
|
199
|
+
// console.log('currentBreakpoint', currentBreakpoint)
|
|
200
|
+
|
|
201
|
+
let parts: ReactNode[] = []
|
|
202
|
+
for (let breakpointName of defaultBreakpoints) {
|
|
203
|
+
if (currentBreakpoint && currentBreakpoint !== breakpointName) {
|
|
204
|
+
continue
|
|
205
|
+
}
|
|
206
|
+
let realVariant = breakpointsMap[breakpointName]
|
|
207
|
+
if (!realVariant) {
|
|
208
|
+
continue
|
|
209
|
+
}
|
|
210
|
+
let mapped = defaultBreakpoints.filter((x) => breakpointsMap[x])
|
|
211
|
+
|
|
212
|
+
let map = getClassMap(mapped)[breakpointName]
|
|
213
|
+
let className = classNames('', map)
|
|
214
|
+
|
|
215
|
+
parts.push(
|
|
216
|
+
<div key={breakpointName} className={className}>
|
|
217
|
+
{/* @ts-expect-error */}
|
|
218
|
+
<Component {...rest} variant={realVariant} />
|
|
219
|
+
</div>,
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return parts
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const onResize = (callback) => {
|
|
227
|
+
window.addEventListener('resize', callback)
|
|
228
|
+
return () => window.removeEventListener('resize', callback)
|
|
229
|
+
}
|