לדלג לתוכן

8.8 ערכות נושא ומצב כהה פתרון

פתרון - ערכות נושא ומצב כהה

פתרון תרגיל 1

import {
  useMantineColorScheme,
  useComputedColorScheme,
  ActionIcon,
  Tooltip,
  SegmentedControl,
  Group,
  Stack,
  Paper,
  Title,
  Text,
} from "@mantine/core";

function ThemeToggle() {
  const { colorScheme, setColorScheme } = useMantineColorScheme();
  const computedColorScheme = useComputedColorScheme("light");

  return (
    <Paper p="xl" withBorder maw={400} mx="auto" mt="xl">
      <Stack gap="lg">
        <Title order={3}>העדפות תצוגה</Title>

        {/* כפתור החלפה מהיר */}
        <Group justify="space-between">
          <Text>מצב תצוגה</Text>
          <Tooltip
            label={
              computedColorScheme === "dark"
                ? "עבור למצב בהיר"
                : "עבור למצב כהה"
            }
          >
            <ActionIcon
              variant="default"
              size="lg"
              radius="xl"
              onClick={() =>
                setColorScheme(
                  computedColorScheme === "dark" ? "light" : "dark"
                )
              }
              aria-label="החלף מצב תצוגה"
            >
              {computedColorScheme === "dark" ? "S" : "M"}
            </ActionIcon>
          </Tooltip>
        </Group>

        {/* בחירה מלאה */}
        <div>
          <Text size="sm" fw={500} mb="xs">
            בחירת מצב
          </Text>
          <SegmentedControl
            fullWidth
            value={colorScheme}
            onChange={(value) => setColorScheme(value as "light" | "dark" | "auto")}
            data={[
              { value: "light", label: "בהיר" },
              { value: "dark", label: "כהה" },
              { value: "auto", label: "אוטומטי" },
            ]}
          />
        </div>

        <Text size="xs" c="dimmed">
          מצב נוכחי: {computedColorScheme === "dark" ? "כהה" : "בהיר"}
          {colorScheme === "auto" && " (אוטומטי)"}
        </Text>
      </Stack>
    </Paper>
  );
}
  • SegmentedControl מציג 3 אפשרויות בצורה ברורה
  • ActionIcon מספק החלפה מהירה בלחיצה
  • מצב "אוטומטי" עוקב אחרי הגדרות המערכת

פתרון תרגיל 2

import {
  createTheme,
  MantineProvider,
  CSSVariablesResolver,
  AppShell,
  Burger,
  Group,
  NavLink,
  Title,
  Text,
  Paper,
  SimpleGrid,
  Stack,
  Button,
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";

const theme = createTheme({
  primaryColor: "blue",
});

const resolver: CSSVariablesResolver = (theme) => ({
  variables: {
    "--header-height": "60px",
    "--sidebar-width": "250px",
    "--transition-speed": "0.3s",
  },
  light: {
    "--app-bg": "#f1f3f5",
    "--card-bg": "#ffffff",
    "--card-hover-bg": "#f8f9fa",
    "--text-primary": "#212529",
    "--text-secondary": "#868e96",
    "--border-color": "#dee2e6",
    "--accent": theme.colors.blue[6],
    "--accent-light": theme.colors.blue[0],
    "--sidebar-bg": "#ffffff",
    "--header-bg": "#ffffff",
  },
  dark: {
    "--app-bg": "#141517",
    "--card-bg": "#1f2023",
    "--card-hover-bg": "#2c2e33",
    "--text-primary": "#c1c2c5",
    "--text-secondary": "#909296",
    "--border-color": "#373a40",
    "--accent": theme.colors.blue[4],
    "--accent-light": "#1c3a5c",
    "--sidebar-bg": "#1a1b1e",
    "--header-bg": "#1a1b1e",
  },
});

function ThemedLayout() {
  const [opened, { toggle }] = useDisclosure();

  return (
    <MantineProvider theme={theme} defaultColorScheme="auto" cssVariablesResolver={resolver}>
      <AppShell
        header={{ height: 60 }}
        navbar={{ width: 250, breakpoint: "sm", collapsed: { mobile: !opened } }}
        padding="md"
        styles={{
          main: {
            backgroundColor: "var(--app-bg)",
            transition: "background-color var(--transition-speed)",
          },
          header: {
            backgroundColor: "var(--header-bg)",
            borderColor: "var(--border-color)",
            transition: "background-color var(--transition-speed), border-color var(--transition-speed)",
          },
          navbar: {
            backgroundColor: "var(--sidebar-bg)",
            borderColor: "var(--border-color)",
            transition: "background-color var(--transition-speed), border-color var(--transition-speed)",
          },
        }}
      >
        <AppShell.Header>
          <Group h="100%" px="md">
            <Burger opened={opened} onClick={toggle} hiddenFrom="sm" size="sm" />
            <Title order={3} style={{ color: "var(--text-primary)", transition: "color var(--transition-speed)" }}>
              אפליקציה
            </Title>
          </Group>
        </AppShell.Header>

        <AppShell.Navbar p="md">
          <Stack gap="xs">
            <NavLink label="דשבורד" active />
            <NavLink label="משתמשים" />
            <NavLink label="הגדרות" />
          </Stack>
        </AppShell.Navbar>

        <AppShell.Main>
          <SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }}>
            {[1, 2, 3, 4, 5, 6].map((n) => (
              <Paper
                key={n}
                p="lg"
                withBorder
                style={{
                  backgroundColor: "var(--card-bg)",
                  borderColor: "var(--border-color)",
                  transition: "all var(--transition-speed)",
                  cursor: "pointer",
                }}
                onMouseEnter={(e) => {
                  e.currentTarget.style.backgroundColor = "var(--card-hover-bg)";
                }}
                onMouseLeave={(e) => {
                  e.currentTarget.style.backgroundColor = "var(--card-bg)";
                }}
              >
                <Text style={{ color: "var(--text-primary)" }} fw={500}>
                  כרטיס {n}
                </Text>
                <Text style={{ color: "var(--text-secondary)" }} size="sm" mt="xs">
                  תיאור הכרטיס
                </Text>
              </Paper>
            ))}
          </SimpleGrid>
        </AppShell.Main>
      </AppShell>
    </MantineProvider>
  );
}
  • transition על כל האלמנטים מבטיח מעבר חלק בין מצבים
  • משתני CSS מותאמים לכל חלק בלייאוט
  • hover effect עם JavaScript כי CSS vars בתוך CSS Modules פשוט יותר

פתרון תרגיל 3

import { useState } from "react";
import { useLocalStorage } from "@mantine/hooks";
import {
  createTheme,
  MantineProvider,
  CSSVariablesResolver,
  Container,
  Title,
  Text,
  Button,
  TextInput,
  Paper,
  SimpleGrid,
  Stack,
  Group,
  SegmentedControl,
  Card,
  Badge,
} from "@mantine/core";

type ThemeName = "default" | "ocean" | "forest" | "sunset";

const themes: Record<
  ThemeName,
  {
    primary: string;
    label: string;
    light: Record<string, string>;
    dark: Record<string, string>;
  }
> = {
  default: {
    primary: "blue",
    label: "ברירת מחדל",
    light: { "--accent-bg": "#e7f5ff", "--accent-text": "#1971c2" },
    dark: { "--accent-bg": "#1c3a5c", "--accent-text": "#74c0fc" },
  },
  ocean: {
    primary: "cyan",
    label: "אוקיינוס",
    light: { "--accent-bg": "#e3fafc", "--accent-text": "#0c8599" },
    dark: { "--accent-bg": "#1a3a3e", "--accent-text": "#66d9e8" },
  },
  forest: {
    primary: "green",
    label: "יער",
    light: { "--accent-bg": "#ebfbee", "--accent-text": "#2f9e44" },
    dark: { "--accent-bg": "#1a3b1e", "--accent-text": "#69db7c" },
  },
  sunset: {
    primary: "orange",
    label: "שקיעה",
    light: { "--accent-bg": "#fff4e6", "--accent-text": "#e8590c" },
    dark: { "--accent-bg": "#3b2e1a", "--accent-text": "#ffa94d" },
  },
};

function ThemeSystemDemo() {
  const [themeName, setThemeName] = useLocalStorage<ThemeName>({
    key: "app-theme",
    defaultValue: "default",
  });

  const currentTheme = themes[themeName];

  const theme = createTheme({
    primaryColor: currentTheme.primary,
  });

  const resolver: CSSVariablesResolver = () => ({
    variables: {},
    light: currentTheme.light,
    dark: currentTheme.dark,
  });

  return (
    <MantineProvider theme={theme} defaultColorScheme="auto" cssVariablesResolver={resolver}>
      <Container size="md" py="xl">
        <Stack gap="xl">
          <Title order={2}>בחירת ערכת נושא</Title>

          <SegmentedControl
            value={themeName}
            onChange={(val) => setThemeName(val as ThemeName)}
            data={Object.entries(themes).map(([key, value]) => ({
              value: key,
              label: value.label,
            }))}
            fullWidth
          />

          {/* דוגמאות */}
          <Paper p="lg" withBorder>
            <Title order={3} mb="md">תצוגה מקדימה</Title>

            <Stack gap="md">
              <Group>
                <Button>כפתור ראשי</Button>
                <Button variant="light">בהיר</Button>
                <Button variant="outline">מתאר</Button>
                <Button variant="subtle">עדין</Button>
              </Group>

              <TextInput label="שדה טקסט" placeholder="הקלד כאן..." />

              <SimpleGrid cols={2}>
                <Card p="md" withBorder>
                  <Group justify="space-between" mb="xs">
                    <Text fw={500}>כרטיס 1</Text>
                    <Badge>חדש</Badge>
                  </Group>
                  <Text size="sm" c="dimmed">תיאור הכרטיס</Text>
                </Card>
                <Card p="md" withBorder>
                  <Group justify="space-between" mb="xs">
                    <Text fw={500}>כרטיס 2</Text>
                    <Badge variant="outline">פעיל</Badge>
                  </Group>
                  <Text size="sm" c="dimmed">תיאור הכרטיס</Text>
                </Card>
              </SimpleGrid>

              <Paper p="md" style={{ backgroundColor: "var(--accent-bg)" }}>
                <Text style={{ color: "var(--accent-text)" }} fw={500}>
                  אזור מודגש עם צבעי ערכת הנושא
                </Text>
              </Paper>
            </Stack>
          </Paper>
        </Stack>
      </Container>
    </MantineProvider>
  );
}
  • useLocalStorage שומר את ערכת הנושא
  • SegmentedControl מאפשר בחירה ברורה
  • כל הקומפוננטות מגיבות אוטומטית לשינוי primaryColor

פתרון תרגיל 4

import { useLocalStorage } from "@mantine/hooks";
import {
  createTheme,
  MantineProvider,
  Container,
  Title,
  Text,
  Paper,
  SimpleGrid,
  Stack,
  Group,
  SegmentedControl,
  Button,
  Card,
  Badge,
  TextInput,
  Select,
  Divider,
} from "@mantine/core";

type ColorMode = "light" | "dark" | "auto";
type ThemeName = "default" | "ocean" | "forest" | "sunset";
type FontSize = "small" | "default" | "large";
type Density = "compact" | "default" | "comfortable";

const fontSizeMap: Record<FontSize, Record<string, string>> = {
  small: { xs: "0.65rem", sm: "0.775rem", md: "0.875rem", lg: "1rem", xl: "1.125rem" },
  default: { xs: "0.75rem", sm: "0.875rem", md: "1rem", lg: "1.125rem", xl: "1.25rem" },
  large: { xs: "0.875rem", sm: "1rem", md: "1.125rem", lg: "1.25rem", xl: "1.5rem" },
};

const spacingMap: Record<Density, Record<string, string>> = {
  compact: { xs: "0.25rem", sm: "0.5rem", md: "0.75rem", lg: "1rem", xl: "1.5rem" },
  default: { xs: "0.5rem", sm: "0.75rem", md: "1rem", lg: "1.5rem", xl: "2rem" },
  comfortable: { xs: "0.75rem", sm: "1rem", md: "1.5rem", lg: "2rem", xl: "3rem" },
};

const primaryColors: Record<ThemeName, string> = {
  default: "blue",
  ocean: "cyan",
  forest: "green",
  sunset: "orange",
};

function DisplaySettings() {
  const [colorMode, setColorMode] = useLocalStorage<ColorMode>({
    key: "display-color-mode",
    defaultValue: "auto",
  });
  const [themeName, setThemeName] = useLocalStorage<ThemeName>({
    key: "display-theme",
    defaultValue: "default",
  });
  const [fontSize, setFontSize] = useLocalStorage<FontSize>({
    key: "display-font-size",
    defaultValue: "default",
  });
  const [density, setDensity] = useLocalStorage<Density>({
    key: "display-density",
    defaultValue: "default",
  });

  function resetAll() {
    setColorMode("auto");
    setThemeName("default");
    setFontSize("default");
    setDensity("default");
  }

  const theme = createTheme({
    primaryColor: primaryColors[themeName],
    fontSizes: fontSizeMap[fontSize],
    spacing: spacingMap[density],
  });

  return (
    <MantineProvider theme={theme} forceColorScheme={colorMode === "auto" ? undefined : colorMode}>
      <Container size="lg" py="xl">
        <Title order={2} mb="xl">הגדרות תצוגה</Title>

        <SimpleGrid cols={{ base: 1, md: 2 }} spacing="xl">
          {/* הגדרות */}
          <Paper p="lg" withBorder>
            <Stack gap="lg">
              <div>
                <Text fw={500} mb="xs">ערכת צבעים</Text>
                <SegmentedControl
                  fullWidth
                  value={colorMode}
                  onChange={(v) => setColorMode(v as ColorMode)}
                  data={[
                    { value: "light", label: "בהיר" },
                    { value: "dark", label: "כהה" },
                    { value: "auto", label: "אוטומטי" },
                  ]}
                />
              </div>

              <div>
                <Text fw={500} mb="xs">ערכת נושא</Text>
                <SegmentedControl
                  fullWidth
                  value={themeName}
                  onChange={(v) => setThemeName(v as ThemeName)}
                  data={[
                    { value: "default", label: "ברירת מחדל" },
                    { value: "ocean", label: "אוקיינוס" },
                    { value: "forest", label: "יער" },
                    { value: "sunset", label: "שקיעה" },
                  ]}
                />
              </div>

              <div>
                <Text fw={500} mb="xs">גודל פונט</Text>
                <SegmentedControl
                  fullWidth
                  value={fontSize}
                  onChange={(v) => setFontSize(v as FontSize)}
                  data={[
                    { value: "small", label: "קטן" },
                    { value: "default", label: "רגיל" },
                    { value: "large", label: "גדול" },
                  ]}
                />
              </div>

              <div>
                <Text fw={500} mb="xs">צפיפות</Text>
                <SegmentedControl
                  fullWidth
                  value={density}
                  onChange={(v) => setDensity(v as Density)}
                  data={[
                    { value: "compact", label: "צפוף" },
                    { value: "default", label: "רגיל" },
                    { value: "comfortable", label: "מרווח" },
                  ]}
                />
              </div>

              <Button variant="light" color="red" onClick={resetAll}>
                איפוס להגדרות ברירת מחדל
              </Button>
            </Stack>
          </Paper>

          {/* תצוגה מקדימה */}
          <Paper p="lg" withBorder>
            <Title order={3} mb="md">תצוגה מקדימה</Title>

            <Stack gap="md">
              <Card p="md" withBorder>
                <Group justify="space-between" mb="xs">
                  <Text fw={500}>כרטיס לדוגמה</Text>
                  <Badge>פעיל</Badge>
                </Group>
                <Text size="sm" c="dimmed">
                  טקסט תיאורי שמדגים את גודל הפונט והצפיפות הנבחרים
                </Text>
              </Card>

              <TextInput label="שדה טקסט" placeholder="דוגמה..." />

              <Select
                label="בחירה"
                placeholder="בחר..."
                data={["אפשרות 1", "אפשרות 2", "אפשרות 3"]}
              />

              <Group>
                <Button>ראשי</Button>
                <Button variant="light">בהיר</Button>
                <Button variant="outline">מתאר</Button>
              </Group>

              <Text size="xs" c="dimmed">
                ערכת נושא: {themeName} | פונט: {fontSize} | צפיפות: {density}
              </Text>
            </Stack>
          </Paper>
        </SimpleGrid>
      </Container>
    </MantineProvider>
  );
}
  • כל ההגדרות ב-useLocalStorage ונשארות אחרי רענון
  • התצוגה המקדימה מגיבה בזמן אמת
  • forceColorScheme דורס את ההגדרה כשלא auto

פתרון תרגיל 5

import {
  AppShell,
  Burger,
  Group,
  NavLink,
  Title,
  Text,
  Button,
  SimpleGrid,
  Paper,
  Table,
  Badge,
  Avatar,
  Stack,
  Divider,
  ActionIcon,
  Tooltip,
  useMantineColorScheme,
  useComputedColorScheme,
  Container,
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";

function FullThemedLayout() {
  const [opened, { toggle }] = useDisclosure();
  const { setColorScheme } = useMantineColorScheme();
  const computed = useComputedColorScheme("light");

  const stats = [
    { label: "הכנסות", value: "52,340", change: "+18.2%" },
    { label: "משתמשים", value: "3,456", change: "+5.1%" },
    { label: "הזמנות", value: "892", change: "+12.7%" },
    { label: "המרות", value: "4.3%", change: "-0.8%" },
  ];

  const users = [
    { name: "יוסי כהן", email: "yossi@ex.com", role: "מנהל", status: "פעיל" },
    { name: "דנה לוי", email: "dana@ex.com", role: "מפתח", status: "פעיל" },
    { name: "אבי מזרחי", email: "avi@ex.com", role: "מעצב", status: "חופשה" },
  ];

  return (
    <AppShell
      header={{ height: 60 }}
      navbar={{ width: 250, breakpoint: "sm", collapsed: { mobile: !opened } }}
      footer={{ height: 50 }}
      padding="md"
    >
      {/* Header */}
      <AppShell.Header>
        <Group h="100%" px="md" justify="space-between">
          <Group>
            <Burger opened={opened} onClick={toggle} hiddenFrom="sm" size="sm" />
            <Title order={3}>CRM</Title>
          </Group>
          <Group>
            <Tooltip label={computed === "dark" ? "מצב בהיר" : "מצב כהה"}>
              <ActionIcon
                variant="default"
                size="lg"
                onClick={() =>
                  setColorScheme(computed === "dark" ? "light" : "dark")
                }
              >
                {computed === "dark" ? "S" : "M"}
              </ActionIcon>
            </Tooltip>
            <Avatar color="blue" radius="xl" size="sm">U</Avatar>
          </Group>
        </Group>
      </AppShell.Header>

      {/* Sidebar */}
      <AppShell.Navbar p="md">
        <Stack justify="space-between" h="100%">
          <Stack gap="xs">
            <NavLink label="דשבורד" active />
            <NavLink label="לקוחות" />
            <NavLink label="הזמנות" />
            <NavLink label="מוצרים" />
            <NavLink label="דוחות" />
            <Divider />
            <NavLink label="הגדרות" />
          </Stack>
          <Text size="xs" c="dimmed">גרסה 2.1.0</Text>
        </Stack>
      </AppShell.Navbar>

      {/* תוכן */}
      <AppShell.Main>
        <Container fluid>
          <Title order={2} mb="lg">דשבורד</Title>

          <SimpleGrid cols={{ base: 1, sm: 2, lg: 4 }} mb="xl">
            {stats.map((stat) => (
              <Paper key={stat.label} shadow="xs" p="md" withBorder>
                <Text size="sm" c="dimmed" tt="uppercase" fw={500}>
                  {stat.label}
                </Text>
                <Text size="xl" fw={700} mt="xs">{stat.value}</Text>
                <Text
                  size="sm"
                  c={stat.change.startsWith("+") ? "green" : "red"}
                  mt="xs"
                >
                  {stat.change}
                </Text>
              </Paper>
            ))}
          </SimpleGrid>

          <Paper shadow="xs" withBorder>
            <Table striped highlightOnHover>
              <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>
                {users.map((user) => (
                  <Table.Tr key={user.name}>
                    <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={user.status === "פעיל" ? "green" : "yellow"}
                        variant="light"
                      >
                        {user.status}
                      </Badge>
                    </Table.Td>
                  </Table.Tr>
                ))}
              </Table.Tbody>
            </Table>
          </Paper>
        </Container>
      </AppShell.Main>

      {/* Footer */}
      <AppShell.Footer>
        <Group h="100%" px="md" justify="space-between">
          <Text size="xs" c="dimmed">כל הזכויות שמורות 2024</Text>
          <Group gap="md">
            <Text size="xs" c="dimmed" component="a" href="#">תנאי שימוש</Text>
            <Text size="xs" c="dimmed" component="a" href="#">פרטיות</Text>
          </Group>
        </Group>
      </AppShell.Footer>
    </AppShell>
  );
}
  • כל הקומפוננטות של Mantine מגיבות אוטומטית למצב כהה
  • שימוש ב-c="dimmed" מבטיח צבע מותאם בשני המצבים
  • Paper, Table, Badge - כולם משנים צבעים אוטומטית

פתרון תרגיל 6

import { createContext, useContext, ReactNode } from "react";
import { useLocalStorage } from "@mantine/hooks";
import {
  createTheme,
  MantineProvider,
  CSSVariablesResolver,
} from "@mantine/core";

// טיפוסים
type ColorMode = "light" | "dark" | "auto";
type ThemeName = "default" | "ocean" | "forest";
type FontScale = "small" | "default" | "large";

interface AppThemeContextValue {
  colorMode: ColorMode;
  setColorMode: (mode: ColorMode) => void;
  themeName: ThemeName;
  setThemeName: (name: ThemeName) => void;
  fontScale: FontScale;
  setFontScale: (scale: FontScale) => void;
  resetAll: () => void;
}

// Context
const AppThemeContext = createContext<AppThemeContextValue | null>(null);

// Hook
function useAppTheme(): AppThemeContextValue {
  const ctx = useContext(AppThemeContext);
  if (!ctx) throw new Error("useAppTheme must be used within AppThemeProvider");
  return ctx;
}

// תצורות
const primaryColors: Record<ThemeName, string> = {
  default: "blue",
  ocean: "cyan",
  forest: "green",
};

const fontSizeConfigs: Record<FontScale, Record<string, string>> = {
  small: { xs: "0.65rem", sm: "0.775rem", md: "0.875rem", lg: "1rem", xl: "1.125rem" },
  default: { xs: "0.75rem", sm: "0.875rem", md: "1rem", lg: "1.125rem", xl: "1.25rem" },
  large: { xs: "0.875rem", sm: "1rem", md: "1.125rem", lg: "1.25rem", xl: "1.5rem" },
};

// Provider
function AppThemeProvider({ children }: { children: ReactNode }) {
  const [colorMode, setColorMode] = useLocalStorage<ColorMode>({
    key: "app-color-mode",
    defaultValue: "auto",
  });
  const [themeName, setThemeName] = useLocalStorage<ThemeName>({
    key: "app-theme-name",
    defaultValue: "default",
  });
  const [fontScale, setFontScale] = useLocalStorage<FontScale>({
    key: "app-font-scale",
    defaultValue: "default",
  });

  function resetAll() {
    setColorMode("auto");
    setThemeName("default");
    setFontScale("default");
  }

  const theme = createTheme({
    primaryColor: primaryColors[themeName],
    fontSizes: fontSizeConfigs[fontScale],
  });

  const resolver: CSSVariablesResolver = (t) => ({
    variables: {},
    light: {
      "--surface": "#ffffff",
      "--surface-hover": "#f8f9fa",
    },
    dark: {
      "--surface": "#25262b",
      "--surface-hover": "#2c2e33",
    },
  });

  const contextValue: AppThemeContextValue = {
    colorMode,
    setColorMode,
    themeName,
    setThemeName,
    fontScale,
    setFontScale,
    resetAll,
  };

  return (
    <AppThemeContext.Provider value={contextValue}>
      <MantineProvider
        theme={theme}
        defaultColorScheme={colorMode}
        cssVariablesResolver={resolver}
      >
        {children}
      </MantineProvider>
    </AppThemeContext.Provider>
  );
}

export { AppThemeProvider, useAppTheme };
// שימוש
import { AppThemeProvider, useAppTheme } from "./AppThemeProvider";
import { Button, Group, Text, SegmentedControl, Stack, Paper } from "@mantine/core";

function ThemeControls() {
  const { colorMode, setColorMode, themeName, setThemeName, fontScale, setFontScale, resetAll } = useAppTheme();

  return (
    <Paper p="lg" withBorder>
      <Stack gap="md">
        <SegmentedControl
          value={colorMode}
          onChange={(v) => setColorMode(v as any)}
          data={[{ value: "light", label: "בהיר" }, { value: "dark", label: "כהה" }, { value: "auto", label: "אוטומטי" }]}
        />
        <SegmentedControl
          value={themeName}
          onChange={(v) => setThemeName(v as any)}
          data={[{ value: "default", label: "ברירת מחדל" }, { value: "ocean", label: "אוקיינוס" }, { value: "forest", label: "יער" }]}
        />
        <SegmentedControl
          value={fontScale}
          onChange={(v) => setFontScale(v as any)}
          data={[{ value: "small", label: "קטן" }, { value: "default", label: "רגיל" }, { value: "large", label: "גדול" }]}
        />
        <Button variant="light" color="red" onClick={resetAll}>איפוס</Button>
      </Stack>
    </Paper>
  );
}

function App() {
  return (
    <AppThemeProvider>
      <ThemeControls />
    </AppThemeProvider>
  );
}
  • Context מרכז את כל לוגיקת ה-theme
  • useAppTheme מספק גישה נוחה מכל מקום
  • כל ההגדרות ב-localStorage ומשוחזרות אוטומטית

תשובות לשאלות

  1. useMantineColorScheme לעומת useComputedColorScheme - useMantineColorScheme מחזיר את הערך שהוגדר: "light", "dark", או "auto". useComputedColorScheme מחזיר את המצב בפועל: תמיד "light" או "dark". כשהמשתמש בוחר "auto", useMantineColorScheme יחזיר "auto" אבל useComputedColorScheme יחזיר את מה שנקבע לפי הגדרות המערכת.

  2. localStorage ולא cookie - localStorage מאפשר גישה ישירה מ-JavaScript ללא צורך בשרת, מכיל עד 5MB (cookie רק 4KB), ולא נשלח בכל בקשת HTTP (חוסך bandwidth). בנוסף, העדפת theme היא הגדרה לוקאלית שלא צריכה להגיע לשרת.

  3. CSSVariablesResolver - הוא מאפשר הגדרת משתנים דרך JavaScript עם גישה לאובייקט ה-theme. ב-data-mantine-color-scheme ב-CSS צריך לכתוב ידנית כל ערך ואין גישה לנתוני theme. ה-Resolver גם מבטיח שהמשתנים מעודכנים אוטומטית כשה-theme משתנה.

  4. מניעת הבהוב - Mantine מטפלת בזה על ידי הוספת סקריפט inline ב-head (דרך ColorSchemeScript) שקורא את ה-localStorage ומגדיר את ה-data-mantine-color-scheme לפני שהדף מרונדר. כך הדפדפן כבר יודע את ערכת הצבעים הנכונה לפני שריאקט נטען.

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