לדלג לתוכן

8.4 מנטין קומפוננטות הרצאה

מנטין קומפוננטות - Mantine Components

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


קומפוננטות קלט - Inputs

TextInput - שדה טקסט

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

function TextInputExamples() {
  return (
    <>
      <TextInput label="שם מלא" placeholder="הכנס שם" />

      <TextInput
        label="אימייל"
        placeholder="your@email.com"
        description="לא נשתף את האימייל שלך"
        required
      />

      <TextInput
        label="שם משתמש"
        error="שם המשתמש כבר תפוס"
      />

      <TextInput
        label="חיפוש"
        leftSection="Q"
        rightSection="X"
      />

      <TextInput
        label="כתובת"
        disabled
        value="רח' הרצל 1"
      />

      {/* גדלים */}
      <TextInput size="xs" label="קטן מאוד" />
      <TextInput size="sm" label="קטן" />
      <TextInput size="md" label="רגיל" />
      <TextInput size="lg" label="גדול" />

      {/* variants */}
      <TextInput variant="default" label="ברירת מחדל" />
      <TextInput variant="filled" label="מלא" />
      <TextInput variant="unstyled" label="ללא עיצוב" />
    </>
  );
}

NumberInput - שדה מספרי

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

function NumberInputExamples() {
  return (
    <>
      <NumberInput label="כמות" placeholder="הכנס מספר" />

      <NumberInput
        label="מחיר"
        prefix="$"
        suffix=" USD"
        thousandSeparator=","
        decimalScale={2}
        min={0}
        max={10000}
      />

      <NumberInput
        label="גיל"
        min={0}
        max={120}
        step={1}
        clampBehavior="strict"
      />

      {/* עם כפתורי +/- */}
      <NumberInput
        label="כמות"
        defaultValue={1}
        min={1}
        max={99}
        stepHoldDelay={500}
        stepHoldInterval={100}
      />

      {/* ללא כפתורי +/- */}
      <NumberInput
        label="טלפון"
        hideControls
        placeholder="050-1234567"
      />
    </>
  );
}

Select ו-MultiSelect

import { Select, MultiSelect } from "@mantine/core";

function SelectExamples() {
  return (
    <>
      {/* Select בסיסי */}
      <Select
        label="עיר"
        placeholder="בחר עיר"
        data={["תל אביב", "ירושלים", "חיפה", "באר שבע"]}
      />

      {/* עם ערך ותווית */}
      <Select
        label="מחלקה"
        data={[
          { value: "dev", label: "פיתוח" },
          { value: "design", label: "עיצוב" },
          { value: "marketing", label: "שיווק" },
        ]}
      />

      {/* עם חיפוש */}
      <Select
        label="מדינה"
        placeholder="חפש מדינה..."
        searchable
        data={["ישראל", "ארצות הברית", "בריטניה", "גרמניה", "צרפת"]}
        nothingFoundMessage="לא נמצא"
      />

      {/* עם קבוצות */}
      <Select
        label="שפה"
        data={[
          { group: "פרונטאנד", items: ["JavaScript", "TypeScript", "HTML"] },
          { group: "באקאנד", items: ["Python", "Java", "Go"] },
        ]}
      />

      {/* ניתן לנקות */}
      <Select label="סטטוס" clearable data={["פעיל", "לא פעיל", "מושהה"]} />

      {/* בחירה מרובה */}
      <MultiSelect
        label="תגיות"
        placeholder="בחר תגיות"
        data={["ריאקט", "TypeScript", "Mantine", "Tailwind", "CSS"]}
        searchable
        clearable
      />
    </>
  );
}

קומפוננטות בחירה נוספות

import { Checkbox, Radio, Switch, Textarea } from "@mantine/core";

function MoreInputs() {
  return (
    <>
      {/* Checkbox */}
      <Checkbox label="אני מסכים לתנאי השימוש" />
      <Checkbox label="מסומן כברירת מחדל" defaultChecked />
      <Checkbox label="מושבת" disabled />
      <Checkbox
        label="עם תיאור"
        description="תיאור נוסף מתחת ל-Checkbox"
      />

      {/* קבוצת Checkbox */}
      <Checkbox.Group
        label="בחר שפות"
        description="סמן את השפות שאתה יודע"
        defaultValue={["react"]}
      >
        <Checkbox value="react" label="ריאקט" />
        <Checkbox value="vue" label="Vue" />
        <Checkbox value="angular" label="Angular" />
        <Checkbox value="svelte" label="Svelte" />
      </Checkbox.Group>

      {/* Radio */}
      <Radio.Group
        name="plan"
        label="בחר תוכנית"
        defaultValue="pro"
      >
        <Radio value="free" label="חינם" />
        <Radio value="pro" label="פרו - 49 ש\"ח לחודש" />
        <Radio value="enterprise" label="ארגוני - צור קשר" />
      </Radio.Group>

      {/* Switch */}
      <Switch label="מצב כהה" />
      <Switch label="התראות" defaultChecked />
      <Switch label="פעיל" size="lg" onLabel="כן" offLabel="לא" />

      {/* Textarea */}
      <Textarea
        label="הודעה"
        placeholder="כתוב את ההודעה שלך..."
        minRows={3}
        maxRows={6}
        autosize
      />
    </>
  );
}

הצגת נתונים - Data Display

Table - טבלה

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

const employees = [
  { name: "יוסי כהן", role: "מפתח", department: "פיתוח", status: "פעיל" },
  { name: "דנה לוי", role: "מעצבת", department: "עיצוב", status: "פעיל" },
  { name: "אבי מזרחי", role: "QA", department: "בדיקות", status: "חופשה" },
];

function TableExample() {
  return (
    <Table striped highlightOnHover withTableBorder withColumnBorders>
      <Table.Thead>
        <Table.Tr>
          <Table.Th>שם</Table.Th>
          <Table.Th>תפקיד</Table.Th>
          <Table.Th>מחלקה</Table.Th>
          <Table.Th>סטטוס</Table.Th>
        </Table.Tr>
      </Table.Thead>
      <Table.Tbody>
        {employees.map((emp) => (
          <Table.Tr key={emp.name}>
            <Table.Td>{emp.name}</Table.Td>
            <Table.Td>{emp.role}</Table.Td>
            <Table.Td>{emp.department}</Table.Td>
            <Table.Td>{emp.status}</Table.Td>
          </Table.Tr>
        ))}
      </Table.Tbody>
    </Table>
  );
}
  • striped - שורות מפוספסות
  • highlightOnHover - הדגשה בעכבר
  • withTableBorder - גבול חיצוני
  • withColumnBorders - גבולות בין עמודות

Badge - תגית

import { Badge, Group } from "@mantine/core";

function BadgeExamples() {
  return (
    <Group>
      <Badge>ברירת מחדל</Badge>
      <Badge color="red">דחוף</Badge>
      <Badge color="green" variant="light">פעיל</Badge>
      <Badge color="orange" variant="outline">ממתין</Badge>
      <Badge color="gray" variant="dot">טיוטה</Badge>
      <Badge size="lg" radius="sm">גדול</Badge>
      <Badge leftSection="!">חדש</Badge>
    </Group>
  );
}

Card - כרטיסייה

import { Card, Image, Text, Badge, Button, Group } from "@mantine/core";

function ProductCard() {
  return (
    <Card shadow="sm" padding="lg" radius="md" withBorder maw={340}>
      <Card.Section>
        <Image
          src="https://placehold.co/340x200"
          height={200}
          alt="מוצר"
        />
      </Card.Section>

      <Group justify="space-between" mt="md" mb="xs">
        <Text fw={500}>אוזניות אלחוטיות</Text>
        <Badge color="pink" variant="light">
          מבצע
        </Badge>
      </Group>

      <Text size="sm" c="dimmed">
        אוזניות בלוטות' איכותיות עם ביטול רעשים אקטיבי וסוללה של 30 שעות
      </Text>

      <Button variant="light" color="blue" fullWidth mt="md" radius="md">
        הוסף לעגלה
      </Button>
    </Card>
  );
}
  • Card.Section מתפרס על כל רוחב הכרטיסייה, כולל מעבר ל-padding

Avatar - תמונת פרופיל

import { Avatar, Group } from "@mantine/core";

function AvatarExamples() {
  return (
    <Group>
      {/* עם תמונה */}
      <Avatar src="https://placehold.co/40" alt="משתמש" />

      {/* עם ראשי תיבות */}
      <Avatar color="blue" radius="xl">
        יכ
      </Avatar>

      {/* גדלים */}
      <Avatar size="sm" color="red">S</Avatar>
      <Avatar size="md" color="green">M</Avatar>
      <Avatar size="lg" color="orange">L</Avatar>
      <Avatar size="xl" color="grape">XL</Avatar>

      {/* קבוצת אווטארים */}
      <Avatar.Group>
        <Avatar color="blue">א</Avatar>
        <Avatar color="red">ב</Avatar>
        <Avatar color="green">ג</Avatar>
        <Avatar>+5</Avatar>
      </Avatar.Group>
    </Group>
  );
}

שכבות-על - Overlays

import { Modal, Button, TextInput, Group, Stack } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";

function ModalExample() {
  const [opened, { open, close }] = useDisclosure(false);

  return (
    <>
      <Modal opened={opened} onClose={close} title="הוספת משתמש" centered>
        <Stack>
          <TextInput label="שם" placeholder="הכנס שם" />
          <TextInput label="אימייל" placeholder="הכנס אימייל" />
          <Group justify="flex-end" mt="md">
            <Button variant="default" onClick={close}>
              ביטול
            </Button>
            <Button onClick={close}>שמור</Button>
          </Group>
        </Stack>
      </Modal>

      <Button onClick={open}>פתח מודאל</Button>
    </>
  );
}
  • useDisclosure מנהל את מצב פתוח/סגור
  • centered ממרכז את המודאל במסך
  • onClose נקרא גם בלחיצה על הרקע וגם על X

Drawer - מגירה

import { Drawer, Button, NavLink, Stack } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";

function DrawerExample() {
  const [opened, { open, close }] = useDisclosure(false);

  return (
    <>
      <Drawer
        opened={opened}
        onClose={close}
        title="תפריט ניווט"
        position="right"
        size="sm"
      >
        <Stack>
          <NavLink label="בית" onClick={close} />
          <NavLink label="מוצרים" onClick={close} />
          <NavLink label="אודות" onClick={close} />
          <NavLink label="צור קשר" onClick={close} />
        </Stack>
      </Drawer>

      <Button onClick={open}>פתח תפריט</Button>
    </>
  );
}
  • position - right, left, top, bottom
  • size - xs, sm, md, lg, xl או ערך מספרי

Tooltip - הסבר

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

function TooltipExamples() {
  return (
    <Group>
      <Tooltip label="שמור את השינויים">
        <Button>שמור</Button>
      </Tooltip>

      <Tooltip label="אפשרות זו מושבתת" position="bottom">
        <Button disabled>מושבת</Button>
      </Tooltip>

      <Tooltip
        label="מידע נוסף על הפעולה"
        multiline
        w={200}
        withArrow
      >
        <Button variant="light">מידע</Button>
      </Tooltip>

      {/* Tooltip עם עיכוב */}
      <Tooltip label="מופיע אחרי 500ms" openDelay={500}>
        <Button variant="outline">עם עיכוב</Button>
      </Tooltip>
    </Group>
  );
}

Popover - חלון צף

import { Popover, Button, Text, Stack, TextInput } from "@mantine/core";

function PopoverExample() {
  return (
    <Popover width={300} position="bottom" withArrow shadow="md">
      <Popover.Target>
        <Button>פתח חלון צף</Button>
      </Popover.Target>
      <Popover.Dropdown>
        <Stack gap="sm">
          <Text size="sm" fw={500}>
            סינון תוצאות
          </Text>
          <TextInput placeholder="חפש..." size="xs" />
          <Button size="xs" fullWidth>
            החל
          </Button>
        </Stack>
      </Popover.Dropdown>
    </Popover>
  );
}
import { Menu, Button } from "@mantine/core";

function MenuExample() {
  return (
    <Menu shadow="md" width={200}>
      <Menu.Target>
        <Button>תפריט</Button>
      </Menu.Target>

      <Menu.Dropdown>
        <Menu.Label>פעולות</Menu.Label>
        <Menu.Item>עריכה</Menu.Item>
        <Menu.Item>שכפול</Menu.Item>
        <Menu.Item>העברה</Menu.Item>

        <Menu.Divider />

        <Menu.Label>מתקדם</Menu.Label>
        <Menu.Item>ייצוא</Menu.Item>
        <Menu.Item color="red">מחיקה</Menu.Item>
      </Menu.Dropdown>
    </Menu>
  );
}

ניווט - Navigation

Tabs - כרטיסיות ניווט

import { Tabs, Text } from "@mantine/core";

function TabsExample() {
  return (
    <Tabs defaultValue="general">
      <Tabs.List>
        <Tabs.Tab value="general">כללי</Tabs.Tab>
        <Tabs.Tab value="security">אבטחה</Tabs.Tab>
        <Tabs.Tab value="notifications">התראות</Tabs.Tab>
      </Tabs.List>

      <Tabs.Panel value="general" pt="md">
        <Text>הגדרות כלליות</Text>
      </Tabs.Panel>

      <Tabs.Panel value="security" pt="md">
        <Text>הגדרות אבטחה</Text>
      </Tabs.Panel>

      <Tabs.Panel value="notifications" pt="md">
        <Text>הגדרות התראות</Text>
      </Tabs.Panel>
    </Tabs>
  );
}
  • defaultValue מגדיר את הכרטיסייה הפעילה בהתחלה
  • ניתן להוסיף variant="outline" או variant="pills"
import { Breadcrumbs, Anchor } from "@mantine/core";

function BreadcrumbsExample() {
  const items = [
    { title: "בית", href: "/" },
    { title: "מוצרים", href: "/products" },
    { title: "אלקטרוניקה", href: "/products/electronics" },
    { title: "אוזניות", href: "#" },
  ];

  return (
    <Breadcrumbs>
      {items.map((item) => (
        <Anchor href={item.href} key={item.title}>
          {item.title}
        </Anchor>
      ))}
    </Breadcrumbs>
  );
}

Pagination - עימוד

import { Pagination } from "@mantine/core";
import { useState } from "react";

function PaginationExample() {
  const [activePage, setPage] = useState(1);

  return (
    <Pagination
      value={activePage}
      onChange={setPage}
      total={10}
      siblings={1}
      boundaries={1}
    />
  );
}
  • total - מספר העמודים
  • siblings - כמה עמודים להציג מכל צד של הנוכחי
  • boundaries - כמה עמודים להציג בקצוות
import { NavLink, Stack } from "@mantine/core";
import { useState } from "react";

function NavLinkExample() {
  const [active, setActive] = useState(0);

  const items = [
    { label: "דשבורד", description: "סקירה כללית" },
    { label: "משתמשים", description: "ניהול משתמשים" },
    { label: "הגדרות", description: "הגדרות מערכת" },
  ];

  return (
    <Stack gap={0} w={250}>
      {items.map((item, index) => (
        <NavLink
          key={item.label}
          active={index === active}
          label={item.label}
          description={item.description}
          onClick={() => setActive(index)}
        />
      ))}
    </Stack>
  );
}

משוב - Feedback

Notification - התראה

import { Notification, Stack } from "@mantine/core";

function NotificationExamples() {
  return (
    <Stack maw={400}>
      <Notification title="פעולה בוצעה" color="green" withCloseButton>
        ההזמנה נשמרה בהצלחה
      </Notification>

      <Notification title="שגיאה" color="red">
        לא ניתן לשמור את הנתונים
      </Notification>

      <Notification title="אזהרה" color="yellow">
        החשבון שלך עומד לפוג
      </Notification>

      <Notification title="טוען..." loading>
        מעבד את הבקשה שלך
      </Notification>
    </Stack>
  );
}

Alert - התראה בולטת

import { Alert, Stack } from "@mantine/core";

function AlertExamples() {
  return (
    <Stack>
      <Alert variant="light" color="blue" title="מידע">
        שחרור גרסה חדשה מתוכנן לשבוע הבא
      </Alert>

      <Alert variant="light" color="green" title="הצלחה">
        הפרופיל עודכן בהצלחה
      </Alert>

      <Alert variant="light" color="red" title="שגיאה">
        אירעה שגיאה בעת שמירת הנתונים
      </Alert>

      <Alert variant="filled" color="yellow" title="אזהרה">
        שים לב - פעולה זו היא בלתי הפיכה
      </Alert>
    </Stack>
  );
}

Progress - סרגל התקדמות

import { Progress, Stack, Text } from "@mantine/core";

function ProgressExamples() {
  return (
    <Stack>
      <Progress value={45} />

      <Progress value={75} color="green" size="lg" radius="xl" />

      {/* עם תווית */}
      <div>
        <Text size="sm" mb={4}>העלאת קובץ - 67%</Text>
        <Progress value={67} animated />
      </div>

      {/* מרובה חלקים */}
      <Progress.Root size="xl">
        <Progress.Section value={35} color="cyan">
          <Progress.Label>קבצים</Progress.Label>
        </Progress.Section>
        <Progress.Section value={25} color="pink">
          <Progress.Label>תמונות</Progress.Label>
        </Progress.Section>
        <Progress.Section value={15} color="orange">
          <Progress.Label>אחר</Progress.Label>
        </Progress.Section>
      </Progress.Root>
    </Stack>
  );
}

Skeleton - שלד טעינה

import { Skeleton, Stack, Group } from "@mantine/core";

function SkeletonExample() {
  return (
    <Stack>
      {/* שורות טקסט */}
      <Skeleton height={8} radius="xl" />
      <Skeleton height={8} radius="xl" width="70%" />
      <Skeleton height={8} radius="xl" width="50%" />

      {/* כרטיס טעינה */}
      <Group>
        <Skeleton height={50} circle />
        <div style={{ flex: 1 }}>
          <Skeleton height={8} radius="xl" mb={8} />
          <Skeleton height={8} radius="xl" width="60%" />
        </div>
      </Group>

      {/* תמונה */}
      <Skeleton height={200} radius="md" />
    </Stack>
  );
}

LoadingOverlay - שכבת טעינה

import { LoadingOverlay, Box, Text, Button } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";

function LoadingOverlayExample() {
  const [visible, { toggle }] = useDisclosure(false);

  return (
    <>
      <Button onClick={toggle} mb="md">
        {visible ? "הסתר טעינה" : "הצג טעינה"}
      </Button>

      <Box pos="relative" mih={200}>
        <LoadingOverlay
          visible={visible}
          zIndex={1000}
          overlayProps={{ radius: "sm", blur: 2 }}
        />
        <Text>תוכן שמוסתר בזמן טעינה</Text>
        <Text>עוד תוכן...</Text>
      </Box>
    </>
  );
}
  • pos="relative" על ההורה חשוב - ה-LoadingOverlay ממוקם absolute
  • blur מטשטש את התוכן שמאחור

דוגמה מלאה - דף ניהול משתמשים

import {
  Container,
  Title,
  Table,
  Badge,
  Group,
  Button,
  Modal,
  TextInput,
  Select,
  Stack,
  Avatar,
  Menu,
  Tabs,
  Pagination,
  Text,
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { useState } from "react";

interface User {
  id: number;
  name: string;
  email: string;
  role: string;
  status: "active" | "inactive" | "suspended";
}

const usersData: User[] = [
  { id: 1, name: "יוסי כהן", email: "yossi@example.com", role: "מנהל", status: "active" },
  { id: 2, name: "דנה לוי", email: "dana@example.com", role: "מפתח", status: "active" },
  { id: 3, name: "אבי מזרחי", email: "avi@example.com", role: "מעצב", status: "inactive" },
  { id: 4, name: "שירה אברהם", email: "shira@example.com", role: "QA", status: "suspended" },
];

const statusColors: Record<string, string> = {
  active: "green",
  inactive: "gray",
  suspended: "red",
};

const statusLabels: Record<string, string> = {
  active: "פעיל",
  inactive: "לא פעיל",
  suspended: "מושהה",
};

function UsersPage() {
  const [opened, { open, close }] = useDisclosure(false);
  const [activePage, setPage] = useState(1);

  return (
    <Container size="lg" py="xl">
      <Group justify="space-between" mb="lg">
        <Title order={2}>ניהול משתמשים</Title>
        <Button onClick={open}>הוסף משתמש</Button>
      </Group>

      <Tabs defaultValue="all" mb="lg">
        <Tabs.List>
          <Tabs.Tab value="all">הכל (4)</Tabs.Tab>
          <Tabs.Tab value="active">פעילים (2)</Tabs.Tab>
          <Tabs.Tab value="inactive">לא פעילים (1)</Tabs.Tab>
          <Tabs.Tab value="suspended">מושהים (1)</Tabs.Tab>
        </Tabs.List>
      </Tabs>

      <Table striped highlightOnHover withTableBorder>
        <Table.Thead>
          <Table.Tr>
            <Table.Th>משתמש</Table.Th>
            <Table.Th>אימייל</Table.Th>
            <Table.Th>תפקיד</Table.Th>
            <Table.Th>סטטוס</Table.Th>
            <Table.Th>פעולות</Table.Th>
          </Table.Tr>
        </Table.Thead>
        <Table.Tbody>
          {usersData.map((user) => (
            <Table.Tr key={user.id}>
              <Table.Td>
                <Group gap="sm">
                  <Avatar size="sm" color="blue" radius="xl">
                    {user.name.charAt(0)}
                  </Avatar>
                  <Text size="sm" fw={500}>
                    {user.name}
                  </Text>
                </Group>
              </Table.Td>
              <Table.Td>
                <Text size="sm">{user.email}</Text>
              </Table.Td>
              <Table.Td>
                <Text size="sm">{user.role}</Text>
              </Table.Td>
              <Table.Td>
                <Badge color={statusColors[user.status]} variant="light">
                  {statusLabels[user.status]}
                </Badge>
              </Table.Td>
              <Table.Td>
                <Menu shadow="md" width={150}>
                  <Menu.Target>
                    <Button variant="subtle" size="xs">
                      ...
                    </Button>
                  </Menu.Target>
                  <Menu.Dropdown>
                    <Menu.Item>עריכה</Menu.Item>
                    <Menu.Item>צפייה</Menu.Item>
                    <Menu.Divider />
                    <Menu.Item color="red">מחיקה</Menu.Item>
                  </Menu.Dropdown>
                </Menu>
              </Table.Td>
            </Table.Tr>
          ))}
        </Table.Tbody>
      </Table>

      <Group justify="center" mt="lg">
        <Pagination value={activePage} onChange={setPage} total={5} />
      </Group>

      {/* מודאל הוספת משתמש */}
      <Modal opened={opened} onClose={close} title="הוספת משתמש חדש" centered>
        <Stack>
          <TextInput label="שם מלא" placeholder="הכנס שם" required />
          <TextInput label="אימייל" placeholder="הכנס אימייל" required />
          <Select
            label="תפקיד"
            placeholder="בחר תפקיד"
            data={["מנהל", "מפתח", "מעצב", "QA"]}
            required
          />
          <Group justify="flex-end" mt="md">
            <Button variant="default" onClick={close}>
              ביטול
            </Button>
            <Button onClick={close}>הוסף</Button>
          </Group>
        </Stack>
      </Modal>
    </Container>
  );
}

סיכום

  • Mantine מציעה מגוון רחב של קומפוננטות קלט: TextInput, NumberInput, Select, Checkbox, Radio, Switch, Textarea
  • קומפוננטות הצגת נתונים כוללות Table, Badge, Card, Avatar, Image
  • שכבות-על: Modal, Drawer, Tooltip, Popover, Menu - כולן עובדות עם useDisclosure
  • קומפוננטות ניווט: Tabs, Breadcrumbs, Pagination, NavLink
  • קומפוננטות משוב: Notification, Alert, Progress, Skeleton, LoadingOverlay
  • כל הקומפוננטות תומכות ב-variants, colors, sizes ו-system props
  • שילוב הקומפוננטות יחד יוצר ממשקים מקצועיים ועקביים