לדלג לתוכן

8.6 מנטין מתקדם הרצאה

מנטין מתקדם - Advanced Mantine

בשיעור זה נלמד טכניקות מתקדמות של Mantine: יצירת theme מותאם, שימוש ב-Styles API, משתני CSS, סגנונות רספונסיביים, קומפוננטות פולימורפיות, ובניית קומפוננטה מותאמת עם factory.


יצירת Theme מותאם עם createTheme

הגדרה מלאה של theme

import { createTheme, MantineColorsTuple, MantineProvider } from "@mantine/core";

const brand: MantineColorsTuple = [
  "#f0f4ff",
  "#dce4f5",
  "#b4c6e7",
  "#8aa7db",
  "#668dd0",
  "#4e7dc9",
  "#4275c7",
  "#3363b0",
  "#29589e",
  "#1a4b8c",
];

const theme = createTheme({
  // צבעים
  colors: {
    brand,
  },
  primaryColor: "brand",
  primaryShade: { light: 6, dark: 8 },

  // פונטים
  fontFamily: "Rubik, sans-serif",
  fontFamilyMonospace: "JetBrains Mono, monospace",
  headings: {
    fontFamily: "Heebo, sans-serif",
    fontWeight: "700",
    sizes: {
      h1: { fontSize: "2.5rem", lineHeight: "1.2" },
      h2: { fontSize: "2rem", lineHeight: "1.3" },
      h3: { fontSize: "1.5rem", lineHeight: "1.4" },
    },
  },

  // גדלים
  fontSizes: {
    xs: "0.75rem",
    sm: "0.875rem",
    md: "1rem",
    lg: "1.125rem",
    xl: "1.25rem",
  },

  spacing: {
    xs: "0.5rem",
    sm: "0.75rem",
    md: "1rem",
    lg: "1.5rem",
    xl: "2rem",
  },

  radius: {
    xs: "2px",
    sm: "4px",
    md: "8px",
    lg: "16px",
    xl: "32px",
  },

  // ברירות מחדל
  defaultRadius: "md",
  cursorType: "pointer",

  // breakpoints
  breakpoints: {
    xs: "36em",
    sm: "48em",
    md: "62em",
    lg: "75em",
    xl: "88em",
  },

  // דריסת ברירות מחדל של קומפוננטות
  components: {
    Button: {
      defaultProps: {
        radius: "md",
      },
    },
    TextInput: {
      defaultProps: {
        radius: "md",
      },
    },
    Paper: {
      defaultProps: {
        radius: "md",
        shadow: "sm",
      },
    },
    Card: {
      defaultProps: {
        radius: "md",
        shadow: "sm",
        withBorder: true,
      },
    },
  },
});

function App() {
  return (
    <MantineProvider theme={theme}>
      {/* כל הקומפוננטות ישתמשו ב-theme הזה */}
    </MantineProvider>
  );
}
  • primaryShade מאפשר הגדרת גוון שונה למצב בהיר וכהה
  • components מאפשר הגדרת ברירות מחדל לכל קומפוננטה
  • כל ערכי ה-theme זמינים דרך משתני CSS

גישה ל-theme בקומפוננטות

import { useMantineTheme } from "@mantine/core";

function ThemeAwareComponent() {
  const theme = useMantineTheme();

  return (
    <div style={{ color: theme.colors.brand[6] }}>
      צבע מהתימה
    </div>
  );
}

Styles API - שליטה על סגנונות

כל קומפוננטה של Mantine מורכבת מחלקים פנימיים שניתן לעצב בנפרד.

classNames - הוספת מחלקות CSS

/* CustomButton.module.css */
.root {
  font-weight: 600;
  letter-spacing: 0.5px;
}

.label {
  text-transform: uppercase;
}

.inner {
  gap: 8px;
}
import { Button } from "@mantine/core";
import styles from "./CustomButton.module.css";

function CustomButton() {
  return (
    <Button
      classNames={{
        root: styles.root,
        label: styles.label,
        inner: styles.inner,
      }}
    >
      כפתור מותאם
    </Button>
  );
}

styles prop - סגנונות אינליין לכל חלק

import { TextInput } from "@mantine/core";

function CustomInput() {
  return (
    <TextInput
      label="שם"
      styles={{
        root: { marginBottom: 16 },
        label: { fontWeight: 700, fontSize: 14, color: "#333" },
        input: {
          borderColor: "#4275c7",
          "&:focus": { borderColor: "#1a4b8c" },
        },
        error: { fontSize: 12, marginTop: 4 },
      }}
    />
  );
}

הגדרת סגנונות ברמת ה-theme

const theme = createTheme({
  components: {
    Button: {
      classNames: {
        root: "my-button-root",
        label: "my-button-label",
      },
      styles: {
        root: { fontWeight: 600 },
      },
    },

    TextInput: {
      styles: (theme) => ({
        label: {
          fontWeight: 600,
          marginBottom: 4,
        },
        input: {
          borderRadius: theme.radius.md,
        },
      }),
    },

    Card: {
      defaultProps: {
        shadow: "sm",
        radius: "md",
        withBorder: true,
      },
      styles: {
        root: {
          transition: "box-shadow 0.2s ease",
          "&:hover": {
            boxShadow: "var(--mantine-shadow-md)",
          },
        },
      },
    },
  },
});

משתני CSS ו-Theme Tokens

Mantine מייצרת משתני CSS אוטומטית מה-theme:

משתנים זמינים

/* צבעים */
var(--mantine-color-blue-5)
var(--mantine-color-brand-6)
var(--mantine-primary-color-filled)
var(--mantine-primary-color-light)

/* פונטים */
var(--mantine-font-family)
var(--mantine-font-family-monospace)
var(--mantine-font-size-md)

/* ריווח */
var(--mantine-spacing-md)
var(--mantine-spacing-lg)

/* רדיוס */
var(--mantine-radius-md)
var(--mantine-radius-lg)

/* צל */
var(--mantine-shadow-sm)
var(--mantine-shadow-md)

/* Breakpoints */
var(--mantine-breakpoint-sm)
var(--mantine-breakpoint-md)

/* מצב בהיר/כהה */
var(--mantine-color-scheme)

שימוש ב-CSS Modules עם משתני Mantine

/* Dashboard.module.css */
.card {
  padding: var(--mantine-spacing-lg);
  border-radius: var(--mantine-radius-md);
  background-color: var(--mantine-color-body);
  border: 1px solid var(--mantine-color-default-border);
  transition: box-shadow 0.2s ease;
}

.card:hover {
  box-shadow: var(--mantine-shadow-md);
}

.title {
  font-family: var(--mantine-font-family-headings);
  font-size: var(--mantine-font-size-lg);
  font-weight: 700;
  color: var(--mantine-color-text);
}

.description {
  font-size: var(--mantine-font-size-sm);
  color: var(--mantine-color-dimmed);
  line-height: 1.6;
}

/* שימוש במצב כהה */
.highlight {
  background-color: var(--mantine-color-blue-0);
}

[data-mantine-color-scheme="dark"] .highlight {
  background-color: var(--mantine-color-blue-9);
}

הוספת משתני CSS מותאמים

import { createTheme, CSSVariablesResolver } from "@mantine/core";

const resolver: CSSVariablesResolver = (theme) => ({
  variables: {
    "--sidebar-width": "250px",
    "--header-height": "60px",
    "--content-max-width": "1200px",
  },
  light: {
    "--app-bg": theme.colors.gray[0],
    "--card-bg": theme.white,
    "--text-primary": theme.colors.gray[9],
  },
  dark: {
    "--app-bg": theme.colors.dark[7],
    "--card-bg": theme.colors.dark[6],
    "--text-primary": theme.colors.gray[0],
  },
});

function App() {
  return (
    <MantineProvider theme={theme} cssVariablesResolver={resolver}>
      {/* כעת ניתן להשתמש ב-var(--sidebar-width) בכל מקום */}
    </MantineProvider>
  );
}

סגנונות רספונסיביים עם Mantine

System Props רספונסיביים

import { SimpleGrid, Text, Paper, Box } from "@mantine/core";

function ResponsiveExample() {
  return (
    <>
      {/* רשת רספונסיבית */}
      <SimpleGrid
        cols={{ base: 1, sm: 2, lg: 3 }}
        spacing={{ base: "sm", sm: "md", lg: "lg" }}
      >
        <Paper p="md" shadow="xs">1</Paper>
        <Paper p="md" shadow="xs">2</Paper>
        <Paper p="md" shadow="xs">3</Paper>
      </SimpleGrid>

      {/* גלוי/נסתר */}
      <Box visibleFrom="md">
        <Text>נראה רק בדסקטופ</Text>
      </Box>
      <Box hiddenFrom="md">
        <Text>נראה רק בטלפון/טאבלט</Text>
      </Box>

      {/* Flex רספונסיבי */}
      <Flex
        direction={{ base: "column", sm: "row" }}
        gap={{ base: "sm", sm: "lg" }}
      >
        <Button>1</Button>
        <Button>2</Button>
      </Flex>
    </>
  );
}

שימוש ב-media queries עם CSS Modules של Mantine

/* Responsive.module.css */
.container {
  padding: var(--mantine-spacing-sm);
}

@media (min-width: 48em) {
  .container {
    padding: var(--mantine-spacing-lg);
  }
}

/* שימוש ב-mixin של postcss-preset-mantine */
.sidebar {
  display: none;

  @media (min-width: 48em) {
    display: block;
    width: 250px;
  }
}

קומפוננטות פולימורפיות - Polymorphic Components

קומפוננטות פולימורפיות יכולות לשנות את אלמנט ה-HTML שהן מרנדרות:

import { Button, Text, Paper, Group } from "@mantine/core";

function PolymorphicExamples() {
  return (
    <>
      {/* Button כ-anchor */}
      <Button component="a" href="https://example.com" target="_blank">
        קישור שנראה ככפתור
      </Button>

      {/* Text כ-label */}
      <Text component="label" htmlFor="my-input" fw={500}>
        תווית
      </Text>

      {/* Paper כ-article */}
      <Paper component="article" p="md" shadow="xs">
        מאמר
      </Paper>

      {/* Button עם React Router Link */}
      {/* <Button component={Link} to="/dashboard">ניווט</Button> */}

      {/* Text כ-span */}
      <Text span c="blue" fw={700}>
        טקסט מודגש בתוך שורה
      </Text>
    </>
  );
}
  • component prop משנה את אלמנט ה-HTML הבסיסי
  • כל ה-props של האלמנט החדש זמינים (href ל-a, to ל-Link)
  • TypeScript יבדוק שה-props תואמים לאלמנט

דוגמה עם TypeScript מלא

import { Button, ButtonProps } from "@mantine/core";
import { Link, LinkProps } from "react-router-dom";

// כפתור ניווט שמשלב Button עם Link
type NavButtonProps = ButtonProps & LinkProps;

function NavButton(props: NavButtonProps) {
  return <Button component={Link} {...props} />;
}

// שימוש
function Nav() {
  return (
    <Group>
      <NavButton to="/" variant="subtle">בית</NavButton>
      <NavButton to="/about" variant="subtle">אודות</NavButton>
    </Group>
  );
}

בניית קומפוננטה מותאמת עם Factory

Mantine מאפשרת ליצור קומפוננטות מותאמות שתומכות ב-Styles API, theme overrides, ו-ref forwarding.

דוגמה בסיסית

import {
  Box,
  BoxProps,
  StylesApiProps,
  factory,
  Factory,
  useProps,
  useStyles,
} from "@mantine/core";
import classes from "./StatCard.module.css";

// הגדרת הטיפוסים
export type StatCardStylesNames = "root" | "title" | "value" | "description";

export interface StatCardProps
  extends BoxProps,
    StylesApiProps<StatCardFactory> {
  title: string;
  value: string | number;
  description?: string;
}

type StatCardFactory = Factory<{
  props: StatCardProps;
  ref: HTMLDivElement;
  stylesNames: StatCardStylesNames;
}>;

// ברירות מחדל
const defaultProps: Partial<StatCardProps> = {};

// הקומפוננטה
const StatCard = factory<StatCardFactory>((_props, ref) => {
  const props = useProps("StatCard", defaultProps, _props);
  const { title, value, description, classNames, styles, className, style, ...others } = props;

  const getStyles = useStyles<StatCardFactory>({
    name: "StatCard",
    classes,
    props,
    classNames,
    styles,
    className,
    style,
  });

  return (
    <Box ref={ref} {...getStyles("root")} {...others}>
      <div {...getStyles("title")}>{title}</div>
      <div {...getStyles("value")}>{value}</div>
      {description && <div {...getStyles("description")}>{description}</div>}
    </Box>
  );
});

StatCard.displayName = "StatCard";
StatCard.classes = classes;

export { StatCard };
/* StatCard.module.css */
.root {
  padding: var(--mantine-spacing-lg);
  border-radius: var(--mantine-radius-md);
  background-color: var(--mantine-color-body);
  border: 1px solid var(--mantine-color-default-border);
}

.title {
  font-size: var(--mantine-font-size-sm);
  color: var(--mantine-color-dimmed);
  margin-bottom: var(--mantine-spacing-xs);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.value {
  font-size: 2rem;
  font-weight: 700;
  color: var(--mantine-color-text);
  line-height: 1;
}

.description {
  font-size: var(--mantine-font-size-sm);
  color: var(--mantine-color-dimmed);
  margin-top: var(--mantine-spacing-xs);
}

שימוש בקומפוננטה

import { StatCard } from "./StatCard";

function Dashboard() {
  return (
    <SimpleGrid cols={4}>
      <StatCard title="משתמשים" value="1,234" description="+12% מהחודש שעבר" />
      <StatCard title="הזמנות" value="567" description="+8%" />
      <StatCard title="הכנסות" value="45K" />
      <StatCard
        title="המרות"
        value="4.8%"
        styles={{
          value: { color: "var(--mantine-color-green-6)" },
        }}
      />
    </SimpleGrid>
  );
}
  • הקומפוננטה תומכת ב-classNames, styles, system props
  • ניתן לדרוס סגנונות ברמת ה-theme
  • ref forwarding עובד כמו בכל קומפוננטה של Mantine

דריסת סגנונות ב-theme

const theme = createTheme({
  components: {
    StatCard: {
      classNames: {
        root: "my-stat-card",
      },
      styles: {
        value: { color: "var(--mantine-primary-color-filled)" },
      },
    },
  },
});

דפוסים מתקדמים

ירושת Theme בקומפוננטות מקוננות

import { MantineProvider, createTheme, Button, Stack } from "@mantine/core";

const sectionTheme = createTheme({
  primaryColor: "red",
  defaultRadius: "xl",
});

function SpecialSection() {
  return (
    <MantineProvider theme={sectionTheme}>
      <Stack>
        <Button>כפתור אדום עם פינות מעוגלות</Button>
        <Button variant="outline">מתאר אדום</Button>
      </Stack>
    </MantineProvider>
  );
}

function App() {
  return (
    <MantineProvider>
      <Stack>
        <Button>כפתור כחול רגיל</Button>
        <SpecialSection />
        <Button>עדיין כחול</Button>
      </Stack>
    </MantineProvider>
  );
}
  • MantineProvider מקונן דורס את ה-theme רק לילדים שלו
  • שימושי לאזורים עם עיצוב שונה באותו דף

שימוש ב-data attributes

import { Button, ButtonProps } from "@mantine/core";

interface StatusButtonProps extends ButtonProps {
  status: "active" | "pending" | "error";
}

function StatusButton({ status, ...others }: StatusButtonProps) {
  return <Button data-status={status} {...others} />;
}
/* סגנונות מבוססי data attributes */
.mantine-Button-root[data-status="active"] {
  --button-bg: var(--mantine-color-green-6);
}

.mantine-Button-root[data-status="pending"] {
  --button-bg: var(--mantine-color-yellow-6);
}

.mantine-Button-root[data-status="error"] {
  --button-bg: var(--mantine-color-red-6);
}

סיכום

  • createTheme מאפשר התאמה אישית מלאה של צבעים, פונטים, גדלים ורדיוסים
  • ניתן להגדיר ברירות מחדל לכל קומפוננטה דרך components ב-theme
  • Styles API מאפשר גישה לכל חלק פנימי של קומפוננטה דרך classNames ו-styles
  • משתני CSS של Mantine זמינים בכל הפרויקט ומאפשרים עקביות
  • CSSVariablesResolver מאפשר הוספת משתנים מותאמים עם תמיכה במצב כהה
  • קומפוננטות פולימורפיות משנות את אלמנט ה-HTML עם component prop
  • factory מאפשר ליצור קומפוננטות מותאמות עם תמיכה מלאה ב-Styles API
  • MantineProvider מקונן מאפשר theme שונה לאזורים ספציפיים