טבלה דינמית – סיפור על css variables

משך זמן קריאה: 4 דקות

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

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

 

טבלה בדסקטופ מול טבלה במובייל
מראה של טבלה בדסקטופ מול טבלה במובייל

טבלאות במובייל

כדי ליישם את המראה שבתמונה בטבלה במובייל, אנחנו מפרקים כל TR ומשנים את מאפיין ה-display שלה מ-table-row (המאפיין הדיפולטי שלה) ל-grid. אחרי שהגדרנו את זה, אנחנו נגדיר את כמות העמודות שאנחנו צריכים בעבור העיצוב, וניתן להם שמות על ידי שימוש ב-grid-template-areas.  עכשיו שיש לנו אזורים מוגדרים, נוכל להגדיר לכל תא (TD) לאיזה אזור הוא צריך להכנס.
את הקרדיט על הרעיון להסתכל שונה על כל שורה של טבלה (TR) והיישום של העיצוב בעזרת display:grid אני לא אקח לעצמי, כי אלעד שכטר עשה את לפניי; אני רק נשענתי על הקוד שלו.

//style for TR
.table-row{
  display: grid;
  grid-template-columns:auto 1fr auto auto;
  grid-template-areas:"name name last last" "clock time chg chg-pct";
  column-gap: 8px;
}

//style for TD
.col-name{
  grid-area: name;
}
.col-last{
  grid-area: name;
}

הבעיה

כדי שזה יקרה, אנחנו צריכים בעצם לטרגט כל TD שאנחנו רוצים ולכוון אותו ל-grid-area הנכון. אבל מה קורה אם כל טבלה היא שונה – בעלת מספר שונה של עמודות, וצרכים שונים מבחינת מראה במובייל? הרי לא נרצה להוסיף אין סוף קלאסים לקומפוננטה בכל פעם שמחילים אותה על טבלה חדשה.

הפתרון

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

שלב ראשון

נוסיף ל-type של ה-props של הקומפוננטה 3 מאפיינים: מאפיין בוליאני – האם הטבלה בעיצוב של גריד או לא (היות וישנן עוד אפשרויות תצוגה, כמו עיצוב רגיל או עיצוב עמודה קפואה), ועוד 2 מאפיינים מסוג string שיכילו את מספר העמודות ואת תבנית הגריד:

type TableProps = {
    ....
    mobileGrid?: boolean;
    mobileGridColNum?: string;
    mobileGridTemplate?: string;
};

כעת נגדיר enum של typescript (הסבר מה זה למי שזה לא ידוע לו), שייצג את שמות המשתנים של ה-css שאנחנו מתכוונים לתמוך בהם (הקוד הבא נכתב בזכות אביב משה שסייע לי במימוש ה-JS, כי מה אני מבינה בזה 😉 :

enum CssVars {
    GridColumn = "--grid-columns",
    GridTemplate = "--grid-template",
}

ונייצא את הקומפוננטה של הטבלה:

export const Table: React.FunctionComponent<TableProps> = ({
  mobileGridColNum, 
  mobileGridTemplate, 
  /*other table props*/
  }) => {
  const cssProperties: { [key in CssVars]: string } & 
  React.CSSProperties = {
      [CssVars.GridColumn]: mobileGridColNum,
      [CssVars.GridTemplate]: mobileGridTemplate,
  };   
return (
  <table style={cssProperties}>{children}</table> 
  ); 
}

שימו לב! את ההגדרות של משתני ה-css אני מושכת לתגית style:

<table style={cssProperties}>

אני יודעת, זה נראה לא נכון. מפתאום שימוש ב-inline-style?! אבל זהו בעצם השימוש הנכון ל-inline-style: אנחנו לא מגדירים פה ערכים אבסולוטיים אלא ערכים דינאמיים.

 

שלב שני

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

type TableCellProps = {
    ....
    tdPosition?: string;
}

enum CssVars {
    cellPosition = "--cell-positions",
}

וכמו בהגדרות של הטבלה, נגדיר גם את ההגדרה על תא:

export const TableCell: React.FunctionComponent<TableCellProps> = ({
  tdPosition
  /*other table props*/
}) => {
    const cssProperties: { [key in CssVars]: string | undefined } & 
    React.CSSProperties = {
        [CssVars.cellPosition]: tdPosition,
    };  
return (
  <td style={cssProperties}>{children}</td>
  );
};

 

שלב שלישי

בקובץ scss של הקומפוננטה נשתמש במשתני ה-css שהגדרנו:

.table-mobile-grid {
  .row {
    display: grid;
    grid-template-columns: var(--grid-columns);
    grid-template-areas: var(--grid-template);
    grid-gap: 8px;
    
  }
  .cell {
    grid-area: var(--cell-positions);
  }
}

 

שלב רביעי

בכל פעם שנשתמש בקומפוננטה של הטבלה, ניתן יהיה להגדיר את מראה המובייל שלה:

<Table
    mobileGrid
    mobileGridColNum="auto 1fr auto"
    mobileGridTemplate={'"clock name price" 
                         "recommendation recommendation recommendation"'}
>
  <TableRow>
    <MarketOpenCell tdPosition="clock"/>
    <LinkCell tdPosition="name" />
    <LastCell tdPosition="price" />
    <TechnicalCell tdPosition="recommendation"/>
  </TableRow>
</Table>

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

 

דבר אחרון

היות ואנחנו לא מציגים את כל התאים שבשורה כאשר הטבלה שלנו מוגדרת בתצוגת גריד במובייל, כל התאים (TD) מקבלים display: none. לכן בנוסף לשימוש במשתנה css כדי לקבוע את המיקום של כל תא, הוספתי מאפיין נוסף שבו ניתן להגדיר את אופן ה-display שלו. אני לא משתמשת בערך בוליאני כדי לקבוע אם להציג או לא, היות ולפעמים אני רוצה להחיל display:flex על התא (בעיקר כדי ליישר אופקית אלמנטים בתא).

וכך זה נראה:

.table-mobile-grid {
  .row {
    display: grid;
    grid-template-columns: var(--grid-columns);
    grid-template-areas: var(--grid-template);
    grid-gap: 8px;
  }
  .cell {
    --cell-display: none;
    display: var(--cell-display);
    grid-area: var(--cell-positions);
  }
}

 

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

ולינק אחרון לקריאה, אמנם לא קשור לטבלאות אבל כן קשור לשימוש במשתני css וריאקט – Use CSS Variables instead of React Context

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *