PluginProbe
Smart Post – Post Grid, Post Carousel, Post Slider Gutenberg Blocks for Blog & News / 4.0.7
Smart Post – Post Grid, Post Carousel, Post Slider Gutenberg Blocks for Blog & News v4.0.7
4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 4.0.2 4.0.1 2.3.5 2.3.6 2.4.0 2.4.1 2.4.10 2.4.11 2.4.12 2.4.13 2.4.14 2.4.15 2.4.16 2.4.17 2.4.18 2.4.19 2.4.2 2.4.20 2.4.21 All 88 releases
post-carousel / src / components / typography / typography.js

typography.js in Smart Post – Post Grid, Post Carousel, Post Slider Gutenberg Blocks for Blog & News 4.0.7, at src/components/typography/typography.js

709 lines 20.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import Select, { components } from "react-select";
2 import { __ } from "@wordpress/i18n";
3 import { Button, Popover, RangeControl, Flex, Tooltip } from "@wordpress/components";
4 import { useEffect, useRef, useState } from "@wordpress/element";
5 import SPRangeControl from "../rangeControl/rangeControl.js";
6 import ComponentsTopSection from "../componentsTopControl/ComponentsTopSection.js";
7 import { EditIcon } from "./svgIcon.js";
8 import "./editor.scss";
9 import SelectField from "../selectField/selectField.js";
10 import {
11 fontWeightMap,
12 getFontSizePresets,
13 getFontWeightList,
14 getGlobalTypoPresets,
15 textCaseOptions,
16 textDecorationOptions,
17 } from "./utility.js";
18 import InputControl from "../inputControl/inputControl.js";
19 import { useDeviceType } from "../../controls/controls.js";
20 import Divider from "../divider/divider.js";
21 import classNames from "classnames";
22 import { BorderIcon, GlobalIcon } from "../../icons/icons.js";
23 import { dispatch, useSelect, select } from "@wordpress/data";
24 import useGoogleFonts from "../../hooks/useGoogleFontApi.js";
25
26 const TypographyNew = ({
27 setAttributes,
28 attributes,
29 fontSizeDefault = { unit: "px", value: 16 },
30 lineDefaultValue = "",
31 typographyLabel = "Typography",
32 fontSizePresetType = "body",
33 onStateUpdate = false,
34 }) => {
35 const fontFamilies = useSelect((_select) => {
36 const settings = _select("core/editor")?.getEditorSettings();
37 return settings?.__experimentalFeatures?.typography?.fontFamilies || [];
38 }, []);
39
40 const [systemFonts, setSystemFonts] = useState([]);
41 const [allFonts, setAllFonts] = useState([]);
42 const [fontLists, setFontLists] = useState([]);
43 const {
44 family,
45 familyKey,
46 fontSize,
47 fontSizeKey,
48 lineHeight,
49 lineHeightKey,
50 fontSpacing,
51 fontSpacingKey,
52 wordSpacing,
53 wordSpacingKey,
54 globalTypo,
55 globalTypoKey,
56 } = attributes;
57 const deviceType = useDeviceType();
58 const fontSizePresets = getFontSizePresets(fontSizePresetType);
59 const globalTypoOptions = !onStateUpdate ? getGlobalTypoPresets() : {};
60
61 const [isVisible, setIsVisible] = useState(false);
62 const [isCustomFontSize, setIsCustomFontSize] = useState(
63 (!fontSizePresets?.find((preset) => preset?.value === fontSize?.device?.[deviceType]) &&
64 fontSize?.device?.[deviceType] !== "") ||
65 false
66 );
67 const typoBtnRef = useRef(null);
68 const openPluginSidebar = () => {
69 const sidebarName = "smart-post-show-pro-global-settings/sidebar";
70 if (select("core/edit-site")) {
71 dispatch("core/edit-site").openGeneralSidebar(sidebarName);
72 } else if (select("core/edit-post")) {
73 dispatch("core/edit-post").openGeneralSidebar(sidebarName);
74 } else if (select("core/customize-widgets")) {
75 dispatch("core/customize-widgets").openGeneralSidebar(sidebarName);
76 } else {
77 console.log("Could not determine current editor type");
78 }
79 };
80 const toggleVisible = () => {
81 setIsVisible((state) => !state);
82 };
83
84 const { googleFonts } = useGoogleFonts();
85
86 const rendKey = () => {
87 return Math.random().toString(36).substring(2, 9);
88 };
89
90 useEffect(() => {
91 if (allFonts.length === 0 && googleFonts.length > 0) {
92 let wpFonts = [];
93 if (fontFamilies) {
94 const customFonts = fontFamilies.custom || [];
95 const themeFonts = fontFamilies.theme || [];
96 const _systemFonts = [...customFonts, ...themeFonts];
97 wpFonts = _systemFonts?.map((f) => {
98 const variants = f?.fontFace?.map((v) => v.fontWeight);
99 let variantsArray = [];
100 if (variants?.length > 0) {
101 variantsArray = variants?.length === 1 ? variants[0]?.split(" ") : variants;
102 } else {
103 variantsArray = ["300", "400", "500", "600", "700", "800"];
104 }
105 return {
106 label: f.name,
107 value: f.fontFamily,
108 font: {
109 family: f.fontFamily,
110 variants: variantsArray,
111 },
112 };
113 });
114 }
115
116 const grouped = [
117 ...(wpFonts.length > 0 ? [{ label: "System Fonts", options: wpFonts }] : []),
118 {
119 label: "Google Fonts",
120 options: googleFonts.filter((font, i) => i < 50 && font),
121 },
122 ];
123 setSystemFonts(wpFonts);
124 setAllFonts([...wpFonts, ...googleFonts]);
125 setFontLists(grouped);
126 }
127 }, [googleFonts, fontFamilies, allFonts]);
128 const fontSearch = (inputValue) => {
129 if (!inputValue) {
130 setFontLists([
131 ...(systemFonts.length > 0
132 ? [
133 {
134 label: "System Fonts",
135 options: systemFonts,
136 },
137 ]
138 : []),
139 {
140 label: "Google Fonts",
141 options: googleFonts.filter((font, i) => i < 50 && font),
142 },
143 ]);
144 return;
145 }
146
147 const searchedFonts = allFonts
148 .filter((font) => font.label.toLowerCase().includes(inputValue.toLowerCase()))
149 .slice(0, 30);
150 setFontLists([
151 {
152 label: "Search Result",
153 options: searchedFonts,
154 },
155 ]);
156 };
157 const typoBtnActiveClass = `.sp-${rendKey()}`;
158
159 useEffect(() => {
160 const clickOutSite = (e) => {
161 const target = e.target.closest(".sp-smart-post-typography-fonts");
162 const buttonTarget = e.target.closest(`.sp-smart-post-typography-trigger button${typoBtnActiveClass}`);
163 const familyTarget = e.target.closest(".css-1nmdiq5-menu");
164 if (!target && isVisible && !buttonTarget && !familyTarget && !typoBtnRef.current?.contains(e.target)) {
165 setIsVisible(false);
166 }
167 };
168 window.addEventListener("click", clickOutSite);
169
170 return () => window.removeEventListener("click", clickOutSite);
171 });
172
173 // default and active family options.
174 const defaultFamilyOption = {
175 label: "Default",
176 value: "Default",
177 font: {
178 family: "Default",
179 variants: ["300", "400", "500", "600", "700", "800"],
180 },
181 };
182
183 const activeFontFamily =
184 family.typography.family === ""
185 ? defaultFamilyOption
186 : {
187 label:
188 allFonts?.find((font) => font.value === family.typography.family)?.label ||
189 family.typography.family,
190 value: family.googleFont?.family,
191 font: family.googleFont,
192 };
193
194 const allFamilyList = [defaultFamilyOption, ...fontLists];
195
196 const isAvailableOnList = allFamilyList?.find((font) => font.value === activeFontFamily.value);
197
198 const fontFamilySelectOptions = isAvailableOnList
199 ? allFamilyList
200 : [defaultFamilyOption, activeFontFamily, ...fontLists];
201
202 // font family.
203 const { fontWeight, style } = family.typography;
204
205 const onChangeFontStyle = (fontStyle) => {
206 const arrayOfStyles = fontWeightMap[fontStyle]?.split(" ");
207 const _style = fontWeightMap[fontStyle].includes("italic") ? "italic" : "";
208 const _fontWeight = arrayOfStyles[arrayOfStyles?.length - 1];
209
210 if (onStateUpdate) {
211 onStateUpdate(familyKey, {
212 ...family,
213 typography: {
214 ...family.typography,
215 fontWeight: _fontWeight,
216 style: _style,
217 },
218 });
219 return;
220 }
221
222 setAttributes({
223 [familyKey]: {
224 ...family,
225 typography: {
226 ...family.typography,
227 fontWeight: _fontWeight,
228 style: _style,
229 },
230 },
231 });
232 };
233
234 const onChangeFontSize = (newValue) => {
235 setAttributes({
236 [fontSizeKey]: {
237 ...fontSize,
238 device: {
239 ...fontSize?.device,
240 [deviceType]: newValue.value,
241 },
242 unit: {
243 ...fontSize.unit,
244 [deviceType]: fontSize?.unit?.[deviceType] ? fontSize?.unit?.[deviceType] : fontSizeDefault.unit,
245 },
246 },
247 });
248 };
249 const onChangeTextStyles = (key, value) => {
250 const newValue = family?.typography[key] === value ? "" : value;
251 if (onStateUpdate) {
252 onStateUpdate(familyKey, {
253 ...family,
254 typography: {
255 ...family.typography,
256 [key]: newValue,
257 },
258 });
259 return;
260 }
261 setAttributes({
262 [familyKey]: {
263 ...family,
264 typography: {
265 ...family.typography,
266 [key]: newValue,
267 },
268 },
269 });
270 };
271 const onChangeLineHeight = (newValue) => {
272 if (onStateUpdate) {
273 onStateUpdate(lineHeightKey, { ...lineHeight, value: newValue?.value });
274 } else {
275 setAttributes({
276 [lineHeightKey]: {
277 ...lineHeight,
278 device: { ...lineHeight.device, [newValue.deviceType]: newValue?.value },
279 },
280 });
281 }
282 };
283
284 const onGlobalTypoChange = (value) => {
285 // reset typography
286 setAttributes({
287 [globalTypoKey]: value,
288 [fontSizeKey]: {
289 ...fontSize,
290 device: {
291 Desktop: value.fontSize,
292 Tablet: value.fontSize,
293 Mobile: value.fontSize,
294 },
295 unit: {
296 Desktop: "",
297 Tablet: "",
298 Mobile: "",
299 },
300 },
301 [lineHeightKey]: {
302 ...lineHeight,
303 device: {
304 Desktop: value.lineHeight,
305 Tablet: value.lineHeight,
306 Mobile: value.lineHeight,
307 },
308 unit: {
309 Desktop: "",
310 Tablet: "",
311 Mobile: "",
312 },
313 },
314 [familyKey]: {
315 ...family,
316 typography: {
317 ...family.typography,
318 family: "",
319 fontWeight: "",
320 style: "",
321 transform: "",
322 decoration: "",
323 },
324 },
325 [fontSpacingKey]: {
326 ...fontSpacing,
327 device: {
328 Desktop: 0,
329 Tablet: 0,
330 Mobile: 0,
331 },
332 },
333 [wordSpacingKey]: {
334 ...wordSpacing,
335 device: {
336 Desktop: 0,
337 Tablet: 0,
338 Mobile: 0,
339 },
340 },
341 });
342 };
343
344 const activeGlobalTypo = globalTypo?.length === 0 ? { label: "Default", value: "" } : globalTypo;
345
346 return (
347 <div className="sp-smart-post-typography sp-smart-post-component-mb">
348 <Flex justify="space-between" align="center" className="sp-smart-post-typography-trigger">
349 <p> {typographyLabel}</p>
350 <Button
351 ref={typoBtnRef}
352 aria-label={isVisible ? "open typography popup" : "close typography popup"}
353 onClick={() => toggleVisible()}
354 size="compact"
355 className={classNames(isVisible ? `sp-${rendKey()} active` : `sp-${rendKey()}`)}
356 icon={<EditIcon />}
357 />
358 </Flex>
359
360 {isVisible && (
361 <Popover shift={true} focusOnMount={false}>
362 <div className={`sp-smart-post-typography-fonts`}>
363 <div className="sp-smart-post-typography-header">
364 <h4>{__("Typography", "post-carousel")}</h4>
365 {!onStateUpdate && (
366 <Button onClick={openPluginSidebar} size="compact" icon={<GlobalIcon />} />
367 )}
368 </div>
369
370 {!onStateUpdate && (
371 <>
372 <div className="sp-smart-post-select-field sp-smart-post-component-mb">
373 <span className="sp-smart-post-component-title">
374 {__("Select Global Style", "post-carousel")}
375 </span>
376
377 <Select
378 className="sp-smart-post-select-react sp-smart-post-wrapper"
379 options={[{ label: "Default", value: "" }, ...globalTypoOptions]}
380 value={activeGlobalTypo}
381 placeholder={"Select Global"}
382 onChange={(value) => onGlobalTypoChange(value)}
383 formatOptionLabel={(option) => (
384 <span
385 className={option.class}
386 style={{
387 fontSize: option.fontSize,
388 lineHeight: option.lineHeight,
389 }}
390 >
391 {option.label}
392 </span>
393 )}
394 components={{
395 SingleValue: ({ children, data, ...props }) => {
396 return (
397 <components.SingleValue {...props}>
398 {data.label}
399 </components.SingleValue>
400 );
401 },
402 }}
403 />
404 </div>
405 <Divider position={"sp-w-100pct bottom"} text="Customize" />
406 </>
407 )}
408
409 <div className="sp-smart-post-typography-fields">
410 <div className={`sp-smart-post-typography-family sp-smart-post-component-mb`}>
411 <div className="sp-smart-post-select-field">
412 <div className="sp-smart-post-header">
413 <span className="sp-smart-post-component-title">
414 {__("Font Family", "post-carousel")}
415 </span>
416 </div>
417
418 <Select
419 options={fontFamilySelectOptions}
420 value={activeFontFamily}
421 placeholder={activeFontFamily.label}
422 onChange={(nextFont) =>
423 onStateUpdate
424 ? onStateUpdate(familyKey, {
425 ...family,
426 googleFont: nextFont?.font,
427 typography: {
428 ...family.typography,
429 family:
430 "Default" !== nextFont?.font?.family
431 ? nextFont?.font?.family
432 : "",
433 fontWeight:
434 "regular" === nextFont?.font?.variants[0]
435 ? "400"
436 : nextFont?.font?.variants[0],
437 },
438 })
439 : setAttributes({
440 [familyKey]: {
441 ...family,
442 googleFont: nextFont?.font,
443 typography: {
444 ...family.typography,
445 family:
446 "Default" !== nextFont?.font?.family
447 ? nextFont?.font?.family
448 : "",
449 fontWeight:
450 "regular" === nextFont?.font?.variants[0]
451 ? "400"
452 : nextFont?.font?.variants[0],
453 },
454 },
455 })
456 }
457 onInputChange={(inputValue) => fontSearch(inputValue)}
458 />
459 </div>
460 </div>
461 <SelectField
462 label={__("Font Style", "post-carousel")}
463 attributes={
464 activeFontFamily?.font?.variants.includes(`${fontWeight}${style}`)
465 ? `${fontWeight}${style}`
466 : fontWeight
467 }
468 items={getFontWeightList(activeFontFamily)}
469 onChange={(newStyle) => onChangeFontStyle(newStyle)}
470 __nextHasNoMarginBottom
471 />
472 </div>
473
474 <div className="sp-smart-post-typography-multiple-button-group sp-smart-post-component-mb">
475 <ComponentsTopSection
476 label={__("Font Size", "post-carousel")}
477 units={["px", "em"]}
478 attributes={fontSize}
479 setAttributes={setAttributes}
480 attributesKey={fontSizeKey}
481 onReset={() =>
482 !onStateUpdate &&
483 setAttributes({
484 [fontSizeKey]: {
485 ...fontSize,
486 device: {
487 ...fontSize?.device,
488 [deviceType]: fontSizeDefault.value,
489 },
490 unit: {
491 ...fontSize.unit,
492 [deviceType]: fontSizeDefault.unit,
493 },
494 },
495 })
496 }
497 onUnitChange={(newValue) =>
498 onStateUpdate
499 ? onStateUpdate(fontSizeKey, { ...fontSize, unit: newValue?.unit })
500 : setAttributes({
501 [fontSizeKey]: {
502 ...attributes.fontSize,
503 unit: {
504 ...attributes.fontSize.unit,
505 [deviceType]: newValue,
506 },
507 },
508 })
509 }
510 />
511 <div
512 className={classNames(
513 "sp-smart-post-typography-font-size-presets",
514 isCustomFontSize && "active"
515 )}
516 >
517 {isCustomFontSize ? (
518 <RangeControl
519 // value={ ajax ? currentValue : value }
520 value={fontSize.device?.[deviceType] || fontSize?.value}
521 color="var(--sp-smart-primary-2-400)"
522 onChange={(newValue) =>
523 onStateUpdate
524 ? onStateUpdate(fontSizeKey, { ...fontSize, value: newValue })
525 : onChangeFontSize({
526 value: newValue,
527 })
528 }
529 min={0}
530 max={200}
531 step={1}
532 __nextHasNoMarginBottom={true}
533 __next40pxDefaultSize
534 />
535 ) : (
536 <div className="sp-smart-post-button-group-list">
537 {fontSizePresets?.map(({ label, value }, i) => (
538 <button
539 key={i}
540 className={`components-button ${
541 [fontSize?.device?.[deviceType], fontSize?.value].includes(value)
542 ? " active"
543 : ""
544 }`}
545 onClick={() =>
546 onStateUpdate
547 ? onStateUpdate(fontSizeKey, { ...fontSize, value })
548 : onChangeFontSize({
549 value,
550 })
551 }
552 >
553 <span title={value}>{label}</span>
554 </button>
555 ))}
556 </div>
557 )}
558
559 <Button onClick={() => setIsCustomFontSize((prev) => !prev)}>
560 <BorderIcon isActive={isCustomFontSize} />
561 </Button>
562 </div>
563 </div>
564
565 <SPRangeControl
566 label={__("Line Height", "post-carousel")}
567 setAttributes={setAttributes}
568 attributes={lineHeight}
569 attributesKey={lineHeightKey}
570 max={6}
571 step={0.1}
572 defaultValue={{ value: lineDefaultValue }}
573 typoLineHeight={true}
574 onValueChange={(newValue) => onChangeLineHeight(newValue)}
575 />
576
577 <div className="sp-smart-post-typography-word-spacing-latter-spacing-wrapper sp-d-flex sp-gap-8px">
578 <div className="sp-smart-post-typography-line-height-picker">
579 <ComponentsTopSection
580 label={__("Letter Spacing", "post-carousel")}
581 attributes={fontSpacing}
582 attributesKey={fontSpacingKey}
583 setAttributes={setAttributes}
584 units={["px", "em"]}
585 onUnitChange={(newValue) =>
586 onStateUpdate
587 ? onStateUpdate(fontSpacingKey, { ...fontSpacing, unit: newValue })
588 : setAttributes({
589 [fontSpacingKey]: {
590 ...attributes.fontSpacing,
591 unit: {
592 ...attributes.fontSpacing.unit,
593 [deviceType]: newValue,
594 },
595 },
596 })
597 }
598 />
599
600 <InputControl
601 attributes={fontSpacing?.device?.[deviceType] || fontSpacing?.value}
602 type="number"
603 onChange={(newValue) =>
604 onStateUpdate
605 ? onStateUpdate(fontSpacingKey, { ...fontSpacing, value: newValue })
606 : setAttributes({
607 [fontSpacingKey]: {
608 ...fontSpacing,
609 device: {
610 ...fontSpacing?.device,
611 [deviceType]: newValue,
612 },
613 },
614 })
615 }
616 min={0}
617 />
618 </div>
619 {wordSpacing && (
620 <div className="sp-smart-post-typography-letter-spacing-picker">
621 <ComponentsTopSection
622 label={__("Word Spacing", "post-carousel")}
623 attributes={wordSpacing}
624 attributesKey={wordSpacingKey}
625 setAttributes={setAttributes}
626 units={["px", "em"]}
627 onUnitChange={(newValue) =>
628 onStateUpdate
629 ? onStateUpdate(wordSpacingKey, { ...wordSpacing, unit: newValue })
630 : setAttributes({
631 [wordSpacingKey]: {
632 ...attributes.wordSpacing,
633 unit: {
634 ...attributes.wordSpacing.unit,
635 [deviceType]: newValue,
636 },
637 },
638 })
639 }
640 />
641 <InputControl
642 attributes={wordSpacing?.device?.[deviceType] || wordSpacing?.value}
643 type="number"
644 onChange={(newValue) =>
645 onStateUpdate
646 ? onStateUpdate(wordSpacingKey, { ...wordSpacing, value: newValue })
647 : setAttributes({
648 [wordSpacingKey]: {
649 ...wordSpacing,
650 device: {
651 ...wordSpacing?.device,
652 [deviceType]: newValue,
653 },
654 },
655 })
656 }
657 min={0}
658 />
659 </div>
660 )}
661 </div>
662
663 <Flex align="center">
664 <div className="sp-smart-post-typography-multiple-button-group sp-smart-post-component-mb">
665 <ComponentsTopSection label={__("Decoration", "post-carousel")} />
666
667 <div className="sp-smart-post-button-group-list">
668 {textDecorationOptions?.map(({ label, key, value }, i) => (
669 <Tooltip placement="top" text={value} key={i}>
670 <button
671 className={`components-button${
672 family?.typography[key] === value ? " active" : ""
673 }`}
674 onClick={() => onChangeTextStyles(key, value)}
675 >
676 <span title={value}>{label}</span>
677 </button>
678 </Tooltip>
679 ))}
680 </div>
681 </div>
682 <div className="sp-smart-post-typography-multiple-button-group sp-smart-post-component-mb">
683 <ComponentsTopSection label={__("Case", "post-carousel")} />
684
685 <div className="sp-smart-post-button-group-list">
686 {textCaseOptions?.map(({ label, key, value }, i) => (
687 <Tooltip placement="top" text={value} key={i}>
688 <button
689 className={`components-button ${
690 family?.typography[key] === value ? " active" : ""
691 }`}
692 onClick={() => onChangeTextStyles(key, value)}
693 >
694 <span title={value}>{label}</span>
695 </button>
696 </Tooltip>
697 ))}
698 </div>
699 </div>
700 </Flex>
701 </div>
702 </Popover>
703 )}
704 </div>
705 );
706 };
707
708 export default TypographyNew;
709