Стилизация

Документация по системе стилизации в приложении, настройке цветовой схемы и оформлению компонентов.

Обзор

Boilerplate использует Tailwind CSS для стилизации и построен на основе компонентов Shadcn UI. Система стилизации базируется на:

  • Tailwind CSS для утилитарных классов и отзывчивого дизайна
  • CSS-переменных для определения цветовой схемы
  • Компонентов Shadcn UI для унифицированного внешнего вида
  • Поддержки светлой и тёмной темы

Основные файлы

Система стилизации основана на следующих ключевых файлах:

  • globals.css - глобальные стили и CSS-переменные
  • tailwind.config.js - конфигурация Tailwind CSS
  • lib/utils.ts - утилиты для работы с классами
  • components.json - конфигурация компонентов Shadcn UI

Цветовая схема

Цветовая схема определяется через CSS-переменные в файле globals.css. Приложение поддерживает светлую и тёмную темы с различными наборами цветов.

Основные цветовые переменные

В корне определены следующие основные CSS-переменные:

css
:root {
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  --card: oklch(1 0 0);
  --card-foreground: oklch(0.145 0 0);
  --popover: oklch(1 0 0);
  --popover-foreground: oklch(0.145 0 0);
  --primary: oklch(0.205 0 0);
  --primary-foreground: oklch(0.985 0 0);
  --secondary: oklch(0.97 0 0);
  --secondary-foreground: oklch(0.205 0 0);
  --muted: oklch(0.97 0 0);
  --muted-foreground: oklch(0.556 0 0);
  --accent: oklch(0.97 0 0);
  --accent-foreground: oklch(0.205 0 0);
  --destructive: oklch(0.577 0.245 27.325);
  --destructive-foreground: oklch(0.577 0.245 27.325);
  --border: oklch(0.922 0 0);
  --input: oklch(0.922 0 0);
  --ring: oklch(0.708 0 0);
  --radius: 0.625rem;
}

Тёмная тема

Для тёмной темы определены переопределения CSS-переменных:

css
.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  --card: oklch(0.145 0 0);
  --card-foreground: oklch(0.985 0 0);
  --popover: oklch(0.145 0 0);
  --popover-foreground: oklch(0.985 0 0);
  --primary: oklch(0.985 0 0);
  --primary-foreground: oklch(0.205 0 0);
  --secondary: oklch(0.269 0 0);
  --secondary-foreground: oklch(0.985 0 0);
  --muted: oklch(0.269 0 0);
  --muted-foreground: oklch(0.708 0 0);
  --accent: oklch(0.269 0 0);
  --accent-foreground: oklch(0.985 0 0);
  --destructive: oklch(0.396 0.141 25.723);
  --destructive-foreground: oklch(0.637 0.237 25.331);
  --border: oklch(0.269 0 0);
  --input: oklch(0.269 0 0);
  --ring: oklch(0.439 0 0);
}

Изменение цветовой схемы

Для изменения цветовой схемы можно отредактировать переменные в файле globals.css. Например, чтобы изменить основной цвет на синий:

css
:root {
  /* Другие переменные */
  --primary: oklch(0.5 0.2 240); /* Синий цвет */
  --primary-foreground: oklch(0.985 0 0);
}

Настройка Tailwind

Настройка Tailwind выполняется в файле tailwind.config.js. Конфигурация включает подключение CSS-переменных к цветам Tailwind:

javascript
/** @type {import('tailwindcss').Config} */
module.exports = {
  darkMode: ["class"],
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    container: {
      center: true,
      padding: "2rem",
      screens: {
        "2xl": "1400px",
      },
    },
    extend: {
      colors: {
        border: "hsl(var(--border))",
        input: "hsl(var(--input))",
        ring: "hsl(var(--ring))",
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
        primary: {
          DEFAULT: "hsl(var(--primary))",
          foreground: "hsl(var(--primary-foreground))",
        },
        secondary: {
          DEFAULT: "hsl(var(--secondary))",
          foreground: "hsl(var(--secondary-foreground))",
        },
        // Другие цвета
      },
      borderRadius: {
        lg: "var(--radius)",
        md: "calc(var(--radius) - 2px)",
        sm: "calc(var(--radius) - 4px)",
      },
      // Другие настройки
    },
  },
  plugins: [require("tailwindcss-animate")],
}

Добавление новых цветов

Чтобы добавить новый цвет в палитру, выполните следующие шаги:

  1. Добавьте новую CSS-переменную в globals.css:
    css
    :root {
      /* Существующие переменные */
      --success: oklch(0.6 0.2 140);
      --success-foreground: oklch(0.985 0 0);
    }
    
    .dark {
      /* Существующие переменные */
      --success: oklch(0.5 0.2 140);
      --success-foreground: oklch(0.985 0 0);
    }
  2. Добавьте цвет в конфигурацию Tailwind:
    javascript
    // tailwind.config.js
    module.exports = {
      theme: {
        extend: {
          colors: {
            // Существующие цвета
            success: {
              DEFAULT: "hsl(var(--success))",
              foreground: "hsl(var(--success-foreground))",
            },
          },
        },
      },
    }

Переключение темы

Для добавления переключения между светлой и тёмной темой создайте компонент переключателя:

tsx
// components/ThemeToggle.tsx
"use client";

import { useState, useEffect } from "react";
import { useTheme } from "next-themes";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";

export function ThemeToggle() {
  const [mounted, setMounted] = useState(false);
  const { theme, setTheme } = useTheme();

  // Предотвращаем гидратационные ошибки
  useEffect(() => {
    setMounted(true);
  }, []);

  if (!mounted) {
    return null;
  }

  return (
    <div className="flex items-center space-x-2">
      <Switch
        id="theme-toggle"
        checked={theme === "dark"}
        onCheckedChange={(checked) => {
          setTheme(checked ? "dark" : "light");
        }}
      />
      <Label htmlFor="theme-toggle">
        {theme === "dark" ? "Тёмная тема" : "Светлая тема"}
      </Label>
    </div>
  );
}

Затем добавьте провайдер темы в корневой компонент:

tsx
// app/providers.tsx
"use client";

import { ThemeProvider } from "next-themes";
import { ReactNode } from "react";

export function Providers({ children }: { children: ReactNode }) {
  return (
    <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
      {children}
    </ThemeProvider>
  );
}

И обновите корневой layout:

tsx
// app/layout.tsx
import { Providers } from "./providers";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ru" suppressHydrationWarning>
      <body>
        <Providers>
          {children}
        </Providers>
      </body>
    </html>
  );
}

Стилизация компонентов Shadcn UI

Компоненты Shadcn UI используют систему CSS-переменных и могут быть настроены для соответствия дизайну вашего приложения.

Настройка кнопок

Для настройки внешнего вида кнопок вы можете переопределить компонент Button:

tsx
// components/ui/button.tsx
"use client";

import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";

import { cn } from "@/lib/utils";

const buttonVariants = cva(
  "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive:
          "bg-destructive text-destructive-foreground hover:bg-destructive/90",
        outline:
          "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
        secondary:
          "bg-secondary text-secondary-foreground hover:bg-secondary/80",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline",
        // Добавьте свой вариант
        custom: "bg-blue-500 text-white hover:bg-blue-600",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 rounded-md px-3",
        lg: "h-11 rounded-md px-8",
        icon: "h-10 w-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
);

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, asChild = false, ...props }, ref) => {
    const Comp = asChild ? Slot : "button";
    return (
      <Comp
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      />
    );
  }
);
Button.displayName = "Button";

export { Button, buttonVariants };

Затем вы можете использовать новый вариант:

tsx
import { Button } from "@/components/ui/button";

export function MyComponent() {
  return (
    <Button variant="custom">Мой стилизованный кнопка</Button>
  );
}

Стилизация форм

Для стилизации форм используйте компоненты из Shadcn UI, которые поддерживают все настройки темы:

tsx
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";

// Определение схемы формы
const formSchema = z.object({
  email: z.string().email("Введите корректный email"),
  password: z.string().min(8, "Пароль должен содержать не менее 8 символов"),
});

export function LoginForm() {
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      email: "",
      password: "",
    },
  });

  function onSubmit(values: z.infer<typeof formSchema>) {
    // Обработка отправки формы
    console.log(values);
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input 
                  placeholder="example@example.com" 
                  {...field} 
                  // Дополнительные стили
                  className="bg-muted/50 border-border/50 focus:border-primary"
                />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="password"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Пароль</FormLabel>
              <FormControl>
                <Input 
                  type="password" 
                  placeholder="********" 
                  {...field} 
                  // Дополнительные стили
                  className="bg-muted/50 border-border/50 focus:border-primary"
                />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit" className="w-full">Войти</Button>
      </form>
    </Form>
  );
}

Кастомизация стилей полей формы

Для изменения стилей полей формы можно либо переопределить компонент, либо добавить классы через className:

tsx
// Переопределение компонента Input
// components/ui/input.tsx
import * as React from "react";
import { cn } from "@/lib/utils";

export interface InputProps
  extends React.InputHTMLAttributes<HTMLInputElement> {}

const Input = React.forwardRef<HTMLInputElement, InputProps>(
  ({ className, type, ...props }, ref) => {
    return (
      <input
        type={type}
        className={cn(
          "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
          // Дополнительные стили по умолчанию
          "transition-colors focus:border-primary",
          className
        )}
        ref={ref}
        {...props}
      />
    );
  }
);
Input.displayName = "Input";

export { Input };

Утилита cn

Для объединения классов Tailwind используется утилита cn из lib/utils.ts:

typescript
// lib/utils.ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Эта утилита позволяет объединять классы, в том числе условно:

tsx
import { cn } from "@/lib/utils";

export function MyComponent({ isActive }: { isActive: boolean }) {
  return (
    <div
      className={cn(
        "p-4 rounded-md transition-colors",
        isActive 
          ? "bg-primary text-primary-foreground" 
          : "bg-muted text-muted-foreground hover:bg-muted/80"
      )}
    >
      Мой компонент
    </div>
  );
}

Адаптивный дизайн

Tailwind CSS предоставляет готовые брейкпоинты для адаптивного дизайна:

  • sm - 640px и выше
  • md - 768px и выше
  • lg - 1024px и выше
  • xl - 1280px и выше
  • 2xl - 1536px и выше

Пример использования адаптивных классов:

tsx
<div className="flex flex-col md:flex-row md:space-x-4">
  <div className="w-full md:w-1/2 lg:w-1/3 p-4">
    <h3 className="text-xl md:text-2xl">Заголовок</h3>
    <p className="text-sm md:text-base">Содержимое</p>
  </div>
  <div className="w-full md:w-1/2 lg:w-2/3 p-4">
    <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
      {/* Содержимое сетки */}
    </div>
  </div>
</div>

Рекомендации

  • Используйте компоненты Shadcn UI для обеспечения единообразия интерфейса
  • Следуйте подходу "mobile-first" при разработке с Tailwind
  • Избегайте прямого изменения CSS файлов, используйте переменные и Tailwind
  • Для повторяющихся комбинаций стилей создавайте компоненты или классы в globals.css
  • Используйте утилиту cn для объединения классов и условного применения стилей
  • При необходимости кастомизации темы, редактируйте CSS-переменные в globals.css

Полезные ресурсы