uchill/front_material/MATERIAL_COMPONENTS_GUIDE.md

767 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 🎨 Руководство по Material Web Components 3
**Только Material компоненты! Никаких собственных компонентов!**
---
## 📦 Установка и настройка
```bash
npm install @material/web
```
---
## 🔧 Настройка TypeScript
Создать файл `types/material-web.d.ts`:
```typescript
// Декларации для Material Web Components
declare namespace JSX {
interface IntrinsicElements {
// Buttons
'md-filled-button': any;
'md-outlined-button': any;
'md-text-button': any;
'md-elevated-button': any;
'md-tonal-button': any;
'md-filled-tonal-button': any;
// Text Fields
'md-filled-text-field': any;
'md-outlined-text-field': any;
// Cards
'md-filled-card': any;
'md-elevated-card': any;
'md-outlined-card': any;
// Lists
'md-list': any;
'md-list-item': any;
// Navigation
'md-navigation-bar': any;
'md-navigation-tab': any;
'md-navigation-drawer': any;
'md-navigation-drawer-modal': any;
// Dialogs & Sheets
'md-dialog': any;
// Chips
'md-chip-set': any;
'md-assist-chip': any;
'md-filter-chip': any;
'md-input-chip': any;
'md-suggestion-chip': any;
// Icons
'md-icon': any;
'md-icon-button': any;
'md-filled-icon-button': any;
'md-tonal-icon-button': any;
'md-outlined-icon-button': any;
// Form Controls
'md-checkbox': any;
'md-radio': any;
'md-switch': any;
'md-slider': any;
// Select
'md-filled-select': any;
'md-outlined-select': any;
'md-select-option': any;
// Menus
'md-menu': any;
'md-menu-item': any;
'md-sub-menu': any;
// Progress
'md-circular-progress': any;
'md-linear-progress': any;
// FAB
'md-fab': any;
'md-branded-fab': any;
// Badges
'md-badge': any;
// Divider
'md-divider': any;
// Tabs
'md-tabs': any;
'md-primary-tab': any;
'md-secondary-tab': any;
}
}
```
---
## 📚 Импорт компонентов
Создать файл `lib/material-components.ts`:
```typescript
// Buttons
import '@material/web/button/filled-button.js';
import '@material/web/button/outlined-button.js';
import '@material/web/button/text-button.js';
import '@material/web/button/elevated-button.js';
import '@material/web/button/tonal-button.js';
// Text Fields
import '@material/web/textfield/filled-text-field.js';
import '@material/web/textfield/outlined-text-field.js';
// Cards
import '@material/web/labs/card/filled-card.js';
import '@material/web/labs/card/elevated-card.js';
import '@material/web/labs/card/outlined-card.js';
// Lists
import '@material/web/list/list.js';
import '@material/web/list/list-item.js';
// Navigation (из labs)
import '@material/web/labs/navigationbar/navigation-bar.js';
import '@material/web/labs/navigationtab/navigation-tab.js';
import '@material/web/labs/navigationdrawer/navigation-drawer.js';
// Dialogs
import '@material/web/dialog/dialog.js';
// Chips
import '@material/web/chips/chip-set.js';
import '@material/web/chips/assist-chip.js';
import '@material/web/chips/filter-chip.js';
import '@material/web/chips/input-chip.js';
import '@material/web/chips/suggestion-chip.js';
// Icons
import '@material/web/icon/icon.js';
import '@material/web/iconbutton/icon-button.js';
import '@material/web/iconbutton/filled-icon-button.js';
import '@material/web/iconbutton/tonal-icon-button.js';
import '@material/web/iconbutton/outlined-icon-button.js';
// Form Controls
import '@material/web/checkbox/checkbox.js';
import '@material/web/radio/radio.js';
import '@material/web/switch/switch.js';
import '@material/web/slider/slider.js';
// Select
import '@material/web/select/filled-select.js';
import '@material/web/select/outlined-select.js';
import '@material/web/select/select-option.js';
// Menus
import '@material/web/menu/menu.js';
import '@material/web/menu/menu-item.js';
import '@material/web/menu/sub-menu.js';
// Progress
import '@material/web/progress/circular-progress.js';
import '@material/web/progress/linear-progress.js';
// FAB
import '@material/web/fab/fab.js';
import '@material/web/fab/branded-fab.js';
// Badges
import '@material/web/labs/badge/badge.js';
// Divider
import '@material/web/divider/divider.js';
// Tabs
import '@material/web/tabs/tabs.js';
import '@material/web/tabs/primary-tab.js';
import '@material/web/tabs/secondary-tab.js';
```
Импортировать в `app/layout.tsx`:
```typescript
import '@/lib/material-components';
```
---
## 🎨 Material Design 3 Grid System
Создать файл `styles/material-grid.css`:
```css
/* Material Design 3 Layout Grid */
/* Breakpoints:
- xs: 0-599px (Mobile)
- sm: 600-839px (Tablet Portrait)
- md: 840-1239px (Tablet Landscape / Small Desktop)
- lg: 1240-1439px (Desktop)
- xl: 1440px+ (Large Desktop)
*/
.md-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
padding: 0 16px;
width: 100%;
}
/* Tablet Portrait (600-839px) */
@media (min-width: 600px) {
.md-grid {
grid-template-columns: repeat(8, 1fr);
gap: 24px;
padding: 0 24px;
}
}
/* Tablet Landscape / Desktop (840-1239px) */
@media (min-width: 840px) {
.md-grid {
grid-template-columns: repeat(12, 1fr);
gap: 24px;
padding: 0 24px;
}
}
/* Large Desktop (1240px+) */
@media (min-width: 1240px) {
.md-grid {
gap: 24px;
padding: 0 24px;
max-width: 1200px;
margin: 0 auto;
}
}
/* Column Span Classes */
/* Mobile (4 columns) */
.md-col-1 { grid-column: span 1; }
.md-col-2 { grid-column: span 2; }
.md-col-3 { grid-column: span 3; }
.md-col-4 { grid-column: span 4; }
/* Tablet (8 columns) */
@media (min-width: 600px) {
.md-col-sm-1 { grid-column: span 1; }
.md-col-sm-2 { grid-column: span 2; }
.md-col-sm-4 { grid-column: span 4; }
.md-col-sm-6 { grid-column: span 6; }
.md-col-sm-8 { grid-column: span 8; }
}
/* Desktop (12 columns) */
@media (min-width: 840px) {
.md-col-md-3 { grid-column: span 3; }
.md-col-md-4 { grid-column: span 4; }
.md-col-md-6 { grid-column: span 6; }
.md-col-md-8 { grid-column: span 8; }
.md-col-md-9 { grid-column: span 9; }
.md-col-md-12 { grid-column: span 12; }
}
/* Flexbox альтернатива для простых случаев */
.md-flex {
display: flex;
gap: 16px;
}
.md-flex-col {
display: flex;
flex-direction: column;
gap: 16px;
}
.md-flex-wrap {
flex-wrap: wrap;
}
```
**Использование:**
```tsx
<div className="md-grid">
<div className="md-col-4 md-col-sm-4 md-col-md-6">
<md-elevated-card>Карточка 1</md-elevated-card>
</div>
<div className="md-col-4 md-col-sm-4 md-col-md-6">
<md-elevated-card>Карточка 2</md-elevated-card>
</div>
</div>
```
---
## 📱 Примеры использования компонентов
### 1. Форма входа
```tsx
'use client';
export default function LoginPage() {
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Логика входа
};
return (
<div className="md-grid" style={{ minHeight: '100vh', alignItems: 'center' }}>
<div className="md-col-4 md-col-sm-8 md-col-md-6" style={{ margin: '0 auto' }}>
<md-elevated-card style={{ padding: '32px' }}>
<h1 className="md-typescale-headline-medium" style={{ marginBottom: '24px' }}>
Вход в систему
</h1>
<form onSubmit={handleSubmit}>
<md-outlined-text-field
label="Email"
type="email"
required
style={{ width: '100%', marginBottom: '16px' }}
></md-outlined-text-field>
<md-outlined-text-field
label="Пароль"
type="password"
required
style={{ width: '100%', marginBottom: '24px' }}
></md-outlined-text-field>
<md-filled-button type="submit" style={{ width: '100%' }}>
Войти
</md-filled-button>
</form>
</md-elevated-card>
</div>
</div>
);
}
```
### 2. Дашборд с карточками
```tsx
'use client';
export default function DashboardPage() {
return (
<div className="md-grid" style={{ padding: '24px 0' }}>
{/* Статистика */}
<div className="md-col-4 md-col-sm-4 md-col-md-4">
<md-elevated-card style={{ padding: '24px' }}>
<md-icon style={{ fontSize: '48px', color: 'var(--md-sys-color-primary)' }}>
groups
</md-icon>
<h2 className="md-typescale-headline-small">24</h2>
<p className="md-typescale-body-medium">Студентов</p>
</md-elevated-card>
</div>
<div className="md-col-4 md-col-sm-4 md-col-md-4">
<md-elevated-card style={{ padding: '24px' }}>
<md-icon style={{ fontSize: '48px', color: 'var(--md-sys-color-primary)' }}>
calendar_month
</md-icon>
<h2 className="md-typescale-headline-small">12</h2>
<p className="md-typescale-body-medium">Занятий на неделе</p>
</md-elevated-card>
</div>
<div className="md-col-4 md-col-sm-4 md-col-md-4">
<md-elevated-card style={{ padding: '24px' }}>
<md-icon style={{ fontSize: '48px', color: 'var(--md-sys-color-primary)' }}>
payments
</md-icon>
<h2 className="md-typescale-headline-small">45000</h2>
<p className="md-typescale-body-medium">Доход за месяц</p>
</md-elevated-card>
</div>
</div>
);
}
```
### 3. Bottom Navigation Bar (iOS-style)
```tsx
'use client';
import { usePathname, useRouter } from 'next/navigation';
import { useEffect, useRef } from 'react';
export function BottomNavigationBar({ userRole }: { userRole: 'mentor' | 'client' | 'parent' }) {
const pathname = usePathname();
const router = useRouter();
const navRef = useRef<any>(null);
useEffect(() => {
const nav = navRef.current;
if (!nav) return;
const handleNavigation = (e: CustomEvent) => {
const activeIndex = e.detail.activeIndex;
const tabs = nav.querySelectorAll('md-navigation-tab');
const activeTab = tabs[activeIndex];
const href = activeTab?.getAttribute('data-href');
if (href) {
router.push(href);
}
};
nav.addEventListener('navigation-tab-interaction', handleNavigation);
return () => nav.removeEventListener('navigation-tab-interaction', handleNavigation);
}, [router]);
// Меню для ментора
if (userRole === 'mentor') {
return (
<md-navigation-bar ref={navRef} className="ios-bottom-bar">
<md-navigation-tab
label="Главная"
data-href="/dashboard"
active={pathname === '/dashboard'}
>
<md-icon slot="inactive-icon">home</md-icon>
<md-icon slot="active-icon">home</md-icon>
</md-navigation-tab>
<md-navigation-tab
label="Студенты"
data-href="/students"
active={pathname === '/students'}
>
<md-icon slot="inactive-icon">group</md-icon>
<md-icon slot="active-icon">group</md-icon>
</md-navigation-tab>
<md-navigation-tab
label="Расписание"
data-href="/schedule"
active={pathname === '/schedule'}
>
<md-icon slot="inactive-icon">calendar_month</md-icon>
<md-icon slot="active-icon">calendar_month</md-icon>
</md-navigation-tab>
<md-navigation-tab
label="Чат"
data-href="/chat"
active={pathname === '/chat'}
>
<md-icon slot="inactive-icon">chat</md-icon>
<md-icon slot="active-icon">chat</md-icon>
</md-navigation-tab>
</md-navigation-bar>
);
}
// Аналогично для client и parent...
return null;
}
```
### 4. Список студентов
```tsx
'use client';
export default function StudentsPage() {
return (
<div className="md-grid">
<div className="md-col-4 md-col-sm-8 md-col-md-12">
<md-list>
<md-list-item>
<md-icon slot="start">person</md-icon>
<div slot="headline">Иван Иванов</div>
<div slot="supporting-text">ivan@example.com</div>
<md-icon-button slot="end">
<md-icon>more_vert</md-icon>
</md-icon-button>
</md-list-item>
<md-divider></md-divider>
<md-list-item>
<md-icon slot="start">person</md-icon>
<div slot="headline">Петр Петров</div>
<div slot="supporting-text">petr@example.com</div>
<md-icon-button slot="end">
<md-icon>more_vert</md-icon>
</md-icon-button>
</md-list-item>
</md-list>
</div>
</div>
);
}
```
### 5. Диалог (модальное окно)
```tsx
'use client';
import { useRef } from 'react';
export function CreateLessonDialog() {
const dialogRef = useRef<any>(null);
const openDialog = () => {
dialogRef.current?.show();
};
const closeDialog = () => {
dialogRef.current?.close();
};
return (
<>
<md-filled-button onClick={openDialog}>
Создать занятие
</md-filled-button>
<md-dialog ref={dialogRef}>
<div slot="headline">Новое занятие</div>
<form slot="content" method="dialog">
<md-outlined-text-field
label="Название"
required
style={{ width: '100%', marginBottom: '16px' }}
></md-outlined-text-field>
<md-outlined-text-field
label="Дата"
type="date"
required
style={{ width: '100%', marginBottom: '16px' }}
></md-outlined-text-field>
</form>
<div slot="actions">
<md-text-button onClick={closeDialog}>Отмена</md-text-button>
<md-filled-button onClick={closeDialog}>Создать</md-filled-button>
</div>
</md-dialog>
</>
);
}
```
### 6. Chips (фильтры)
```tsx
'use client';
export function FiltersBar() {
return (
<md-chip-set>
<md-filter-chip label="Все занятия"></md-filter-chip>
<md-filter-chip label="Активные"></md-filter-chip>
<md-filter-chip label="Завершенные"></md-filter-chip>
<md-filter-chip label="Отмененные"></md-filter-chip>
</md-chip-set>
);
}
```
### 7. Прогресс индикаторы
```tsx
'use client';
export function LoadingIndicator() {
return (
<div style={{ display: 'flex', justifyContent: 'center', padding: '48px' }}>
<md-circular-progress indeterminate></md-circular-progress>
</div>
);
}
export function UploadProgress({ value }: { value: number }) {
return (
<md-linear-progress value={value / 100}></md-linear-progress>
);
}
```
---
## 🎨 Кастомизация под iOS 24+ стиль
Создать файл `styles/ios-material.css`:
```css
/* iOS 24+ адаптация для Material компонентов */
/* Bottom Navigation Bar */
md-navigation-bar {
--md-navigation-bar-container-color: rgba(255, 255, 255, 0.8);
--md-navigation-bar-container-height: 80px;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-top: 0.5px solid rgba(0, 0, 0, 0.1);
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.08);
border-radius: 24px 24px 0 0;
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
}
[data-theme="dark"] md-navigation-bar {
--md-navigation-bar-container-color: rgba(28, 28, 30, 0.9);
border-top-color: rgba(255, 255, 255, 0.1);
}
/* Cards с blur эффектом */
md-elevated-card {
--md-elevated-card-container-color: rgba(255, 255, 255, 0.8);
--md-elevated-card-container-shape: 20px;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
[data-theme="dark"] md-elevated-card {
--md-elevated-card-container-color: rgba(28, 28, 30, 0.8);
}
/* Buttons с iOS стилем */
md-filled-button {
--md-filled-button-container-shape: 16px;
--md-filled-button-container-height: 48px;
font-weight: 500;
}
/* Text Fields с iOS стилем */
md-outlined-text-field {
--md-outlined-text-field-container-shape: 12px;
--md-outlined-text-field-outline-width: 1px;
}
/* List Items с отступами */
md-list-item {
--md-list-item-container-shape: 12px;
margin: 4px 8px;
}
/* Dialogs с rounded corners */
md-dialog {
--md-dialog-container-shape: 24px;
}
```
---
## 🌈 Material Icons
Использовать Material Symbols из Google Fonts:
```html
<!-- В app/layout.tsx или в head -->
<link
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200"
rel="stylesheet"
/>
```
**Использование:**
```tsx
<md-icon>home</md-icon>
<md-icon>calendar_month</md-icon>
<md-icon>chat</md-icon>
<md-icon>group</md-icon>
<md-icon>settings</md-icon>
```
**Список популярных иконок:**
- `home` - Главная
- `calendar_month` - Календарь
- `chat` - Чат
- `group` - Студенты/Группы
- `person` - Профиль
- `settings` - Настройки
- `notifications` - Уведомления
- `payment` - Оплата
- `school` - Обучение
- `video_call` - Видеозвонок
- `assignment` - Задания
- `folder` - Материалы
---
## 📋 Типографика Material Design 3
```html
<!-- Заголовки -->
<h1 className="md-typescale-display-large">Display Large</h1>
<h2 className="md-typescale-display-medium">Display Medium</h2>
<h3 className="md-typescale-display-small">Display Small</h3>
<h1 className="md-typescale-headline-large">Headline Large</h1>
<h2 className="md-typescale-headline-medium">Headline Medium</h2>
<h3 className="md-typescale-headline-small">Headline Small</h3>
<!-- Основной текст -->
<p className="md-typescale-body-large">Body Large</p>
<p className="md-typescale-body-medium">Body Medium</p>
<p className="md-typescale-body-small">Body Small</p>
<!-- Подписи -->
<p className="md-typescale-label-large">Label Large</p>
<p className="md-typescale-label-medium">Label Medium</p>
<p className="md-typescale-label-small">Label Small</p>
```
Импортировать типографику:
```typescript
import { styles as typescaleStyles } from '@material/web/typography/md-typescale-styles.js';
// В layout.tsx
useEffect(() => {
document.adoptedStyleSheets.push(typescaleStyles.styleSheet);
}, []);
```
---
## 🎯 Важные замечания
1. **Web Components в React:**
- Material Web Components - это нативные Web Components
- Используются напрямую в JSX как HTML элементы
- Не нужны wrapper компоненты
2. **События:**
- Используйте `ref` для доступа к элементу
- Добавляйте слушатели событий через `addEventListener`
3. **Стилизация:**
- CSS Variables для кастомизации
- Только чистый CSS, без Tailwind
- Material Grid System для layout
4. **Компоненты из `labs`:**
- Некоторые компоненты находятся в `@material/web/labs/`
- Например: `navigation-bar`, `badge`, `card`
---
**Документация:** https://github.com/material-components/material-web
**Demo:** https://material-web.dev/