@webflow/webflow-cli 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/.eslintrc.js +5 -0
- package/.turbo/turbo-build.log +13 -0
- package/.turbo/turbo-lint.log +5 -0
- package/CHANGELOG.md +7 -0
- package/dist/devlink-engine/_Builtin/BackgroundVideo.d.ts +25 -0
- package/dist/devlink-engine/_Builtin/BackgroundVideo.js +38 -0
- package/dist/devlink-engine/_Builtin/Basic.d.ts +740 -0
- package/dist/devlink-engine/_Builtin/Basic.js +100 -0
- package/dist/devlink-engine/_Builtin/Dropdown.d.ts +38 -0
- package/dist/devlink-engine/_Builtin/Dropdown.js +47 -0
- package/dist/devlink-engine/_Builtin/Facebook.d.ts +11 -0
- package/dist/devlink-engine/_Builtin/Facebook.js +22 -0
- package/dist/devlink-engine/_Builtin/Form.d.ts +8401 -0
- package/dist/devlink-engine/_Builtin/Form.js +316 -0
- package/dist/devlink-engine/_Builtin/Map.d.ts +14 -0
- package/dist/devlink-engine/_Builtin/Map.js +71 -0
- package/dist/devlink-engine/_Builtin/Navbar.d.ts +53 -0
- package/dist/devlink-engine/_Builtin/Navbar.js +240 -0
- package/dist/devlink-engine/_Builtin/Slider.d.ts +876 -0
- package/dist/devlink-engine/_Builtin/Slider.js +304 -0
- package/dist/devlink-engine/_Builtin/Tabs.d.ts +34 -0
- package/dist/devlink-engine/_Builtin/Tabs.js +84 -0
- package/dist/devlink-engine/_Builtin/Twitter.d.ts +17 -0
- package/dist/devlink-engine/_Builtin/Twitter.js +41 -0
- package/dist/devlink-engine/_Builtin/Typography.d.ts +291 -0
- package/dist/devlink-engine/_Builtin/Typography.js +38 -0
- package/dist/devlink-engine/_Builtin/Video.d.ts +8 -0
- package/dist/devlink-engine/_Builtin/Video.js +8 -0
- package/dist/devlink-engine/_Builtin/YouTubeVideo.d.ts +15 -0
- package/dist/devlink-engine/_Builtin/YouTubeVideo.js +27 -0
- package/dist/devlink-engine/_Builtin/index.d.ts +13 -0
- package/dist/devlink-engine/_Builtin/index.js +13 -0
- package/dist/devlink-engine/interactions.d.ts +13 -0
- package/dist/devlink-engine/interactions.js +86 -0
- package/dist/devlink-engine/types.d.ts +32 -0
- package/dist/devlink-engine/types.js +1 -0
- package/dist/devlink-engine/utils.d.ts +66 -0
- package/dist/devlink-engine/utils.js +133 -0
- package/dist/index.js +197591 -0
- package/package.json +30 -0
- package/src/index.ts +33 -0
- package/tsconfig.json +5 -0
- package/tsup.config.ts +6 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { EASING_FUNCTIONS, KEY_CODES, cj, debounce } from "../utils";
|
|
3
|
+
const DEFAULT_SLIDER_CONFIG = {
|
|
4
|
+
navSpacing: 3,
|
|
5
|
+
navShadow: false,
|
|
6
|
+
autoplay: false,
|
|
7
|
+
delay: 4000,
|
|
8
|
+
iconArrows: true,
|
|
9
|
+
animation: "slide",
|
|
10
|
+
navNumbers: true,
|
|
11
|
+
easing: "ease",
|
|
12
|
+
navRound: true,
|
|
13
|
+
hideArrows: false,
|
|
14
|
+
disableSwipe: false,
|
|
15
|
+
duration: 500,
|
|
16
|
+
infinite: true,
|
|
17
|
+
autoMax: 0,
|
|
18
|
+
navInvert: false,
|
|
19
|
+
};
|
|
20
|
+
export const SliderContext = React.createContext({
|
|
21
|
+
...DEFAULT_SLIDER_CONFIG,
|
|
22
|
+
slideAmount: 0,
|
|
23
|
+
setSlideAmount: () => { },
|
|
24
|
+
setCurrentSlide: () => { },
|
|
25
|
+
goToNextSlide: () => { },
|
|
26
|
+
goToPreviousSlide: () => { },
|
|
27
|
+
slide: { current: 0, previous: 0 },
|
|
28
|
+
isAutoplayPaused: false,
|
|
29
|
+
setAutoplayPause: () => { },
|
|
30
|
+
});
|
|
31
|
+
function useSwipe({ onSwipeLeft, onSwipeRight, config, }) {
|
|
32
|
+
const SWIPE_DELTA = 150;
|
|
33
|
+
const [touchStart, setTouchStart] = React.useState(0);
|
|
34
|
+
const [touchEnd, setTouchEnd] = React.useState(0);
|
|
35
|
+
const handleTouchStart = (e) => {
|
|
36
|
+
setTouchStart(e.touches[0].clientX);
|
|
37
|
+
};
|
|
38
|
+
const handleTouchMove = (e) => {
|
|
39
|
+
setTouchEnd(e.touches[0].clientX);
|
|
40
|
+
};
|
|
41
|
+
const handleTouchEnd = () => {
|
|
42
|
+
if (config?.disableSwipe)
|
|
43
|
+
return;
|
|
44
|
+
if (touchStart - touchEnd > SWIPE_DELTA) {
|
|
45
|
+
onSwipeLeft();
|
|
46
|
+
}
|
|
47
|
+
if (touchStart - touchEnd < -SWIPE_DELTA) {
|
|
48
|
+
onSwipeRight();
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
onTouchStart: handleTouchStart,
|
|
53
|
+
onTouchMove: handleTouchMove,
|
|
54
|
+
onTouchEnd: handleTouchEnd,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function SliderWrapper({ className = "", ...props }) {
|
|
58
|
+
const [slideAmount, setSlideAmount] = React.useState(0);
|
|
59
|
+
const [selectedSlide, setSelectedSlide] = React.useState(0);
|
|
60
|
+
const [prevSelectedSlide, setPrevSelectedSlide] = React.useState(0);
|
|
61
|
+
const [isAutoplayPaused, setAutoplayPause] = React.useState(false);
|
|
62
|
+
const setCurrentSlide = debounce((value) => {
|
|
63
|
+
/**
|
|
64
|
+
* Using a functional update (https://legacy.reactjs.org/docs/hooks-reference.html#functional-updates)
|
|
65
|
+
* so both current and previous slide indexes are always kept in sync
|
|
66
|
+
*/
|
|
67
|
+
setSelectedSlide((prev) => {
|
|
68
|
+
setPrevSelectedSlide(prev);
|
|
69
|
+
return value;
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
const goToNextSlide = debounce(() => {
|
|
73
|
+
if (selectedSlide === slideAmount - 1) {
|
|
74
|
+
setCurrentSlide(0);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
setCurrentSlide(selectedSlide + 1);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
const goToPreviousSlide = debounce(() => {
|
|
81
|
+
if (selectedSlide === 0) {
|
|
82
|
+
setCurrentSlide(slideAmount - 1);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
setCurrentSlide(selectedSlide - 1);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
const swipeHandlers = useSwipe({
|
|
89
|
+
onSwipeLeft: goToNextSlide,
|
|
90
|
+
onSwipeRight: goToPreviousSlide,
|
|
91
|
+
});
|
|
92
|
+
return (React.createElement(SliderContext.Provider, { value: {
|
|
93
|
+
...props,
|
|
94
|
+
slideAmount,
|
|
95
|
+
setSlideAmount,
|
|
96
|
+
slide: { current: selectedSlide, previous: prevSelectedSlide },
|
|
97
|
+
setCurrentSlide,
|
|
98
|
+
goToNextSlide,
|
|
99
|
+
goToPreviousSlide,
|
|
100
|
+
isAutoplayPaused,
|
|
101
|
+
setAutoplayPause,
|
|
102
|
+
} },
|
|
103
|
+
React.createElement("div", { ...swipeHandlers, className: cj(className, "w-slider"), role: "region", "aria-label": "carousel" }, props.children)));
|
|
104
|
+
}
|
|
105
|
+
function useAutoplay() {
|
|
106
|
+
const { autoplay, delay, autoMax, isAutoplayPaused, setAutoplayPause, goToNextSlide, } = React.useContext(SliderContext);
|
|
107
|
+
const [autoMaxCount, setAutoMaxCount] = React.useState(0);
|
|
108
|
+
const autoMaxReached = React.useMemo(() => autoMaxCount >= autoMax && autoMax > 0, [autoMax, autoMaxCount]);
|
|
109
|
+
React.useEffect(() => {
|
|
110
|
+
const shouldAutoplay = autoplay && !autoMaxReached && !isAutoplayPaused;
|
|
111
|
+
if (shouldAutoplay) {
|
|
112
|
+
const interval = setInterval(() => {
|
|
113
|
+
/**
|
|
114
|
+
* Using functional update (https://legacy.reactjs.org/docs/hooks-reference.html#functional-updates)
|
|
115
|
+
* so autoMaxCount is left out of the dependency array and we don't infinitely
|
|
116
|
+
* re-render this component
|
|
117
|
+
*/
|
|
118
|
+
setAutoMaxCount((prev) => prev + 1);
|
|
119
|
+
goToNextSlide();
|
|
120
|
+
}, delay);
|
|
121
|
+
return () => clearInterval(interval);
|
|
122
|
+
}
|
|
123
|
+
}, [autoplay, delay, goToNextSlide, autoMaxReached, isAutoplayPaused]);
|
|
124
|
+
const resumeAutoplay = () => setAutoplayPause(true);
|
|
125
|
+
const pauseAutoplay = () => setAutoplayPause(false);
|
|
126
|
+
return { resumeAutoplay, pauseAutoplay };
|
|
127
|
+
}
|
|
128
|
+
export function SliderMask({ className = "", children, ...props }) {
|
|
129
|
+
const { setSlideAmount } = React.useContext(SliderContext);
|
|
130
|
+
const [isHovered, setHovered] = React.useState(false);
|
|
131
|
+
const { resumeAutoplay, pauseAutoplay } = useAutoplay();
|
|
132
|
+
React.useEffect(() => {
|
|
133
|
+
setSlideAmount(React.Children.count(children));
|
|
134
|
+
}, []);
|
|
135
|
+
return (React.createElement("div", { ...props, className: cj(className, "w-slider-mask"), onMouseEnter: () => {
|
|
136
|
+
pauseAutoplay();
|
|
137
|
+
setHovered(true);
|
|
138
|
+
}, onMouseLeave: () => {
|
|
139
|
+
resumeAutoplay();
|
|
140
|
+
setHovered(false);
|
|
141
|
+
}, onFocus: () => pauseAutoplay(), onBlur: () => resumeAutoplay() },
|
|
142
|
+
React.Children.map(children, (child, index) => {
|
|
143
|
+
if (!React.isValidElement(child))
|
|
144
|
+
return null;
|
|
145
|
+
return React.cloneElement(child, {
|
|
146
|
+
...child.props,
|
|
147
|
+
// index is used to determine which slide is active
|
|
148
|
+
index,
|
|
149
|
+
});
|
|
150
|
+
}),
|
|
151
|
+
React.createElement("div", { "aria-live": isHovered ? "polite" : "off", "aria-atomic": "true", className: "w-slider-aria-label" })));
|
|
152
|
+
}
|
|
153
|
+
export function SliderSlide({ tag = "div", className = "", style = {}, index, ...props }) {
|
|
154
|
+
const { animation, duration, easing, slide: { current, previous }, slideAmount, } = React.useContext(SliderContext);
|
|
155
|
+
const isSlideActive = current === index;
|
|
156
|
+
const isSlidePrevious = previous === index;
|
|
157
|
+
const animationStyle = React.useMemo(() => {
|
|
158
|
+
const base = {
|
|
159
|
+
transform: `translateX(-${current * 100}%)`,
|
|
160
|
+
transition: `transform ${duration}ms ${EASING_FUNCTIONS[easing]} 0s`,
|
|
161
|
+
};
|
|
162
|
+
if (animation === "slide") {
|
|
163
|
+
return base;
|
|
164
|
+
}
|
|
165
|
+
if (animation === "cross") {
|
|
166
|
+
return {
|
|
167
|
+
...base,
|
|
168
|
+
opacity: isSlideActive ? 1 : 0,
|
|
169
|
+
transition: `opacity ${duration}ms ${EASING_FUNCTIONS[easing]} 0s, transform 1ms linear ${isSlideActive ? 0 : duration}ms`,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (animation === "outin") {
|
|
173
|
+
return {
|
|
174
|
+
...base,
|
|
175
|
+
opacity: isSlideActive ? 1 : 0,
|
|
176
|
+
transition: `opacity ${duration / 2}ms ${EASING_FUNCTIONS[easing]} ${isSlidePrevious ? 0 : duration / 2}ms, transform 1ms linear ${isSlidePrevious ? duration / 2 : 0}ms`,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
if (animation === "fade") {
|
|
180
|
+
return {
|
|
181
|
+
...base,
|
|
182
|
+
opacity: isSlideActive ? 1 : 0,
|
|
183
|
+
transition: `opacity ${duration}ms ${EASING_FUNCTIONS[easing]} 0s, transform 1ms linear ${isSlideActive ? 0 : duration}ms`,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
if (animation === "over") {
|
|
187
|
+
return {
|
|
188
|
+
...base,
|
|
189
|
+
transition: `transform ${duration}ms ${EASING_FUNCTIONS[easing]} ${isSlidePrevious ? duration : 0}ms`,
|
|
190
|
+
zIndex: isSlideActive ? 1 : 0,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
return base;
|
|
194
|
+
}, [
|
|
195
|
+
animation,
|
|
196
|
+
duration,
|
|
197
|
+
easing,
|
|
198
|
+
current,
|
|
199
|
+
previous,
|
|
200
|
+
isSlideActive,
|
|
201
|
+
isSlidePrevious,
|
|
202
|
+
]);
|
|
203
|
+
return React.createElement(tag, {
|
|
204
|
+
...props,
|
|
205
|
+
className: cj(className, "w-slide"),
|
|
206
|
+
style: { ...style, ...animationStyle },
|
|
207
|
+
"aria-label": `${index + 1} of ${slideAmount}`,
|
|
208
|
+
role: "group",
|
|
209
|
+
"aria-hidden": !isSlideActive ? "true" : "false",
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
export function SliderArrow({ className = "", dir = "left", children, ...props }) {
|
|
213
|
+
const { goToNextSlide, goToPreviousSlide, hideArrows, slideAmount, slide: { current }, } = React.useContext(SliderContext);
|
|
214
|
+
const handleSlideChange = debounce(() => {
|
|
215
|
+
if (dir === "left") {
|
|
216
|
+
goToPreviousSlide();
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
goToNextSlide();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
const isHidden = React.useMemo(() => {
|
|
223
|
+
if (dir === "left" && hideArrows && current === 0)
|
|
224
|
+
return true;
|
|
225
|
+
if (dir === "right" && hideArrows && current === slideAmount - 1)
|
|
226
|
+
return true;
|
|
227
|
+
return false;
|
|
228
|
+
}, [dir, hideArrows, current, slideAmount]);
|
|
229
|
+
return (React.createElement("div", { ...props, onClick: handleSlideChange, onKeyDown: (e) => {
|
|
230
|
+
e.stopPropagation();
|
|
231
|
+
if (e.key === KEY_CODES.ENTER || e.key === KEY_CODES.SPACE) {
|
|
232
|
+
handleSlideChange();
|
|
233
|
+
}
|
|
234
|
+
}, role: "button", tabIndex: 0, className: cj(className, `w-slider-arrow-${dir}`), "aria-label": `${dir === "left" ? "previous" : "next"} slide`, style: { display: isHidden ? "none" : "block" } }, children));
|
|
235
|
+
}
|
|
236
|
+
export function SliderNav({ className = "", ...props }) {
|
|
237
|
+
const { slideAmount, navInvert, navNumbers, navRound, navShadow, setAutoplayPause, setCurrentSlide, } = React.useContext(SliderContext);
|
|
238
|
+
const [focusedDot, setFocusedDot] = React.useState(null);
|
|
239
|
+
const handleFocus = (e) => {
|
|
240
|
+
switch (e.key) {
|
|
241
|
+
case KEY_CODES.ENTER:
|
|
242
|
+
case KEY_CODES.SPACE: {
|
|
243
|
+
e.preventDefault();
|
|
244
|
+
if (focusedDot !== null) {
|
|
245
|
+
setCurrentSlide(focusedDot);
|
|
246
|
+
}
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
case KEY_CODES.ARROW_LEFT:
|
|
250
|
+
case KEY_CODES.ARROW_UP: {
|
|
251
|
+
e.preventDefault();
|
|
252
|
+
setFocusedDot((prev) => Math.max((prev ?? 0) - 1, 0));
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
case KEY_CODES.ARROW_RIGHT:
|
|
256
|
+
case KEY_CODES.ARROW_DOWN: {
|
|
257
|
+
e.preventDefault();
|
|
258
|
+
setFocusedDot((prev) => Math.min((prev ?? 0) + 1, slideAmount - 1));
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
case KEY_CODES.HOME: {
|
|
262
|
+
e.preventDefault();
|
|
263
|
+
setFocusedDot(0);
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
case KEY_CODES.END: {
|
|
267
|
+
e.preventDefault();
|
|
268
|
+
setFocusedDot(slideAmount - 1);
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
default: {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
const dots = [...Array(slideAmount).keys()].map((_, i) => {
|
|
277
|
+
return (React.createElement(SliderDot, { key: i, index: i, focusedDot: focusedDot, handleFocus: handleFocus, setFocusedDot: setFocusedDot }));
|
|
278
|
+
});
|
|
279
|
+
return (React.createElement("div", { ...props, onFocus: (e) => {
|
|
280
|
+
e.stopPropagation();
|
|
281
|
+
setAutoplayPause(true);
|
|
282
|
+
}, onBlur: () => {
|
|
283
|
+
setAutoplayPause(false);
|
|
284
|
+
}, onMouseLeave: (e) => e.stopPropagation(), className: cj(className, `w-slider-nav ${navInvert ? "w-slider-nav-invert" : ""} ${navShadow ? "w-shadow" : ""} ${navRound ? "w-round" : ""} ${navNumbers ? "w-num" : ""}`) }, dots));
|
|
285
|
+
}
|
|
286
|
+
function SliderDot({ index, focusedDot, handleFocus, setFocusedDot, }) {
|
|
287
|
+
const { slideAmount, navSpacing, navNumbers, slide: { current: selectedSlide }, setCurrentSlide, } = React.useContext(SliderContext);
|
|
288
|
+
const ref = React.useRef(null);
|
|
289
|
+
React.useEffect(() => {
|
|
290
|
+
if (focusedDot === index) {
|
|
291
|
+
ref.current?.focus();
|
|
292
|
+
}
|
|
293
|
+
}, [focusedDot, index]);
|
|
294
|
+
const isSlideActive = selectedSlide == index;
|
|
295
|
+
const label = navNumbers ? index + 1 : "";
|
|
296
|
+
return (React.createElement("div", { className: `w-slider-dot ${isSlideActive ? "w-active" : ""}`, "aria-label": `Show slide ${index + 1} of ${slideAmount}`, "aria-pressed": isSlideActive, role: "button", tabIndex: isSlideActive ? 0 : -1, style: {
|
|
297
|
+
marginRight: `${navSpacing}px`,
|
|
298
|
+
marginLeft: `${navSpacing}px`,
|
|
299
|
+
}, onClick: (e) => {
|
|
300
|
+
e.stopPropagation();
|
|
301
|
+
setFocusedDot(index);
|
|
302
|
+
setCurrentSlide(index);
|
|
303
|
+
}, ref: ref, onKeyDown: handleFocus }, label));
|
|
304
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { EASING_FUNCTIONS } from "../utils";
|
|
3
|
+
import { Props } from "./Basic";
|
|
4
|
+
type TabsWrapperProps = Props<"div", {
|
|
5
|
+
current: string;
|
|
6
|
+
easing: keyof typeof EASING_FUNCTIONS;
|
|
7
|
+
fadeIn: number;
|
|
8
|
+
fadeOut: number;
|
|
9
|
+
children?: React.ReactElement<TabsMenuProps | TabsContentProps>[] | React.ReactElement<TabsMenuProps | TabsContentProps>;
|
|
10
|
+
}>;
|
|
11
|
+
export declare function TabsWrapper({ className, fadeIn, fadeOut, easing, current: initialCurrent, ...props }: TabsWrapperProps): JSX.Element;
|
|
12
|
+
type TabsMenuProps = {
|
|
13
|
+
tag?: React.ElementType;
|
|
14
|
+
className?: string;
|
|
15
|
+
children?: React.ReactElement<TabsLinkProps>[];
|
|
16
|
+
};
|
|
17
|
+
export declare function TabsMenu({ tag, className, ...props }: TabsMenuProps): React.ReactElement<any, string | React.JSXElementConstructor<any>>;
|
|
18
|
+
type TabsLinkProps = Props<"a", {
|
|
19
|
+
"data-w-tab": string;
|
|
20
|
+
}>;
|
|
21
|
+
export declare function TabsLink({ className, ...props }: TabsLinkProps): JSX.Element;
|
|
22
|
+
type TabsContentProps = {
|
|
23
|
+
tag?: React.ElementType;
|
|
24
|
+
className?: string;
|
|
25
|
+
children?: React.ReactElement<TabsPaneProps>[] | React.ReactElement<TabsPaneProps>;
|
|
26
|
+
};
|
|
27
|
+
export declare function TabsContent({ tag, className, ...props }: TabsContentProps): React.ReactElement<any, string | React.JSXElementConstructor<any>>;
|
|
28
|
+
type TabsPaneProps = React.PropsWithChildren<{
|
|
29
|
+
tag?: React.ElementType;
|
|
30
|
+
className?: string;
|
|
31
|
+
"data-w-tab": string;
|
|
32
|
+
}>;
|
|
33
|
+
export declare function TabsPane({ tag, className, ...props }: TabsPaneProps): React.ReactElement<any, string | React.JSXElementConstructor<any>>;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cj, debounce, EASING_FUNCTIONS } from "../utils";
|
|
3
|
+
const tabsContext = React.createContext({
|
|
4
|
+
current: "Tab 1",
|
|
5
|
+
onTabClick: () => { },
|
|
6
|
+
onLinkKeyDown: () => { },
|
|
7
|
+
});
|
|
8
|
+
export function TabsWrapper({ className = "", fadeIn, fadeOut, easing, current: initialCurrent, ...props }) {
|
|
9
|
+
const [current, setCurrent] = React.useState(initialCurrent);
|
|
10
|
+
const changeTab = React.useCallback((next) => {
|
|
11
|
+
const currentTab = document.querySelector(`.w-tab-pane[data-w-tab="${current}"]`);
|
|
12
|
+
const nextTab = document.querySelector(`.w-tab-pane[data-w-tab="${next}"]`);
|
|
13
|
+
const easingFn = EASING_FUNCTIONS[easing] ?? "ease";
|
|
14
|
+
const animation = currentTab?.animate([{ opacity: 1 }, { opacity: 0 }], {
|
|
15
|
+
duration: fadeOut,
|
|
16
|
+
fill: "forwards",
|
|
17
|
+
easing: easingFn,
|
|
18
|
+
});
|
|
19
|
+
if (animation) {
|
|
20
|
+
animation.onfinish = () => {
|
|
21
|
+
setCurrent(next);
|
|
22
|
+
nextTab?.animate([{ opacity: 0 }, { opacity: 1 }], {
|
|
23
|
+
duration: fadeIn,
|
|
24
|
+
fill: "forwards",
|
|
25
|
+
easing: easingFn,
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}, [current, easing, fadeIn, fadeOut]);
|
|
30
|
+
const onTabClick = debounce(changeTab);
|
|
31
|
+
const onLinkKeyDown = debounce((event) => {
|
|
32
|
+
event.preventDefault();
|
|
33
|
+
const currentTab = document.querySelector(`.w-tab-pane[data-w-tab="${current}"]`);
|
|
34
|
+
const allTabs = document.querySelectorAll(".w-tab-pane");
|
|
35
|
+
const firstTab = allTabs[0];
|
|
36
|
+
const lastTab = allTabs[allTabs.length - 1];
|
|
37
|
+
const nextTab = (() => {
|
|
38
|
+
switch (event.key) {
|
|
39
|
+
case "ArrowUp":
|
|
40
|
+
case "ArrowLeft":
|
|
41
|
+
return currentTab.previousElementSibling ?? lastTab;
|
|
42
|
+
case "ArrowDown":
|
|
43
|
+
case "ArrowRight":
|
|
44
|
+
return currentTab.nextElementSibling ?? firstTab;
|
|
45
|
+
case "Home":
|
|
46
|
+
return firstTab;
|
|
47
|
+
case "End":
|
|
48
|
+
return lastTab;
|
|
49
|
+
}
|
|
50
|
+
})();
|
|
51
|
+
if (nextTab)
|
|
52
|
+
changeTab(nextTab.getAttribute("data-w-tab"));
|
|
53
|
+
});
|
|
54
|
+
return (React.createElement(tabsContext.Provider, { value: { current, onTabClick, onLinkKeyDown } },
|
|
55
|
+
React.createElement("div", { className: cj(className, "w-tabs"), ...props })));
|
|
56
|
+
}
|
|
57
|
+
export function TabsMenu({ tag = "div", className = "", ...props }) {
|
|
58
|
+
return React.createElement(tag, {
|
|
59
|
+
className: cj(className, "w-tab-menu"),
|
|
60
|
+
role: "tablist",
|
|
61
|
+
...props,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
export function TabsLink({ className = "", ...props }) {
|
|
65
|
+
const { current, onTabClick, onLinkKeyDown } = React.useContext(tabsContext);
|
|
66
|
+
const isCurrent = current === props["data-w-tab"];
|
|
67
|
+
return (React.createElement("a", { className: cj(className, "w-inline-block w-tab-link", isCurrent && "w--current"), onClick: () => onTabClick(props["data-w-tab"]), onKeyDown: onLinkKeyDown, role: "tab", tabIndex: isCurrent ? 0 : -1, "aria-selected": isCurrent, "aria-controls": props["data-w-tab"], ...props }));
|
|
68
|
+
}
|
|
69
|
+
export function TabsContent({ tag = "div", className = "", ...props }) {
|
|
70
|
+
return React.createElement(tag, {
|
|
71
|
+
className: cj(className, "w-tab-content"),
|
|
72
|
+
...props,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
export function TabsPane({ tag = "div", className = "", ...props }) {
|
|
76
|
+
const { current } = React.useContext(tabsContext);
|
|
77
|
+
const isCurrent = current === props["data-w-tab"];
|
|
78
|
+
return React.createElement(tag, {
|
|
79
|
+
className: cj(className, "w-tab-pane", isCurrent && "w--tab-active"),
|
|
80
|
+
role: "tabpanel",
|
|
81
|
+
"aria-labelledby": props["data-w-tab"],
|
|
82
|
+
...props,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
type TwitterSize = "m" | "l";
|
|
3
|
+
type TwitterMode = "follow" | "tweet";
|
|
4
|
+
type TwitterProps = React.PropsWithChildren<{
|
|
5
|
+
className?: string;
|
|
6
|
+
mode?: TwitterMode;
|
|
7
|
+
url?: string;
|
|
8
|
+
text?: string;
|
|
9
|
+
size?: TwitterSize;
|
|
10
|
+
}>;
|
|
11
|
+
declare global {
|
|
12
|
+
interface Window {
|
|
13
|
+
twttr: any;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export declare function Twitter({ className, url, mode, size, text, ...props }: TwitterProps): JSX.Element;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { isUrl, loadScript } from "../utils";
|
|
3
|
+
const modeDict = {
|
|
4
|
+
follow: "createFollowButton",
|
|
5
|
+
tweet: "createShareButton",
|
|
6
|
+
};
|
|
7
|
+
const sizeDict = {
|
|
8
|
+
m: "medium",
|
|
9
|
+
l: "large",
|
|
10
|
+
};
|
|
11
|
+
export function Twitter({ className = "", url = "https://webflow.com", mode = "tweet", size = "m", text = "Check out this site", ...props }) {
|
|
12
|
+
const ref = React.useRef(null);
|
|
13
|
+
if (!isUrl(url)) {
|
|
14
|
+
if (mode === "tweet") {
|
|
15
|
+
url = "https://webflow.com/";
|
|
16
|
+
}
|
|
17
|
+
else if (mode === "follow") {
|
|
18
|
+
url = "webflow";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
React.useEffect(() => {
|
|
22
|
+
let isComponentMounted = true;
|
|
23
|
+
loadScript("https://platform.twitter.com/widgets.js").then(() => {
|
|
24
|
+
if (isComponentMounted) {
|
|
25
|
+
if (window.twttr) {
|
|
26
|
+
const twitterButtonOption = window.twttr.widgets[modeDict[mode]];
|
|
27
|
+
if (twitterButtonOption) {
|
|
28
|
+
twitterButtonOption(url, ref?.current, {
|
|
29
|
+
size: sizeDict[size],
|
|
30
|
+
text,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return () => {
|
|
37
|
+
isComponentMounted = false;
|
|
38
|
+
};
|
|
39
|
+
}, []);
|
|
40
|
+
return (React.createElement("div", { ...props, className: className + " w-widget w-widget-twitter", ref: ref }));
|
|
41
|
+
}
|