אם אתה כמוני ואתה אוהב תרמילים, אתה יודע כמה סוחף זה ללחוץ על מספר מקשים ולצפות בקסם שקורה. בין אם זה ה- Ctrl+C – Ctrl+V המוכר שמפתחנים משתמשים בו ל "לשאוף קוד" 😉 מ- LLMs ועמודי קוד, או התרמילים האישיים שאנו מגדירים בכלי העבודה האהובים עלינו, תרמילים במקלדת חוסכים זמן ומעניקים לנו תחושה של מומחה במחשב.

אז לא תירא! פיצחתי את הקוד לבניית רכיבים המפעילים ומגיבים לתרמילים במקלדת. במאמר זה, אני אלמד אותך כיצד ליצור אותם עם React, Tailwind CSS, ו- Framer Motion.

תוכן

כאן כל מה שנכסה:

דרישות מקדימות

  • יסודות של HTML, CSS ו-Tailwind CSS

  • יסודות של JavaScript, React ו-React Hooks.

מהו רכיב מאזין לקיצורי מקלדת (KSL)?

רכיב מאזין לקיצורי מקלדת (KSLC) הוא רכיב שמאזין לשילובי מקשים ספציפיים ומפעיל פעולות באפליקציה שלך. הוא תוכנן לגרום לאפליקציה שלך להגיב לקיצורי מקלדת, מה שמאפשר חוויית משתמש חלקה ויעילה יותר.

למה זה חשוב?

  • נגישות: רכיב ה-KSL מקל על אנשים שמשתמשים במקלדת להפעיל פעולות, מה שהופך את האפליקציה שלך ליותר כוללת וקלה לשימוש.

  • ניסיון מהיר יותר: קיצורי דרך הם מהירים ויעילים, מה שמאפשר למשתמשים לבצע דברים בזמן קצר יותר. כבר לא צריך להתעסק עם העכבר—פשוט לחץ על מקש (או שניים) ו boom, הפעולה מתבצעת!

  • שימוש חוזר: ברגע שהגדרת את ה-KSL שלך, הוא יכול לנהל קיצורי דרך שונים ברחבי האפליקציה שלך, מה שמקל על ההוספה מבלי לכתוב מחדש את אותה לוגיקה.

  • קוד נקי: במקום לפזר את המאזינים לאירועי מקלדת בכל מקום, רכיב ה-KSL שומר על סדר על ידי ריכוז הלוגיקה. הקוד שלך נשאר נקי, מאורגן וקלה יותר לתחזוקה.

איך לבנות את רכיב ה-KSL

הכנתי מאגר GitHub עם קבצי התחלה כדי להאיץ את הדברים. פשוט שכפל את המאגר הזה והתקן את התלויות.

לפרויקט הזה, אנו משתמשים בדף הבית של Tailwind כמוזה שלנו ויוצרים את הפונקציונליות של KSL. לאחר התקנה והרצת פקודת הבנייה, הנה איך שהדף שלך אמור להיראות:

איך ליצור את רכיב הגילוי

רכיב הגילוי הוא הרכיב שאנו רוצים להציג כשאנו משתמשים בקיצור.

כדי להתחיל, צור קובץ בשם search-box.tsx והדבק בו את הקוד הזה:

export default function SearchBox() {
  return (
    <div className="fixed top-0 left-0 w-full h-full backdrop-blur-sm bg-slate-900/50 ">
      {" "}
      <div className=" p-[15vh] text-[#939AA7] h-full">
        <div className="max-w-xl mx-auto divide-y divide-[#939AA7] bg-[#1e293b] rounded-md">
          <div className="relative flex justify-between px-4 py-2 text-sm ">
            <div className="flex items-center w-full gap-2 text-white">
              <BiSearch size={20} />
              <input
                type="text"
                className="w-full h-full p-2 bg-transparent focus-within:outline-none"
                placeholder="Search Documentation"
              />
            </div>
            <div className="absolute -translate-y-1/2 right-4 top-1/2 ">
              <kbd className="p-1 text-xs rounded-[4px] bg-[#475569] font-sans font-semibold text-slate-400">
                <abbr title="Escape" className="no-underline ">
                  Esc{" "}
                </abbr>{" "}
              </kbd>
            </div>
          </div>
          <div className="flex items-center justify-center p-10 text-center ">
            <h2 className="text-xl">
              How many licks does it take to get to the center of a Tootsie pop?
            </h2>
          </div>
        </div>
      </div>
    </div>
  );
}

אז, מה קורה בקוד הזה?

  1. שכבת עליונה עיקרית (<div className="fixed top-0 left-0 ...">)

    • זו השכבת מסך מלאה שמעבירה את הרקע.

    • הbackdrop-blur-sm מוסיף טשטוש עדין לרקע, וbg-slate-900/50 נותן לו שכבת כהה חצי שקופה.

  2. מעטפת תיבת חיפוש (<div className="p-[15vh] ...">)

    • התוכן ממורכז באמצעות ריפודים ושירותי פלקס.

    • ה-max-w-xl מבטיח שתיבת החיפוש תישאר ברוחב סביר לקריאות.

אז ב-App.tsx שלך, צור מצב שמראה את הרכיב באופן דינמי:

const [isOpen, setIsOpen] = useState<boolean>(false);
  • useState: פונקציית ה-useState מאתחלת את isOpen ל-false, שפירושו שתיבת החיפוש מוסתרת כברירת מחדל.

  • כאשר isOpen מוגדר לtrue, רכיב הSearchBox יוצג על המסך.

ואת רכיב החיפוש:

  {isOpen && <SearchBox />}

כדי להציג את רכיב החיפוש, הוסף פונקציית התחלפות לכפתור הקלט:

<button
  type="button"
  className="items-center hidden h-12 px-4 space-x-3 text-left rounded-lg shadow-sm sm:flex w-72 ring-slate-900/10 focus:outline-none hover:ring-2 hover:ring-sky-500 focus:ring-2 focus:ring-sky-500 bg-slate-800 ring-0 text-slate-300 highlight-white/5 hover:bg-slate-700"
  onClick={() => setIsOpen(true)}>
  <BiSearch size={20} />
  <span className="flex-auto">Quick search...</span>
   <kbd className="font-sans font-semibold text-slate-500">
   <abbr title="Control" className="no-underline text-slate-500">
    Ctrl{" "}
    </abbr>{" "}
    K
   </kbd>
</button>

אירוע הonClick מגדיר את isOpen לtrue, ומציג את הSearchBox.

אבל כפי שראית, זה הוזמן על ידי פעולה של לחיצה, לא על ידי קיצור מקלדת. בואו נעשה את זה הבא.

איך להפעיל את הרכיב באמצעות קיצור מקלדת

כדי לגרום לרכיב להיפתח ולהיסגר באמצעות קיצור מקלדת, נשתמש בחוט useEffect כדי להאזין לשילובי מקשים ספציפיים ולעדכן את מצב הרכיב בהתאם.

שלב 1: האזן לאירועי מקלדת

הוסף חוט useEffect בקובץ App.tsx שלך כדי להאזין ללחיצות מקשים:

  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.ctrlKey && event.key === Key.K) {
        event.preventDefault(); // מנע התנהגות ברירת מחדל של הדפדפן

      }    };

    window.addEventListener("keydown", handleKeyDown);
    return () => {
      window.removeEventListener("keydown", handleKeyDown);
    };
  }, []);

מה קורה בקוד הזה?

  1. התקנת אפקט (useEffect)

    • useEffect מבטיח שהמאזין לאירועים עבור לחיצות מקשים מתווסף כאשר הרכיב מותקן ומנקה כשהרכיב מוסר, ומונע דליפות זיכרון.
  2. שילוב מקשים (event.ctrlKey && event.key === "k")

    • הevent.ctrlKey בודק אם מקש ה-Control נלחץ.

    • הevent.key === "k" מבטיח שאנו מאזינים במיוחד למקש "K". ביחד, זה בודק אם השילוב Ctrl + K נלחץ.

  3. מניעת התנהגות ברירת מחדל (event.preventDefault())

    • כמה דפדפנים עשויים להכיל התנהגויות ברירת מחדל הקשורות לשילובי מקשים כמו Ctrl + K (לדוגמה, ממוקד את שורת הכתובת של הדפדפן). קריאה ל-preventDefault עוצרת פעולה זו.
  4. ניקוי אירועים (return () => ...)

    • פונקציית הניקוי מסירה את מאזין האירועים כדי למנוע הוספת מאזינים כפולים אם הרכיב מתעדכן מחדש.

שלב 2: שינוי נראות הרכיב

השלב הבא, עדכן את הפונקציה handleKeyDown כדי לשנות את נראות הSearchBox כאשר מקש הקיצור נלחץ:

useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      // האזן ל-Ctrl + K
      if (event.ctrlKey && event.key === Key.K) {
        event.preventDefault(); // מניעת התנהגות ברירת מחדל של הדפדפן
        setIsOpen((prev) => !prev); // שינוי נראות תיבת החיפוש
      } else if (event.key === Key.Escape) {
        setIsOpen(false); // סגירת תיבת החיפוש
      }
    };

    window.addEventListener("keydown", handleKeyDown);
    return () => {
      window.removeEventListener("keydown", handleKeyDown);
    };
  }, []);

מה קורה בקוד הזה?

  1. מעבר מצב (setIsOpen((prev) => !prev))

    • כאשר Ctrl + K נלחץ, המגדיר מצב setIsOpen משנה את נראוּת הSearchBox.

    • הארגומנט prev מייצג את המצב הקודם. השימוש ב!prev הופך את ערכו:

      • true (פתוח) הופך לfalse (סגור).

      • false (סגור) הופך לtrue (פתוח).

  2. סגירה עם מקש ה-Escape (event.key === "Escape")

    • כאשר מקש הEscape נלחץ, setIsOpen(false) קובע במפורש את המצב לfalse, סוגר את הSearchBox.

זה מביא לתוצאה הבאה:

כיצד לאנימט את נראות הרכיב

כרגע, הרכיב שלנו עובד, אך חסר לו קצת סגנון, לא הייתם אומרים? בואו נשנה את זה.

שלב 1: צור את רכיב העלילה

נח开始 על ידי יצירת רכיב עלילה, שפועל כרקע כהה ומטושטש עבור תיבת החיפוש. הנה גרסה בסיסית:

import { ReactNode } from "react";

export default function OverlayWrapper({ children }: { children: ReactNode }) {
  return (
    <div
      className="fixed top-0 left-0 w-full h-full backdrop-blur-sm bg-slate-900/50 ">
      {children}
    </div>
  );
}

שלב 2: הוסף אנימציות לרכיב העלילה

עכשיו, בואו נעשה שהרכיב ידהה פנימה והחוצה באמצעות Framer Motion. עדכן את רכיב OverlayWrapper כך:

import { motion } from "framer-motion";
import { ReactNode } from "react";

export default function OverlayWrapper({ children }: { children: ReactNode }) {
  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
      className="fixed top-0 left-0 w-full h-full backdrop-blur-sm bg-slate-900/50 ">
      {children}
    </motion.div>
  );
}
מאפייני אנימציה עיקריים:
  • initial: קובע את המצב ההתחלתי כאשר הרכיב מותקן (שקוף לחלוטין).

  • animate: מגדיר את המצב לאנימציה לעברו (אופקי לחלוטין).

  • exit: ציינו את האנימציה כאשר הרכיב מוסר (מתעלף).

בשלב הבא, הוסף תנועה לתיבת החיפוש עצמה. נגריל לה להחלק ולהתעלף כאשר היא מופיעה ולהחלק ולהיעלם כאשר היא נעלמת.

import { motion } from "framer-motion";
import { BiSearch } from "react-icons/bi";
import OverlayWrapper from "./overlay";

export default function SearchBox() {
  return (
    <OverlayWrapper>
      <motion.div
        initial={{ y: "-10%", opacity: 0 }}
        animate={{ y: "0%", opacity: 1 }}
        exit={{ y: "-5%", opacity: 0 }}
        className=" p-[15vh] text-[#939AA7] h-full">
        <div
          className="max-w-xl mx-auto divide-y divide-[#939AA7] bg-[#1e293b] rounded-md"
        >
          <div className="relative flex justify-between px-4 py-2 text-sm ">
            <div className="flex items-center w-full gap-2 text-white">
              <BiSearch size={20} />
              <input
                type="text"
                className="w-full h-full p-2 bg-transparent focus-within:outline-none"
                placeholder="Search Documentation"
              />
            </div>
            <div className="absolute -translate-y-1/2 right-4 top-1/2 ">
              <kbd className="p-1 text-xs rounded-[4px] bg-[#475569] font-sans font-semibold text-slate-400">
                <abbr title="Escape" className="no-underline ">
                  Esc{" "}
                </abbr>{" "}
              </kbd>
            </div>
          </div>
          <div className="flex items-center justify-center p-10 text-center ">
            <h2 className="text-xl">
              How many licks does it take to get to the center of a Tootsie pop?
            </h2>
          </div>
        </div>
      </motion.div>
    </OverlayWrapper>
  );
}

שלב 4: אפשר מעקב אנימציה עם AnimatePresence

לבסוף, עטוף את הלוגיקה של העיבוד התנאי ברכיב AnimatePresence המסופק על ידי Framer Motion. זהו מבטיח ש-Framer Motion מעקבת מתי אלמנטים נכנסים ויוצאים מ-DOM.

<AnimatePresence>{isOpen && <SearchBox />}</AnimatePresence>

זה מאפשר ל-Framer Motion לעקוב אחרי כאשר אלמנט נכנס ועוזב את DOM. עם זה, אנו מקבלים את התוצאה הבאה:

אה, טוב יותר מרבה!

כיצד לאופטימיז את רכיב ה-KSL שלך

אם חשבת שסיימנו, לא כל כך מהר… יש לנו עוד קצת לבצע.

אנו צריכים לאופטימיז עבור נגישות. עלינו להוסיף דרך למשתמשים לסגור את רכיב החיפוש בעזרת עכבר, מאחר שנגישות היא חשובה מאוד.

כדי לעשות זאת, התחל ביצירת הוק בשם useClickOutside. ההוק משתמש ברפרנס לאלמנט כדי לדעת מתי משתמש לוחץ מחוץ לאלמנט היעד (תיבת חיפוש) שהיא התנהגות פופולרית מאוד לסגירת מודאלי ו-KSLCs.


import { useEffect } from "react";

type ClickOutsideHandler = (event: Event) => void;

export const useClickOutside = (
  ref: React.RefObject<HTMLElement>,
  handler: ClickOutsideHandler
) => {
  useEffect(() => {
    const listener = (event: Event) => {
      // עשה כלום אם לוחץ על אלמנט או אלמנטים יורשים
      if (!ref.current || ref.current.contains(event.target as Node)) return;

      handler(event);
    };

    document.addEventListener("mousedown", listener);
    document.addEventListener("touchstart", listener);

    return () => {
      document.removeEventListener("mousedown", listener);
      document.removeEventListener("touchstart", listener);
    };
  }, [ref, handler]);
};

כדי להשתמש בהוק זה, שלח אליו את הפונקציה האחראית לפתיחה וסגירה של רכיב החיפוש:

<AnimatePresence> {isOpen && <SearchBox close={setIsOpen} />} </AnimatePresence>

אז קבל את הפונקציה בחיפוש עם סוג הפרופ המתאים שלה:

export default function SearchBox({
  close,
}: {
  close: React.Dispatch<React.SetStateAction<boolean>>;
}) {

לאחר מכן, צור הפניה (ref) לפריט שברצונך לעקוב אחריו וסמן את האלמנט הזה:

import { motion } from "framer-motion";
import { useRef } from "react";
import { BiSearch } from "react-icons/bi";
import { useClickOutside } from "../hooks/useClickOutside";
import OverlayWrapper from "./overlay";

export default function SearchBox({
  close,
}: {
  close: React.Dispatch<React.SetStateAction<boolean>>;
}) {
  const searchboxRef = useRef<HTMLDivElement>(null);
  return (
    <OverlayWrapper>
      <motion.div
        initial={{ y: "-10%", opacity: 0 }}
        animate={{ y: "0%", opacity: 1 }}
        exit={{ y: "-5%", opacity: 0 }}
        className=" p-[15vh] text-[#939AA7] h-full">
        <div
          className="max-w-xl mx-auto divide-y divide-[#939AA7] bg-[#1e293b] rounded-md"
          ref={searchboxRef}>
          <div className="relative flex justify-between px-4 py-2 text-sm ">
            <div className="flex items-center w-full gap-2 text-white">
              <BiSearch size={20} />
              <input
                type="text"
                className="w-full h-full p-2 bg-transparent focus-within:outline-none"
                placeholder="Search Documentation"
              />
            </div>
            <div className="absolute -translate-y-1/2 right-4 top-1/2 ">
              <kbd className="p-1 text-xs rounded-[4px] bg-[#475569] font-sans font-semibold text-slate-400">
                <abbr title="Escape" className="no-underline ">
                  Esc{" "}
                </abbr>{" "}
              </kbd>
            </div>
          </div>
          <div className="flex items-center justify-center p-10 text-center ">
            <h2 className="text-xl">
              How many licks does it take to get to the center of a Tootsie pop?
            </h2>
          </div>
        </div>
      </motion.div>
    </OverlayWrapper>
  );
}

לאחר מכן, העבר את ההפניה הזו ואת הפונקציה שתתבצע כאשר נלחץ מחוץ לאלמנט הזה.

useClickOutside(searchboxRef, () => close(false));

בדיקה עכשיו נותנת את התוצאה הבאה:

אנחנו יכולים גם למטב את הקוד קצת יותר. כמו שעשינו עם תכונת הנגישות, אנחנו יכולים להפוך את המאזין שלנו לזיהוי קיצורי דרך הרבה יותר נקי ויעיל עם השלבים הבאים.

ראשית, צור קובץ הוק בשם useKeyBindings לטיפול בקומבינציות של מקשים.

לאחר מכן, הגדר את ההוק ואת הממשק. ההוק יקבל מערך של קושרות, כאשר כל קישור מורכב מ:

  • מערך keys, שמפרט את קומבינציית המקשים (למשל, ["Control", "k"])

  • פונקציית callback, שמתבצעת כאשר המקשים המתאימים נלחצים.

import { useEffect } from "react";

// הגדר את המבנה של קישור מקש
interface KeyBinding {
  keys: string[]; // מערך של מקשים (למשל, ["Control", "k"])
  callback: () => void; // פונקציה שתתבצע כאשר המקשים נלחצים
}

export const useKeyBindings = (bindings: KeyBinding[]) => {

};

לאחר מכן, צור את הפונקציה handleKeyDown. בתוך ה-hook, הגדר פונקציה שתאזין לאירועי מקלדת. פונקציה זו תבדוק אם המקשים שנלחצו תואמים לכל שילובי המקשים המוגדרים.

ננרמל את המקשים לאותיות קטנות כך שההשוואה תהיה חסרת רגישות לאותיות ונעקוב אחרי המקשים שנלחצו על ידי בדיקה של ctrlKey, shiftKey, altKey, metaKey, והמקש שנלחץ (למשל, "k" עבור Ctrl + K).

const handleKeyDown = (event: KeyboardEvent) => {
  // עקוב אחרי המקשים שנלחצו
  const pressedKeys = new Set<string>();

  // בדוק את מקשי המודיפיקטור (Ctrl, Shift, Alt, Meta)
  if (event.ctrlKey) pressedKeys.add("control");
  if (event.shiftKey) pressedKeys.add("shift");
  if (event.altKey) pressedKeys.add("alt");
  if (event.metaKey) pressedKeys.add("meta");

  // הוסף את המקש שנלחץ (למשל, "k" עבור Ctrl + K)
  if (event.key) pressedKeys.add(event.key.toLowerCase());
};

לאחר מכן, נשווה את המקשים שנלחצו עם מערך המקשים מהקשרים שלנו כדי לבדוק אם הם תואמים. אם הם תואמים, נקרא לפונקציית החזרת הקריאה הקשורה. אנו גם מבטיחים שמספר המקשים שנלחצו תואם למספר המקשים שהוגדרו בקישור.

// חזור על כל קישור מקשים
bindings.forEach(({ keys, callback }) => {
  // ננרמל את המקשים לאותיות קטנות לצורך השוואה
  const normalizedKeys = keys.map((key) => key.toLowerCase());

  // בדוק אם המקשים שנלחצו תואמים לקישור המקשים
  const isMatch =
    pressedKeys.size === normalizedKeys.length &&
    normalizedKeys.every((key) => pressedKeys.has(key));

  // אם המקשים תואמים, קרא לפונקציית החזרת הקריאה
  if (isMatch) {
    event.preventDefault(); // מנע התנהגות ברירת מחדל של הדפדפן
    callback(); // מבצע את פונקציית החזרת הקריאה
  }
});

לבסוף, הגדר מאזינים לאירועים על אובייקט החלון כדי להקשיב לאירועי keydown. המאזינים הללו יפעילו את הפונקציה handleKeyDown בכל פעם שנלחץ מקש. ודא לנקות את המאזינים לאירועים כאשר רכיב מתפרק.

useEffect(() => {
  // הוסף מאזינים לאירועים עבור keydown
  window.addEventListener("keydown", handleKeyDown);

  // נקה את המאזינים לאירועים כאשר הרכיב מתפרק
  return () => {
    window.removeEventListener("keydown", handleKeyDown);
  };
}, [bindings]);

ההוק המלא useKeyBindings עכשיו נראה כך:

import { useEffect } from "react";

interface KeyBinding {
  keys: string[]; // שילוב של מקשים כדי להפעיל את הקריאה חזרה (למשל, ["Control", "k"])
  callback: () => void; // הפונקציה לביצוע כאשר המפתחות נלחצים
}

export function useKeyBindings(bindings: KeyBinding[]) {
  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      bindings.forEach(({ keys, callback }) => {
        const normalizedKeys = keys.map((key) => key.toLowerCase());
        const pressedKeys = new Set<string>();

        // עקוב אחרי מקשי שינוי במפורש
        if (event.ctrlKey) pressedKeys.add("control");
        if (event.shiftKey) pressedKeys.add("shift");
        if (event.altKey) pressedKeys.add("alt");
        if (event.metaKey) pressedKeys.add("meta");

        // הוסף את המקש שנלחץ בפועל
        if (event.key) pressedKeys.add(event.key.toLowerCase());

        // תאם בדיוק: המפתחות הנלחצים חייבים להתאים למפתחות המוגדרים
        const isExactMatch =
          pressedKeys.size === normalizedKeys.length &&
          normalizedKeys.every((key) => pressedKeys.has(key));

        if (isExactMatch) {
          event.preventDefault(); // מנע התנהגות ברירת מחדל
          callback(); // בצע את הקריאה חזרה
        }
      });
    };

    window.addEventListener("keydown", handleKeyDown);
    return () => {
      window.removeEventListener("keydown", handleKeyDown);
    };
  }, [bindings]);
}

כך תוכל להשתמש בהוק הזה בApp שלך:

import { useKeyBindings } from "./hooks/useKeyBindings";

export default function App() {
  const [isOpen, setIsOpen] = useState<boolean>(false);

  useKeyBindings([
    {
      keys: ["Control", "k"], // הקשב ל-"Ctrl + K"
      callback: () => setIsOpen((prev) => !prev), // החלף את תיבת החיפוש
    },
    {
      keys: ["Escape"], // הקשב ל-"Escape"
      callback: () => setIsOpen(false), // סגור את תיבת החיפוש
    },
  ]);

מה שנותן את התוצאה הבאה:

עם גישה זו, תוכלו אפילו להוסיף מספר קיצורי דרך כדי להפעיל את נראות רכיב החיפוש.

useKeyBindings([
    {
      keys: ["Control", "k"], // הקשיבו ל"Ctrl + K"
      callback: () => setIsOpen((prev) => !prev), // החליפו את תיבת החיפוש
    },
    {
      keys: ["Control", "d"], // הקשיבו ל"Ctrl + D"
      callback: () => setIsOpen((prev) => !prev), // החליפו את תיבת החיפוש
    },
    {
      keys: ["Escape"], // הקשיבו ל"Escape"
      callback: () => setIsOpen(false), // סגרו את תיבת החיפוש
    },
  ]);

הנה קישורים לכל המשאבים שתצטרכו למאמר הזה:

סיכום

אני מקווה שהמאמר הזה הרגיש כמו קיצור דרך בזמן הנכון, שהביא אתכם לליבת בניית רכיבי קיצורי דרך שניתן לחזור עליהם. עם כל הקשה ואנימציה, תוכלו עכשיו להפוך חוויות אינטרנט רגילות לבלתי רגילות.

אני מקווה שקיצורי הדרך שלכם יעזרו לכם ליצור אפליקציות שיתפסו את המשתמשים שלכם. אחרי הכל, המסעות הטובים ביותר מתחילים לעיתים קרובות עם השילוב הנכון.

אהבתם את המאמרים שלי?

אל תהססו לקנות לי כוס קפה כאן, כדי לשמור על המחשבה שלי פעילה ולספק יותר מאמרים כאלה.

מידע ליצירת קשר

רוצים להתחבר או ליצור איתי קשר? אל תהססו לפנות אלי באחד מהערוצים הבאים: