templates lab updates
This commit is contained in:
parent
5d1bf967ef
commit
21c6880005
28
.gitignore
vendored
Normal file
28
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
*~
|
||||||
|
node_modules
|
||||||
|
bower_components
|
||||||
|
.idea/
|
||||||
|
.idea
|
||||||
|
.build
|
||||||
|
build/
|
||||||
|
_build
|
||||||
|
*.zip
|
||||||
|
*.iml
|
||||||
|
modules.xml
|
||||||
|
vcs.xml
|
||||||
|
workspace.xml
|
||||||
|
cert.pem
|
||||||
|
key.pem
|
||||||
|
.vs/*
|
||||||
|
|
||||||
|
.history
|
||||||
|
.claude
|
||||||
|
.DS_Store
|
||||||
|
.playwright-mcp/
|
||||||
|
*.png
|
||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
.env
|
||||||
|
*.pth
|
||||||
|
uploads/
|
||||||
|
results/
|
||||||
173
modernkit/README.md
Normal file
173
modernkit/README.md
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
# ModernKit Project
|
||||||
|
|
||||||
|
ModernKit is a versatile and performant project boilerplate built with Gulp, Twig, and SCSS. It provides a robust build system and a foundational UI kit to kickstart your web development.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Build System:**
|
||||||
|
- Gulp-powered task runner.
|
||||||
|
- SCSS compilation with Autoprefixer and minification (for production).
|
||||||
|
- JavaScript transpilation (Babel) and concatenation with separate development (unminified) and production (minified) builds.
|
||||||
|
- Image optimization (including WebP generation for production).
|
||||||
|
- Twig templating with dynamic data injection from JSON files (only top-level templates are processed into HTML files).
|
||||||
|
- Asset copying (e.g., other static files).
|
||||||
|
- Favicon copying.
|
||||||
|
- Font copying.
|
||||||
|
- SVG sprite generation and injection into HTML.
|
||||||
|
- Resource versioning (cache busting) using content hashing (for production).
|
||||||
|
- Source map generation for CSS and JavaScript (for development).
|
||||||
|
- **Code Quality:** Biome integration for JavaScript linting and formatting.
|
||||||
|
- Robust error handling with `gulp-plumber`.
|
||||||
|
- **Performance Optimization:** Utilizes `gulp-cached` and `gulp-remember` to speed up recompilation of unchanged files.
|
||||||
|
- Clean task to remove build artifacts.
|
||||||
|
- **UI Kit:**
|
||||||
|
- Basic styles and resets.
|
||||||
|
- Comprehensive typography.
|
||||||
|
- Responsive grid system.
|
||||||
|
- Utility classes for spacing, text alignment, display, and colors.
|
||||||
|
- Basic components (e.g., buttons).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
Make sure you have Node.js and npm installed on your system.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
```bash
|
||||||
|
git clone <your-repository-url>
|
||||||
|
cd modernkit
|
||||||
|
```
|
||||||
|
2. Install the dependencies:
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available Commands
|
||||||
|
|
||||||
|
- `npm start`: Runs the default Gulp task, which starts a development server with BrowserSync, watches for file changes, and recompiles assets automatically. This uses the development JavaScript build (unminified) and generates source maps.
|
||||||
|
- `npm run build`: Performs a one-time development build. Cleans the `dist` directory, lints JavaScript, generates SVG sprites, compiles Twig templates, SCSS, JavaScript (development version), optimizes images, and copies assets, favicon, and fonts.
|
||||||
|
- `npm run build:prod`: Performs a one-time production build. Similar to `npm run build`, but uses minified CSS and JavaScript (production version), optimizes images more aggressively, generates WebP versions of images, and applies content hashing for cache busting.
|
||||||
|
- `npm run test`: Runs Jest tests.
|
||||||
|
- `npm run lint`: Runs Biome check on JavaScript files.
|
||||||
|
- `npm run format`: Runs Biome formatter on JavaScript files.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
modernkit/
|
||||||
|
├───gulpfile.js
|
||||||
|
├───jest.config.js
|
||||||
|
├───package-lock.json
|
||||||
|
├───package.json
|
||||||
|
├───biome.json
|
||||||
|
├───dist/ # Compiled output (ignored by Git)
|
||||||
|
├───node_modules/ # Node.js dependencies (ignored by Git)
|
||||||
|
└───src/
|
||||||
|
├───images/ # Source images
|
||||||
|
│ └───test.png
|
||||||
|
├───js/ # Source JavaScript files
|
||||||
|
│ ├───main.js
|
||||||
|
│ └───main.test.js
|
||||||
|
├───scss/ # Source SCSS files
|
||||||
|
│ ├───main.scss
|
||||||
|
│ ├───_variables.scss
|
||||||
|
│ ├───_base.scss
|
||||||
|
│ ├───_typography.scss
|
||||||
|
│ ├───_grid.scss
|
||||||
|
│ ├───_utilities.scss
|
||||||
|
│ └───_components.scss
|
||||||
|
├───templates/ # Twig templates
|
||||||
|
│ ├───index.twig
|
||||||
|
│ ├───about.twig
|
||||||
|
│ └───components/ # Twig components
|
||||||
|
│ ├───button.twig
|
||||||
|
│ ├───footer.twig
|
||||||
|
│ └───header.twig
|
||||||
|
├───data/ # JSON data for Twig templates
|
||||||
|
│ ├───global.json
|
||||||
|
│ └───about.json
|
||||||
|
├───assets/ # Static assets (e.g., other files)
|
||||||
|
│ └───test.txt
|
||||||
|
├───icons/ # SVG icons for sprite generation
|
||||||
|
│ ├───clock.svg
|
||||||
|
│ └───info.svg
|
||||||
|
└───fonts/ # Font files
|
||||||
|
└───test.ttf
|
||||||
|
```
|
||||||
|
|
||||||
|
## UI Kit Usage
|
||||||
|
|
||||||
|
### Typography
|
||||||
|
|
||||||
|
Use standard HTML heading tags (`<h1>` to `<h6>`) and paragraph tags (`<p>`). Utility classes like `.small` can be applied for smaller text.
|
||||||
|
|
||||||
|
### Grid System
|
||||||
|
|
||||||
|
Utilize the `.container`, `.row`, and `.col-*` classes for responsive layouts. Breakpoints are defined in `src/scss/_variables.scss`.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<!-- Content -->
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<!-- Content -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Buttons
|
||||||
|
|
||||||
|
Apply the `.btn` base class along with contextual classes like `.btn-primary`, `.btn-secondary`, etc.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<button class="btn btn-primary">Primary Button</button>
|
||||||
|
<a href="#" class="btn btn-success">Success Link</a>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Utility Classes
|
||||||
|
|
||||||
|
Various utility classes are available for common styling needs:
|
||||||
|
|
||||||
|
- **Spacing:** `m-1`, `pt-3`, `px-auto`, etc. (for margin and padding)
|
||||||
|
- **Text Alignment:** `text-left`, `text-center`, `text-right`
|
||||||
|
- **Display:** `d-block`, `d-inline-block`, `d-flex`
|
||||||
|
- **Colors:** `text-primary`, `bg-dark`, etc.
|
||||||
|
|
||||||
|
### SVG Icons
|
||||||
|
|
||||||
|
SVG icons from `src/icons` are compiled into a sprite and injected into your HTML. Use them with the `<svg><use>` pattern:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<svg class="icon" width="24" height="24"><use xlink:href="#clock"></use></svg>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data Management
|
||||||
|
|
||||||
|
Twig templates can access global data from `src/data/global.json` and page-specific data from `src/data/<template-name>.json` (e.g., `src/data/about.json` for `about.twig`). Page-specific data will override global data if keys conflict.
|
||||||
|
|
||||||
|
Example in Twig:
|
||||||
|
|
||||||
|
```twig
|
||||||
|
<h1>{{ global.welcomeMessage }}</h1>
|
||||||
|
<p>{{ pageTitle }}</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Jest is configured to run unit tests for JavaScript files. The test environment is set to `jsdom` to simulate a browser environment.
|
||||||
|
|
||||||
|
To run tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Feel free to contribute to this project by submitting issues or pull requests.
|
||||||
34
modernkit/dist/about.html
vendored
Normal file
34
modernkit/dist/about.html
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>About Us - </title>
|
||||||
|
<link rel="stylesheet" href="main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="display: none;"></div>
|
||||||
|
<header class="bg-dark text-white py-3">
|
||||||
|
<div class="container d-flex justify-content-between align-items-center">
|
||||||
|
<a href="#" class="text-white h4 mb-0">ModernKit</a>
|
||||||
|
<nav>
|
||||||
|
<ul class="list-unstyled d-flex mb-0">
|
||||||
|
<li class="ml-3"><a href="#" class="text-white">Home</a></li>
|
||||||
|
<li class="ml-3"><a href="#" class="text-white">About</a></li>
|
||||||
|
<li class="ml-3"><a href="#" class="text-white">Contact</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main class="container py-5">
|
||||||
|
<h1 class="mb-4">About Us</h1>
|
||||||
|
<p>This is the content for the about page. We are a team dedicated to building amazing things.</p>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="bg-dark text-white py-3 mt-5">
|
||||||
|
<div class="container text-center">
|
||||||
|
<p class="mb-0">© 2025 ModernKit. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</footer> <script src="main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
32
modernkit/dist/base.html
vendored
Normal file
32
modernkit/dist/base.html
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title></title>
|
||||||
|
<link rel="stylesheet" href="main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="display: none;"></div>
|
||||||
|
<header class="bg-dark text-white py-3">
|
||||||
|
<div class="container d-flex justify-content-between align-items-center">
|
||||||
|
<a href="#" class="text-white h4 mb-0">ModernKit</a>
|
||||||
|
<nav>
|
||||||
|
<ul class="list-unstyled d-flex mb-0">
|
||||||
|
<li class="ml-3"><a href="#" class="text-white">Home</a></li>
|
||||||
|
<li class="ml-3"><a href="#" class="text-white">About</a></li>
|
||||||
|
<li class="ml-3"><a href="#" class="text-white">Contact</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main class="container py-5">
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="bg-dark text-white py-3 mt-5">
|
||||||
|
<div class="container text-center">
|
||||||
|
<p class="mb-0">© 2025 ModernKit. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</footer> <script src="js/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
106
modernkit/dist/components-showcase.html
vendored
Normal file
106
modernkit/dist/components-showcase.html
vendored
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title></title>
|
||||||
|
<link rel="stylesheet" href="main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="display: none;"></div>
|
||||||
|
<header class="bg-dark text-white py-3">
|
||||||
|
<div class="container d-flex justify-content-between align-items-center">
|
||||||
|
<a href="#" class="text-white h4 mb-0">ModernKit</a>
|
||||||
|
<nav>
|
||||||
|
<ul class="list-unstyled d-flex mb-0">
|
||||||
|
<li class="ml-3"><a href="#" class="text-white">Home</a></li>
|
||||||
|
<li class="ml-3"><a href="#" class="text-white">About</a></li>
|
||||||
|
<li class="ml-3"><a href="#" class="text-white">Contact</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main class="container py-5">
|
||||||
|
<h1>UI Components Showcase</h1>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Tabs</h2>
|
||||||
|
<div class="tabs">
|
||||||
|
<div class="tab-headers">
|
||||||
|
<button class="tab-button active" data-tab="tab1">Tab 1</button>
|
||||||
|
<button class="tab-button" data-tab="tab2">Tab 2</button>
|
||||||
|
<button class="tab-button" data-tab="tab3">Tab 3</button>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content">
|
||||||
|
<div id="tab1" class="tab-pane active">
|
||||||
|
<h3>Content for Tab 1</h3>
|
||||||
|
<p>This is the content for the first tab. It can contain any HTML elements.</p>
|
||||||
|
</div>
|
||||||
|
<div id="tab2" class="tab-pane">
|
||||||
|
<h3>Content for Tab 2</h3>
|
||||||
|
<p>This is the content for the second tab. More information here.</p>
|
||||||
|
</div>
|
||||||
|
<div id="tab3" class="tab-pane">
|
||||||
|
<h3>Content for Tab 3</h3>
|
||||||
|
<p>This is the content for the third tab. Even more details.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Slider (Swiper)</h2>
|
||||||
|
<!-- Slider main container -->
|
||||||
|
<div class="swiper">
|
||||||
|
<!-- Additional required wrapper -->
|
||||||
|
<div class="swiper-wrapper">
|
||||||
|
<!-- Slides -->
|
||||||
|
<div class="swiper-slide">Slide 1</div>
|
||||||
|
<div class="swiper-slide">Slide 2</div>
|
||||||
|
<div class="swiper-slide">Slide 3</div>
|
||||||
|
</div>
|
||||||
|
<!-- If we need pagination -->
|
||||||
|
<div class="swiper-pagination"></div>
|
||||||
|
|
||||||
|
<!-- If we need navigation buttons -->
|
||||||
|
<div class="swiper-button-prev"></div>
|
||||||
|
<div class="swiper-button-next"></div>
|
||||||
|
|
||||||
|
<!-- If we need scrollbar -->
|
||||||
|
<div class="swiper-scrollbar"></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Modal (Micromodal)</h2>
|
||||||
|
<button data-micromodal-trigger="modal-1">Open Modal</button>
|
||||||
|
|
||||||
|
<div class="modal micromodal-slide" id="modal-1" aria-hidden="true">
|
||||||
|
<div class="modal__overlay" tabindex="-1" data-micromodal-close>
|
||||||
|
<div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-1-title">
|
||||||
|
<header class="modal__header">
|
||||||
|
<h2 class="modal__title" id="modal-1-title">
|
||||||
|
Micromodal Example
|
||||||
|
</h2>
|
||||||
|
<button class="modal__close" aria-label="Close modal" data-micromodal-close></button>
|
||||||
|
</header>
|
||||||
|
<div class="modal__content" id="modal-1-content">
|
||||||
|
<p>This is a simple modal window powered by Micromodal.</p>
|
||||||
|
<p>You can put any content here.</p>
|
||||||
|
</div>
|
||||||
|
<footer class="modal__footer">
|
||||||
|
<button class="modal__btn" data-micromodal-close aria-label="Close this dialog window">Close</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="bg-dark text-white py-3 mt-5">
|
||||||
|
<div class="container text-center">
|
||||||
|
<p class="mb-0">© 2025 ModernKit. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</footer> <script src="js/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5
modernkit/dist/icons.svg
vendored
Normal file
5
modernkit/dist/icons.svg
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><symbol id="clock" viewBox="0 0 24 24">
|
||||||
|
<path d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM12 20C7.59 20 4 16.41 4 12C4 7.59 7.59 4 12 4C16.41 4 20 7.59 20 12C20 16.41 16.41 20 12 20ZM12.5 7H11V13L16.25 16.15L17 14.92L12.5 12.25V7Z"/>
|
||||||
|
</symbol><symbol id="info" viewBox="0 0 24 24">
|
||||||
|
<path d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM12 20C7.59 20 4 16.41 4 12C4 7.59 7.59 4 12 4C16.41 4 20 7.59 20 12C20 16.41 16.41 20 12 20ZM11 15H13V17H11V15ZM11 7H13V13H11V7Z"/>
|
||||||
|
</symbol></svg>
|
||||||
|
After Width: | Height: | Size: 666 B |
131
modernkit/dist/index.html
vendored
Normal file
131
modernkit/dist/index.html
vendored
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title></title>
|
||||||
|
<link rel="stylesheet" href="main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="display: none;"></div>
|
||||||
|
<header class="bg-dark text-white py-3">
|
||||||
|
<div class="container d-flex justify-content-between align-items-center">
|
||||||
|
<a href="#" class="text-white h4 mb-0">ModernKit</a>
|
||||||
|
<nav>
|
||||||
|
<ul class="list-unstyled d-flex mb-0">
|
||||||
|
<li class="ml-3"><a href="#" class="text-white">Home</a></li>
|
||||||
|
<li class="ml-3"><a href="#" class="text-white">About</a></li>
|
||||||
|
<li class="ml-3"><a href="#" class="text-white">Contact</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main class="container py-5">
|
||||||
|
<h1 class="mb-4"></h1>
|
||||||
|
|
||||||
|
<section class="mb-5">
|
||||||
|
<h2 class="mb-3">Typography</h2>
|
||||||
|
<h1>Heading 1</h1>
|
||||||
|
<h2>Heading 2</h2>
|
||||||
|
<h3>Heading 3</h3>
|
||||||
|
<h4>Heading 4</h4>
|
||||||
|
<h5>Heading 5</h5>
|
||||||
|
<h6>Heading 6</h6>
|
||||||
|
<p>This is a paragraph of text. It demonstrates the default font size, line height, and color. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||||
|
<p class="small">This is a small paragraph of text.</p>
|
||||||
|
<blockquote>
|
||||||
|
<p>"The only way to do great work is to love what you do."</p>
|
||||||
|
<footer class="blockquote-footer">Steve Jobs</footer>
|
||||||
|
</blockquote>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="mb-5">
|
||||||
|
<h2 class="mb-3">Grid System</h2>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-md-6 col-lg-4">
|
||||||
|
<div class="p-3 bg-light border">Column 1</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-6 col-lg-4">
|
||||||
|
<div class="p-3 bg-light border">Column 2</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-6 col-lg-4">
|
||||||
|
<div class="p-3 bg-light border">Column 3</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="p-3 bg-light border">Half Width</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="p-3 bg-light border">Half Width</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="mb-5">
|
||||||
|
<h2 class="mb-3">Buttons</h2>
|
||||||
|
<div class="mb-3">
|
||||||
|
<button type="button" class="btn btn-primary">
|
||||||
|
Primary Button
|
||||||
|
</button> <button type="button" class="btn btn-secondary">
|
||||||
|
Secondary Button
|
||||||
|
</button> <button type="button" class="btn btn-success">
|
||||||
|
Success Button
|
||||||
|
</button> <button type="button" class="btn btn-danger">
|
||||||
|
Danger Button
|
||||||
|
</button> <button type="button" class="btn btn-warning">
|
||||||
|
Warning Button
|
||||||
|
</button> <button type="button" class="btn btn-info">
|
||||||
|
Info Button
|
||||||
|
</button> <button type="button" class="btn btn-light">
|
||||||
|
Light Button
|
||||||
|
</button> <button type="button" class="btn btn-dark">
|
||||||
|
Dark Button
|
||||||
|
</button> </div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="mb-5">
|
||||||
|
<h2 class="mb-3">Utility Classes</h2>
|
||||||
|
<p class="text-primary">This text is primary colored.</p>
|
||||||
|
<p class="text-secondary">This text is secondary colored.</p>
|
||||||
|
<p class="text-success">This text is success colored.</p>
|
||||||
|
<p class="text-danger">This text is danger colored.</p>
|
||||||
|
<p class="text-warning">This text is warning colored.</p>
|
||||||
|
<p class="text-info">This text is info colored.</p>
|
||||||
|
<p class="text-light bg-dark">This text is light colored on dark background.</p>
|
||||||
|
<p class="text-dark">This text is dark colored.</p>
|
||||||
|
|
||||||
|
<div class="p-3 mb-3 bg-primary text-white">Background Primary</div>
|
||||||
|
<div class="p-3 mb-3 bg-secondary text-white">Background Secondary</div>
|
||||||
|
<div class="p-3 mb-3 bg-success text-white">Background Success</div>
|
||||||
|
<div class="p-3 mb-3 bg-danger text-white">Background Danger</div>
|
||||||
|
<div class="p-3 mb-3 bg-warning text-dark">Background Warning</div>
|
||||||
|
<div class="p-3 mb-3 bg-info text-white">Background Info</div>
|
||||||
|
<div class="p-3 mb-3 bg-light text-dark">Background Light</div>
|
||||||
|
<div class="p-3 mb-3 bg-dark text-white">Background Dark</div>
|
||||||
|
|
||||||
|
<p class="mt-5">Margin Top 5</p>
|
||||||
|
<p class="pb-4">Padding Bottom 4</p>
|
||||||
|
<p class="mx-auto d-block" style="width: 200px;">Centered Block</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="mb-5">
|
||||||
|
<h2 class="mb-3">SVG Icons</h2>
|
||||||
|
<p>
|
||||||
|
<svg class="icon" width="24" height="24"><use xlink:href="#clock"></use></svg>
|
||||||
|
Clock Icon
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<svg class="icon" width="24" height="24"><use xlink:href="#info"></use></svg>
|
||||||
|
Info Icon
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="bg-dark text-white py-3 mt-5">
|
||||||
|
<div class="container text-center">
|
||||||
|
<p class="mb-0">© 2025 ModernKit. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</footer> <script src="js/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5
modernkit/dist/svg-sprite.svg
vendored
Normal file
5
modernkit/dist/svg-sprite.svg
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><symbol id="clock" viewBox="0 0 24 24">
|
||||||
|
<path d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM12 20C7.59 20 4 16.41 4 12C4 7.59 7.59 4 12 4C16.41 4 20 7.59 20 12C20 16.41 16.41 20 12 20ZM12.5 7H11V13L16.25 16.15L17 14.92L12.5 12.25V7Z"/>
|
||||||
|
</symbol><symbol id="info" viewBox="0 0 24 24">
|
||||||
|
<path d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM12 20C7.59 20 4 16.41 4 12C4 7.59 7.59 4 12 4C16.41 4 20 7.59 20 12C20 16.41 16.41 20 12 20ZM11 15H13V17H11V15ZM11 7H13V13H11V7Z"/>
|
||||||
|
</symbol></svg>
|
||||||
|
After Width: | Height: | Size: 666 B |
302
modernkit/gulpfile.js
Normal file
302
modernkit/gulpfile.js
Normal file
|
|
@ -0,0 +1,302 @@
|
||||||
|
import gulp from 'gulp';
|
||||||
|
import twig from 'gulp-twig';
|
||||||
|
import * as dartSass from 'sass';
|
||||||
|
import gulpSass from 'gulp-sass';
|
||||||
|
import autoprefixer from 'gulp-autoprefixer';
|
||||||
|
import browserSync from 'browser-sync';
|
||||||
|
import babel from 'gulp-babel';
|
||||||
|
import concat from 'gulp-concat';
|
||||||
|
import uglify from 'gulp-uglify';
|
||||||
|
import imagemin from 'gulp-imagemin';
|
||||||
|
import cleanCss from 'gulp-clean-css';
|
||||||
|
import { deleteAsync } from 'del';
|
||||||
|
import sourcemaps from 'gulp-sourcemaps';
|
||||||
|
import fs from 'fs';
|
||||||
|
import plumber from 'gulp-plumber';
|
||||||
|
import svgstore from 'gulp-svgstore';
|
||||||
|
import cheerio from 'gulp-cheerio';
|
||||||
|
import through2 from 'through2';
|
||||||
|
import data from 'gulp-data';
|
||||||
|
import path from 'path';
|
||||||
|
import webp from 'gulp-webp';
|
||||||
|
import rev from 'gulp-rev';
|
||||||
|
import revReplace from 'gulp-rev-replace';
|
||||||
|
import cached from 'gulp-cached';
|
||||||
|
import remember from 'gulp-remember';
|
||||||
|
import { exec } from 'child_process';
|
||||||
|
import puppeteer from 'puppeteer';
|
||||||
|
import postcss from 'gulp-postcss';
|
||||||
|
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { dirname } from 'path';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
const sass = gulpSass(dartSass);
|
||||||
|
browserSync.create();
|
||||||
|
|
||||||
|
const cleanTask = () => {
|
||||||
|
return deleteAsync(['dist']);
|
||||||
|
};
|
||||||
|
|
||||||
|
const lintJsTask = (cb) => {
|
||||||
|
exec('npx @biomejs/biome check src/js', (err, stdout, stderr) => {
|
||||||
|
console.log(stdout);
|
||||||
|
console.error(stderr);
|
||||||
|
if (err) {
|
||||||
|
console.error('Biome check failed!', err);
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatJsTask = (cb) => {
|
||||||
|
exec('npx @biomejs/biome format --write src/js', (err, stdout, stderr) => {
|
||||||
|
console.log(stdout);
|
||||||
|
console.error(stderr);
|
||||||
|
if (err) {
|
||||||
|
console.error('Biome format failed!', err);
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const svgSpriteTask = (cb) => {
|
||||||
|
let svgContent = '';
|
||||||
|
return gulp.src('src/icons/**/*.svg')
|
||||||
|
.pipe(cached('svgIcons')) // Cache SVG icons
|
||||||
|
.pipe(plumber())
|
||||||
|
.pipe(cheerio({
|
||||||
|
run: function ($) {
|
||||||
|
$('[fill]').removeAttr('fill');
|
||||||
|
$('[stroke]').removeAttr('stroke');
|
||||||
|
$('[style]').removeAttr('style');
|
||||||
|
},
|
||||||
|
parserOptions: { xmlMode: true }
|
||||||
|
}))
|
||||||
|
.pipe(svgstore({
|
||||||
|
inlineSvg: true
|
||||||
|
}))
|
||||||
|
.pipe(remember('svgIcons')) // Remember all SVG icons
|
||||||
|
.pipe(through2.obj(function (file, enc, cb2) {
|
||||||
|
svgContent = file.contents.toString();
|
||||||
|
cb2(null, file);
|
||||||
|
}))
|
||||||
|
.pipe(gulp.dest('dist')) // Still write to dist for consistency
|
||||||
|
.on('end', () => {
|
||||||
|
// Ensure the file is written before calling the callback
|
||||||
|
fs.promises.writeFile('./dist/svg-sprite.svg', svgContent)
|
||||||
|
.then(cb)
|
||||||
|
.catch(cb);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const twigTask = () => {
|
||||||
|
const globalData = JSON.parse(fs.readFileSync('./src/data/global.json'));
|
||||||
|
const svgSprite = fs.readFileSync('./dist/svg-sprite.svg', 'utf8');
|
||||||
|
|
||||||
|
return gulp.src('src/templates/*.twig') // Process only top-level twig files
|
||||||
|
.pipe(cached('twigTemplates')) // Cache twig templates
|
||||||
|
.pipe(plumber())
|
||||||
|
.pipe(data(function(file) {
|
||||||
|
const fileName = path.basename(file.path, '.twig');
|
||||||
|
const dataFilePath = `./src/data/${fileName}.json`;
|
||||||
|
let pageData = {};
|
||||||
|
if (fs.existsSync(dataFilePath)) {
|
||||||
|
pageData = JSON.parse(fs.readFileSync(dataFilePath));
|
||||||
|
}
|
||||||
|
return { ...globalData, ...pageData, svgSprite: svgSprite };
|
||||||
|
}))
|
||||||
|
.pipe(twig())
|
||||||
|
.pipe(remember('twigTemplates')) // Remember all twig templates
|
||||||
|
.pipe(gulp.dest('dist'))
|
||||||
|
.pipe(browserSync.stream());
|
||||||
|
};
|
||||||
|
|
||||||
|
const scssTask = () => {
|
||||||
|
return gulp.src('src/scss/main.scss')
|
||||||
|
.pipe(cached('scss')) // Cache SCSS files
|
||||||
|
.pipe(plumber())
|
||||||
|
.pipe(sourcemaps.init())
|
||||||
|
.pipe(sass().on('error', sass.logError))
|
||||||
|
.pipe(autoprefixer())
|
||||||
|
.pipe(sourcemaps.write('.'))
|
||||||
|
.pipe(remember('scss')) // Remember all SCSS files
|
||||||
|
.pipe(gulp.dest('dist'))
|
||||||
|
.pipe(browserSync.stream());
|
||||||
|
};
|
||||||
|
|
||||||
|
const scssProdTask = () => {
|
||||||
|
return gulp.src('src/scss/main.scss')
|
||||||
|
.pipe(cached('scssProd')) // Cache SCSS files for prod
|
||||||
|
.pipe(plumber())
|
||||||
|
.pipe(sass().on('error', sass.logError))
|
||||||
|
.pipe(autoprefixer())
|
||||||
|
|
||||||
|
.pipe(cleanCss())
|
||||||
|
.pipe(remember('scssProd')) // Remember all SCSS files for prod
|
||||||
|
.pipe(gulp.dest('dist'));
|
||||||
|
};
|
||||||
|
|
||||||
|
const jsDevTask = () => {
|
||||||
|
return gulp.src('src/js/**/*.js')
|
||||||
|
.pipe(cached('jsDev')) // Cache JS files for dev
|
||||||
|
.pipe(plumber())
|
||||||
|
.pipe(babel({
|
||||||
|
presets: ['@babel/env']
|
||||||
|
}))
|
||||||
|
.pipe(concat('main.js'))
|
||||||
|
.pipe(sourcemaps.write('.'))
|
||||||
|
.pipe(remember('jsDev')) // Remember all JS files for dev
|
||||||
|
.pipe(gulp.dest('dist/js'))
|
||||||
|
.pipe(browserSync.stream());
|
||||||
|
};
|
||||||
|
|
||||||
|
const jsMinTask = () => {
|
||||||
|
return gulp.src(['src/js/**/*.js', '!src/js/**/*.test.js'])
|
||||||
|
.pipe(cached('jsProd')) // Cache JS files for prod
|
||||||
|
.pipe(plumber())
|
||||||
|
.pipe(sourcemaps.init())
|
||||||
|
.pipe(babel({
|
||||||
|
presets: ['@babel/env']
|
||||||
|
}))
|
||||||
|
.pipe(concat('main.min.js'))
|
||||||
|
.pipe(uglify())
|
||||||
|
.pipe(sourcemaps.write('.'))
|
||||||
|
.pipe(remember('jsProd')) // Remember all JS files for prod
|
||||||
|
.pipe(gulp.dest('dist/js'))
|
||||||
|
.pipe(browserSync.stream());
|
||||||
|
};
|
||||||
|
|
||||||
|
const jsTask = () => {
|
||||||
|
return gulp.src(['src/js/**/*.js', '!src/js/**/*.test.js'])
|
||||||
|
.pipe(cached('jsProd')) // Cache JS files for prod
|
||||||
|
.pipe(plumber())
|
||||||
|
.pipe(sourcemaps.init())
|
||||||
|
.pipe(babel({
|
||||||
|
presets: ['@babel/env']
|
||||||
|
}))
|
||||||
|
.pipe(concat('main.js'))
|
||||||
|
.pipe(sourcemaps.write('.'))
|
||||||
|
.pipe(remember('jsProd')) // Remember all JS files for prod
|
||||||
|
.pipe(gulp.dest('dist/js'))
|
||||||
|
.pipe(browserSync.stream());
|
||||||
|
};
|
||||||
|
|
||||||
|
const imagesTask = () => {
|
||||||
|
return gulp.src('src/images/*')
|
||||||
|
.pipe(cached('images')) // Cache images
|
||||||
|
.pipe(plumber())
|
||||||
|
.pipe(imagemin())
|
||||||
|
.pipe(remember('images')) // Remember all images
|
||||||
|
.pipe(gulp.dest('dist/images'))
|
||||||
|
.pipe(browserSync.stream());
|
||||||
|
};
|
||||||
|
|
||||||
|
const imagesProdTask = () => {
|
||||||
|
return gulp.src('src/images/*')
|
||||||
|
.pipe(cached('imagesProd')) // Cache images for prod
|
||||||
|
.pipe(plumber())
|
||||||
|
.pipe(imagemin())
|
||||||
|
.pipe(gulp.dest('dist/images'))
|
||||||
|
.pipe(webp())
|
||||||
|
.pipe(gulp.dest('dist/images'));
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyAssetsTask = () => {
|
||||||
|
return gulp.src('src/assets/**/*')
|
||||||
|
.pipe(cached('assets')) // Cache assets
|
||||||
|
.pipe(plumber())
|
||||||
|
.pipe(remember('assets')) // Remember all assets
|
||||||
|
.pipe(gulp.dest('dist/assets'))
|
||||||
|
.pipe(browserSync.stream());
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyFaviconTask = () => {
|
||||||
|
return gulp.src('src/favicon.ico')
|
||||||
|
.pipe(cached('favicon')) // Cache favicon
|
||||||
|
.pipe(plumber())
|
||||||
|
.pipe(remember('favicon')) // Remember favicon
|
||||||
|
.pipe(gulp.dest('dist'))
|
||||||
|
.pipe(browserSync.stream());
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyFontsTask = () => {
|
||||||
|
return gulp.src('src/fonts/**/*')
|
||||||
|
.pipe(cached('fonts')) // Cache fonts
|
||||||
|
.pipe(plumber())
|
||||||
|
.pipe(remember('fonts')) // Remember all fonts
|
||||||
|
.pipe(gulp.dest('dist/fonts'))
|
||||||
|
.pipe(browserSync.stream());
|
||||||
|
};
|
||||||
|
|
||||||
|
const revTask = () => {
|
||||||
|
return gulp.src(['dist/**/*.css', 'dist/**/*.js'], { base: 'dist' })
|
||||||
|
.pipe(gulp.dest('dist')) // write original assets to dist
|
||||||
|
.pipe(rev())
|
||||||
|
.pipe(gulp.dest('dist')) // write rev'd assets to dist
|
||||||
|
.pipe(rev.manifest())
|
||||||
|
.pipe(gulp.dest('dist')); // write manifest to dist
|
||||||
|
};
|
||||||
|
|
||||||
|
const revReplaceTask = () => {
|
||||||
|
const manifest = gulp.src('dist/rev-manifest.json');
|
||||||
|
|
||||||
|
return gulp.src('dist/**/*.html')
|
||||||
|
.pipe(revReplace({ manifest: manifest }))
|
||||||
|
.pipe(gulp.dest('dist'));
|
||||||
|
};
|
||||||
|
|
||||||
|
const screenshotsTask = async () => {
|
||||||
|
const browser = await puppeteer.launch();
|
||||||
|
const page = await browser.newPage();
|
||||||
|
const resolutions = [
|
||||||
|
{ width: 1920, height: 1080, name: 'desktop' },
|
||||||
|
{ width: 1366, height: 768, name: 'laptop' },
|
||||||
|
{ width: 768, height: 1024, name: 'tablet' },
|
||||||
|
{ width: 375, height: 667, name: 'mobile' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Ensure the screenshots directory exists
|
||||||
|
if (!fs.existsSync('dist/screenshots')) {
|
||||||
|
fs.mkdirSync('dist/screenshots');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const res of resolutions) {
|
||||||
|
await page.setViewport({ width: res.width, height: res.height });
|
||||||
|
await page.goto('file://' + path.resolve(__dirname, 'dist/index.html'));
|
||||||
|
await page.screenshot({ path: `dist/screenshots/screenshot-${res.name}-${res.width}x${res.height}.png` });
|
||||||
|
console.log(`Screenshot captured for ${res.name} (${res.width}x${res.height})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await browser.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const watchTask = () => {
|
||||||
|
browserSync.init({
|
||||||
|
server: {
|
||||||
|
baseDir: './dist'
|
||||||
|
},
|
||||||
|
https: true // Enable HTTPS
|
||||||
|
});
|
||||||
|
gulp.watch('src/templates/**/*.twig', twigTask);
|
||||||
|
gulp.watch('src/scss/**/*.scss', scssTask);
|
||||||
|
gulp.watch('src/js/**/*.js', gulp.series(lintJsTask, jsDevTask));
|
||||||
|
gulp.watch('src/images/*', imagesTask);
|
||||||
|
gulp.watch('src/data/**/*.json', twigTask);
|
||||||
|
gulp.watch('src/assets/**/*', copyAssetsTask);
|
||||||
|
gulp.watch('src/favicon.ico', copyFaviconTask);
|
||||||
|
gulp.watch('src/icons/**/*.svg', gulp.series(svgSpriteTask, twigTask)); // Watch for changes in SVG icons
|
||||||
|
gulp.watch('src/fonts/**/*', copyFontsTask); // Watch for changes in fonts
|
||||||
|
};
|
||||||
|
|
||||||
|
const devBuild = gulp.series(cleanTask, svgSpriteTask, gulp.parallel(scssTask, jsDevTask, imagesTask, copyAssetsTask, copyFaviconTask, copyFontsTask), twigTask);
|
||||||
|
|
||||||
|
export const build = gulp.series(cleanTask, lintJsTask, svgSpriteTask, twigTask, gulp.parallel(scssTask, jsDevTask, imagesTask, copyAssetsTask, copyFaviconTask, copyFontsTask));
|
||||||
|
export const buildProd = gulp.series(cleanTask, lintJsTask, svgSpriteTask, twigTask, gulp.parallel(scssProdTask, jsTask, jsMinTask, imagesProdTask, copyAssetsTask, copyFaviconTask, copyFontsTask), revTask, revReplaceTask, screenshotsTask);
|
||||||
|
export const screenshots = screenshotsTask;
|
||||||
|
export default gulp.series(devBuild, watchTask);
|
||||||
13689
modernkit/package-lock.json
generated
Normal file
13689
modernkit/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
51
modernkit/package.json
Normal file
51
modernkit/package.json
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
{
|
||||||
|
"name": "modernkit",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "gulp",
|
||||||
|
"build": "gulp build",
|
||||||
|
"build:prod": "gulp buildProd"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "module",
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.27.7",
|
||||||
|
"@babel/preset-env": "^7.27.2",
|
||||||
|
"@fullhuman/postcss-purgecss": "^7.0.2",
|
||||||
|
"browser-sync": "^3.0.4",
|
||||||
|
"del": "^8.0.0",
|
||||||
|
"gulp": "^5.0.1",
|
||||||
|
"gulp-autoprefixer": "^9.0.0",
|
||||||
|
"gulp-babel": "^8.0.0",
|
||||||
|
"gulp-cached": "^1.1.1",
|
||||||
|
"gulp-clean-css": "^4.3.0",
|
||||||
|
"gulp-concat": "^2.6.1",
|
||||||
|
"gulp-data": "^1.3.1",
|
||||||
|
"gulp-imagemin": "^9.1.0",
|
||||||
|
"gulp-plumber": "^1.2.1",
|
||||||
|
"gulp-postcss": "^10.0.0",
|
||||||
|
"gulp-remember": "^1.0.1",
|
||||||
|
"gulp-rev": "^10.0.0",
|
||||||
|
"gulp-rev-replace": "^0.4.4",
|
||||||
|
"gulp-sass": "^6.0.1",
|
||||||
|
"gulp-sourcemaps": "^3.0.0",
|
||||||
|
"gulp-svgstore": "^9.0.0",
|
||||||
|
"gulp-twig": "^1.2.0",
|
||||||
|
"gulp-uglify": "^3.0.2",
|
||||||
|
"gulp-webp": "^5.0.0",
|
||||||
|
"postcss": "^8.5.6",
|
||||||
|
"sass": "^1.89.2",
|
||||||
|
"through2": "^4.0.2",
|
||||||
|
"twig": "^1.17.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"gulp-cheerio": "^1.0.0",
|
||||||
|
"micromodal": "^0.6.1",
|
||||||
|
"puppeteer": "^24.10.2",
|
||||||
|
"swiper": "^11.2.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
modernkit/src/assets/test.txt
Normal file
1
modernkit/src/assets/test.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
This is a test asset file.
|
||||||
4
modernkit/src/data/about.json
Normal file
4
modernkit/src/data/about.json
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"pageTitle": "About Us",
|
||||||
|
"aboutContent": "This is the content for the about page. We are a team dedicated to building amazing things."
|
||||||
|
}
|
||||||
5
modernkit/src/data/global.json
Normal file
5
modernkit/src/data/global.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"title": "ModernKit UI Kit",
|
||||||
|
"welcomeMessage": "Welcome to the ModernKit UI Kit Showcase!",
|
||||||
|
"year": 2025
|
||||||
|
}
|
||||||
1
modernkit/src/favicon.ico
Normal file
1
modernkit/src/favicon.ico
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
This is a dummy favicon file.
|
||||||
1
modernkit/src/fonts/test.ttf
Normal file
1
modernkit/src/fonts/test.ttf
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
This is a dummy font file.
|
||||||
3
modernkit/src/icons/clock.svg
Normal file
3
modernkit/src/icons/clock.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM12 20C7.59 20 4 16.41 4 12C4 7.59 7.59 4 12 4C16.41 4 20 7.59 20 12C20 16.41 16.41 20 12 20ZM12.5 7H11V13L16.25 16.15L17 14.92L12.5 12.25V7Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 346 B |
3
modernkit/src/icons/info.svg
Normal file
3
modernkit/src/icons/info.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM12 20C7.59 20 4 16.41 4 12C4 7.59 7.59 4 12 4C16.41 4 20 7.59 20 12C20 16.41 16.41 20 12 20ZM11 15H13V17H11V15ZM11 7H13V13H11V7Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 334 B |
59
modernkit/src/js/main.js
Normal file
59
modernkit/src/js/main.js
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import Swiper from "swiper";
|
||||||
|
import { Navigation, Pagination } from "swiper/modules";
|
||||||
|
import "swiper/css";
|
||||||
|
import "swiper/css/navigation";
|
||||||
|
import "swiper/css/pagination";
|
||||||
|
import MicroModal from "micromodal";
|
||||||
|
|
||||||
|
const button = document.querySelector("button");
|
||||||
|
|
||||||
|
if (button) {
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
alert("Button clicked!");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tabs functionality
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const tabButtons = document.querySelectorAll(".tab-button");
|
||||||
|
const tabPanes = document.querySelectorAll(".tab-pane");
|
||||||
|
|
||||||
|
tabButtons.forEach((button) => {
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
const targetTab = button.dataset.tab;
|
||||||
|
|
||||||
|
tabButtons.forEach((btn) => btn.classList.remove("active"));
|
||||||
|
button.classList.add("active");
|
||||||
|
|
||||||
|
tabPanes.forEach((pane) => {
|
||||||
|
if (pane.id === targetTab) {
|
||||||
|
pane.classList.add("active");
|
||||||
|
} else {
|
||||||
|
pane.classList.remove("active");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize first tab as active
|
||||||
|
if (tabButtons.length > 0) {
|
||||||
|
tabButtons[0].click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Swiper initialization
|
||||||
|
const _swiper = new Swiper(".swiper", {
|
||||||
|
modules: [Navigation, Pagination],
|
||||||
|
loop: true,
|
||||||
|
pagination: {
|
||||||
|
el: ".swiper-pagination",
|
||||||
|
clickable: true,
|
||||||
|
},
|
||||||
|
navigation: {
|
||||||
|
nextEl: ".swiper-button-next",
|
||||||
|
prevEl: ".swiper-button-prev",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Micromodal initialization
|
||||||
|
MicroModal.init();
|
||||||
41
modernkit/src/scss/_base.scss
Normal file
41
modernkit/src/scss/_base.scss
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
@use 'variables';
|
||||||
|
@use "sass:color";
|
||||||
|
|
||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 62.5%; /* 1rem = 10px */
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
color: #333;
|
||||||
|
font-size: 1.6rem; /* Default font size for body, equivalent to 16px */
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: variables.$primary-color;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: color.adjust(variables.$primary-color, $lightness: -10%);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
89
modernkit/src/scss/_components.scss
Normal file
89
modernkit/src/scss/_components.scss
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
// Tabs
|
||||||
|
.tabs {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-headers {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-button {
|
||||||
|
padding: 10px 15px;
|
||||||
|
border: none;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
border-bottom: 2px solid #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-pane {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swiper (Slider)
|
||||||
|
.swiper {
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swiper-slide {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Micromodal (Modal)
|
||||||
|
.modal {
|
||||||
|
/* styles for the outer modal wrapper */
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__overlay {
|
||||||
|
/* styles for the modal overlay */
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__container {
|
||||||
|
/* styles for the modal container */
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__header {
|
||||||
|
/* styles for the modal header */
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__title {
|
||||||
|
/* styles for the modal title */
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__close {
|
||||||
|
/* styles for the close button */
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__content {
|
||||||
|
/* styles for the modal content */
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__footer {
|
||||||
|
/* styles for the modal footer */
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__btn {
|
||||||
|
/* styles for modal buttons */
|
||||||
|
}
|
||||||
49
modernkit/src/scss/_grid.scss
Normal file
49
modernkit/src/scss/_grid.scss
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
@use 'variables';
|
||||||
|
@use "sass:math";
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
padding-right: math.div(variables.$grid-gutter-width, 2);
|
||||||
|
padding-left: math.div(variables.$grid-gutter-width, 2);
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
|
||||||
|
@each $breakpoint, $width in variables.$grid-breakpoints {
|
||||||
|
@if $width > 0 {
|
||||||
|
@media (min-width: $width) {
|
||||||
|
max-width: $width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-right: math.div(variables.$grid-gutter-width, -2);
|
||||||
|
margin-left: math.div(variables.$grid-gutter-width, -2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@for $i from 1 through variables.$grid-columns {
|
||||||
|
.col-#{$i} {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: math.div(100%, variables.$grid-columns) * $i;
|
||||||
|
padding-right: math.div(variables.$grid-gutter-width, 2);
|
||||||
|
padding-left: math.div(variables.$grid-gutter-width, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@each $breakpoint, $width in variables.$grid-breakpoints {
|
||||||
|
@if $width > 0 {
|
||||||
|
@media (min-width: $width) {
|
||||||
|
@for $i from 1 through variables.$grid-columns {
|
||||||
|
.col-#{$breakpoint}-#{$i} {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: math.div(100%, variables.$grid-columns) * $i;
|
||||||
|
padding-right: math.div(variables.$grid-gutter-width, 2);
|
||||||
|
padding-left: math.div(variables.$grid-gutter-width, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
61
modernkit/src/scss/_typography.scss
Normal file
61
modernkit/src/scss/_typography.scss
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
@use 'variables';
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: (variables.$spacer * .5);
|
||||||
|
font-family: variables.$font-family-base;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: variables.$body-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: variables.$h1-font-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: variables.$h2-font-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: variables.$h3-font-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: variables.$h4-font-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: variables.$h5-font-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: variables.$h6-font-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: variables.$spacer;
|
||||||
|
}
|
||||||
|
|
||||||
|
small,
|
||||||
|
.small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
mark,
|
||||||
|
.mark {
|
||||||
|
padding: .2em;
|
||||||
|
background-color: #fcf8e3;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 0 0 variables.$spacer;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
padding-left: 2rem;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: variables.$spacer;
|
||||||
|
}
|
||||||
109
modernkit/src/scss/_utilities.scss
Normal file
109
modernkit/src/scss/_utilities.scss
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
@use 'variables';
|
||||||
|
|
||||||
|
// Margin and Padding
|
||||||
|
@each $prop, $abbrev in (margin, m), (padding, p) {
|
||||||
|
@each $size, $length in variables.$spacers {
|
||||||
|
.#{$abbrev}-#{$size} {
|
||||||
|
#{$prop}: #{$length} !important;
|
||||||
|
}
|
||||||
|
.#{$abbrev}t-#{$size} {
|
||||||
|
#{$prop}-top: #{$length} !important;
|
||||||
|
}
|
||||||
|
.#{$abbrev}b-#{$size} {
|
||||||
|
#{$prop}-bottom: #{$length} !important;
|
||||||
|
}
|
||||||
|
.#{$abbrev}l-#{$size} {
|
||||||
|
#{$prop}-left: #{$length} !important;
|
||||||
|
}
|
||||||
|
.#{$abbrev}r-#{$size} {
|
||||||
|
#{$prop}-right: #{$length} !important;
|
||||||
|
}
|
||||||
|
.#{$abbrev}x-#{$size} {
|
||||||
|
#{$prop}-left: #{$length} !important;
|
||||||
|
#{$prop}-right: #{$length} !important;
|
||||||
|
}
|
||||||
|
.#{$abbrev}y-#{$size} {
|
||||||
|
#{$prop}-top: #{$length} !important;
|
||||||
|
#{$prop}-bottom: #{$length} !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text alignment
|
||||||
|
.text-left {
|
||||||
|
text-align: left !important;
|
||||||
|
}
|
||||||
|
.text-right {
|
||||||
|
text-align: right !important;
|
||||||
|
}
|
||||||
|
.text-center {
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display
|
||||||
|
.d-block {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
.d-inline-block {
|
||||||
|
display: inline-block !important;
|
||||||
|
}
|
||||||
|
.d-flex {
|
||||||
|
display: flex !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
.text-primary {
|
||||||
|
color: variables.$primary-color !important;
|
||||||
|
}
|
||||||
|
.text-secondary {
|
||||||
|
color: variables.$secondary-color !important;
|
||||||
|
}
|
||||||
|
.text-success {
|
||||||
|
color: variables.$success-color !important;
|
||||||
|
}
|
||||||
|
.text-danger {
|
||||||
|
color: variables.$danger-color !important;
|
||||||
|
}
|
||||||
|
.text-warning {
|
||||||
|
color: variables.$warning-color !important;
|
||||||
|
}
|
||||||
|
.text-info {
|
||||||
|
color: variables.$info-color !important;
|
||||||
|
}
|
||||||
|
.text-light {
|
||||||
|
color: variables.$light-color !important;
|
||||||
|
}
|
||||||
|
.text-dark {
|
||||||
|
color: variables.$dark-color !important;
|
||||||
|
}
|
||||||
|
.text-white {
|
||||||
|
color: variables.$white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-primary {
|
||||||
|
background-color: variables.$primary-color !important;
|
||||||
|
}
|
||||||
|
.bg-secondary {
|
||||||
|
background-color: variables.$secondary-color !important;
|
||||||
|
}
|
||||||
|
.bg-success {
|
||||||
|
background-color: variables.$success-color !important;
|
||||||
|
}
|
||||||
|
.bg-danger {
|
||||||
|
background-color: variables.$danger-color !important;
|
||||||
|
}
|
||||||
|
.bg-warning {
|
||||||
|
background-color: variables.$warning-color !important;
|
||||||
|
}
|
||||||
|
.bg-info {
|
||||||
|
background-color: variables.$info-color !important;
|
||||||
|
}
|
||||||
|
.bg-light {
|
||||||
|
background-color: variables.$light-color !important;
|
||||||
|
}
|
||||||
|
.bg-dark {
|
||||||
|
background-color: variables.$dark-color !important;
|
||||||
|
}
|
||||||
|
.bg-white {
|
||||||
|
background-color: variables.$white !important;
|
||||||
|
}
|
||||||
48
modernkit/src/scss/_variables.scss
Normal file
48
modernkit/src/scss/_variables.scss
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
// Colors
|
||||||
|
$primary-color: #007bff;
|
||||||
|
$secondary-color: #6c757d;
|
||||||
|
$success-color: #28a745;
|
||||||
|
$danger-color: #dc3545;
|
||||||
|
$warning-color: #ffc107;
|
||||||
|
$info-color: #17a2b8;
|
||||||
|
$light-color: #f8f9fa;
|
||||||
|
$dark-color: #343a40;
|
||||||
|
$body-color: #212529;
|
||||||
|
$text-color: #333;
|
||||||
|
$white: #fff;
|
||||||
|
|
||||||
|
// Typography
|
||||||
|
$font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
|
$font-size-base: 1rem; // 16px
|
||||||
|
$line-height-base: 1.5;
|
||||||
|
|
||||||
|
$h1-font-size: 2.5rem;
|
||||||
|
$h2-font-size: 2rem;
|
||||||
|
$h3-font-size: 1.75rem;
|
||||||
|
$h4-font-size: 1.5rem;
|
||||||
|
$h5-font-size: 1.25rem;
|
||||||
|
$h6-font-size: 1rem;
|
||||||
|
|
||||||
|
// Spacing
|
||||||
|
$spacer: 1rem;
|
||||||
|
$spacers: (
|
||||||
|
0: 0,
|
||||||
|
1: ($spacer * .25), // 4px
|
||||||
|
2: ($spacer * .5), // 8px
|
||||||
|
3: $spacer, // 16px
|
||||||
|
4: ($spacer * 1.5), // 24px
|
||||||
|
5: ($spacer * 3) // 48px
|
||||||
|
);
|
||||||
|
|
||||||
|
// Breakpoints
|
||||||
|
$grid-breakpoints: (
|
||||||
|
xs: 0,
|
||||||
|
sm: 576px,
|
||||||
|
md: 768px,
|
||||||
|
lg: 992px,
|
||||||
|
xl: 1200px
|
||||||
|
);
|
||||||
|
|
||||||
|
// Grid
|
||||||
|
$grid-columns: 12;
|
||||||
|
$grid-gutter-width: 30px;
|
||||||
6
modernkit/src/scss/main.scss
Normal file
6
modernkit/src/scss/main.scss
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
@use 'variables';
|
||||||
|
@use 'base';
|
||||||
|
@use 'typography';
|
||||||
|
@use 'grid';
|
||||||
|
@use 'utilities';
|
||||||
|
@use 'components';
|
||||||
21
modernkit/src/templates/about.twig
Normal file
21
modernkit/src/templates/about.twig
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{ pageTitle }} - {{ global.title }}</title>
|
||||||
|
<link rel="stylesheet" href="main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="display: none;">{{ svgSprite|raw }}</div>
|
||||||
|
{% include 'components/header.twig' %}
|
||||||
|
|
||||||
|
<main class="container py-5">
|
||||||
|
<h1 class="mb-4">{{ pageTitle }}</h1>
|
||||||
|
<p>{{ aboutContent }}</p>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{% include 'components/footer.twig' %}
|
||||||
|
<script src="main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
20
modernkit/src/templates/base.twig
Normal file
20
modernkit/src/templates/base.twig
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{ global.title }}</title>
|
||||||
|
<link rel="stylesheet" href="main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="display: none;">{{ svgSprite|raw }}</div>
|
||||||
|
{% include 'components/header.twig' %}
|
||||||
|
|
||||||
|
<main class="container py-5">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{% include 'components/footer.twig' %}
|
||||||
|
<script src="js/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
78
modernkit/src/templates/components-showcase.twig
Normal file
78
modernkit/src/templates/components-showcase.twig
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
{% extends "base.twig" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>UI Components Showcase</h1>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Tabs</h2>
|
||||||
|
<div class="tabs">
|
||||||
|
<div class="tab-headers">
|
||||||
|
<button class="tab-button active" data-tab="tab1">Tab 1</button>
|
||||||
|
<button class="tab-button" data-tab="tab2">Tab 2</button>
|
||||||
|
<button class="tab-button" data-tab="tab3">Tab 3</button>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content">
|
||||||
|
<div id="tab1" class="tab-pane active">
|
||||||
|
<h3>Content for Tab 1</h3>
|
||||||
|
<p>This is the content for the first tab. It can contain any HTML elements.</p>
|
||||||
|
</div>
|
||||||
|
<div id="tab2" class="tab-pane">
|
||||||
|
<h3>Content for Tab 2</h3>
|
||||||
|
<p>This is the content for the second tab. More information here.</p>
|
||||||
|
</div>
|
||||||
|
<div id="tab3" class="tab-pane">
|
||||||
|
<h3>Content for Tab 3</h3>
|
||||||
|
<p>This is the content for the third tab. Even more details.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Slider (Swiper)</h2>
|
||||||
|
<!-- Slider main container -->
|
||||||
|
<div class="swiper">
|
||||||
|
<!-- Additional required wrapper -->
|
||||||
|
<div class="swiper-wrapper">
|
||||||
|
<!-- Slides -->
|
||||||
|
<div class="swiper-slide">Slide 1</div>
|
||||||
|
<div class="swiper-slide">Slide 2</div>
|
||||||
|
<div class="swiper-slide">Slide 3</div>
|
||||||
|
</div>
|
||||||
|
<!-- If we need pagination -->
|
||||||
|
<div class="swiper-pagination"></div>
|
||||||
|
|
||||||
|
<!-- If we need navigation buttons -->
|
||||||
|
<div class="swiper-button-prev"></div>
|
||||||
|
<div class="swiper-button-next"></div>
|
||||||
|
|
||||||
|
<!-- If we need scrollbar -->
|
||||||
|
<div class="swiper-scrollbar"></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Modal (Micromodal)</h2>
|
||||||
|
<button data-micromodal-trigger="modal-1">Open Modal</button>
|
||||||
|
|
||||||
|
<div class="modal micromodal-slide" id="modal-1" aria-hidden="true">
|
||||||
|
<div class="modal__overlay" tabindex="-1" data-micromodal-close>
|
||||||
|
<div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-1-title">
|
||||||
|
<header class="modal__header">
|
||||||
|
<h2 class="modal__title" id="modal-1-title">
|
||||||
|
Micromodal Example
|
||||||
|
</h2>
|
||||||
|
<button class="modal__close" aria-label="Close modal" data-micromodal-close></button>
|
||||||
|
</header>
|
||||||
|
<div class="modal__content" id="modal-1-content">
|
||||||
|
<p>This is a simple modal window powered by Micromodal.</p>
|
||||||
|
<p>You can put any content here.</p>
|
||||||
|
</div>
|
||||||
|
<footer class="modal__footer">
|
||||||
|
<button class="modal__btn" data-micromodal-close aria-label="Close this dialog window">Close</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
3
modernkit/src/templates/components/button.twig
Normal file
3
modernkit/src/templates/components/button.twig
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<button type="button" class="btn {{ class }}">
|
||||||
|
{{ text }}
|
||||||
|
</button>
|
||||||
5
modernkit/src/templates/components/footer.twig
Normal file
5
modernkit/src/templates/components/footer.twig
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<footer class="bg-dark text-white py-3 mt-5">
|
||||||
|
<div class="container text-center">
|
||||||
|
<p class="mb-0">© 2025 ModernKit. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
12
modernkit/src/templates/components/header.twig
Normal file
12
modernkit/src/templates/components/header.twig
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<header class="bg-dark text-white py-3">
|
||||||
|
<div class="container d-flex justify-content-between align-items-center">
|
||||||
|
<a href="#" class="text-white h4 mb-0">ModernKit</a>
|
||||||
|
<nav>
|
||||||
|
<ul class="list-unstyled d-flex mb-0">
|
||||||
|
<li class="ml-3"><a href="#" class="text-white">Home</a></li>
|
||||||
|
<li class="ml-3"><a href="#" class="text-white">About</a></li>
|
||||||
|
<li class="ml-3"><a href="#" class="text-white">Contact</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
95
modernkit/src/templates/index.twig
Normal file
95
modernkit/src/templates/index.twig
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
{% extends "base.twig" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="mb-4">{{ global.welcomeMessage }}</h1>
|
||||||
|
|
||||||
|
<section class="mb-5">
|
||||||
|
<h2 class="mb-3">Typography</h2>
|
||||||
|
<h1>Heading 1</h1>
|
||||||
|
<h2>Heading 2</h2>
|
||||||
|
<h3>Heading 3</h3>
|
||||||
|
<h4>Heading 4</h4>
|
||||||
|
<h5>Heading 5</h5>
|
||||||
|
<h6>Heading 6</h6>
|
||||||
|
<p>This is a paragraph of text. It demonstrates the default font size, line height, and color. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||||
|
<p class="small">This is a small paragraph of text.</p>
|
||||||
|
<blockquote>
|
||||||
|
<p>"The only way to do great work is to love what you do."</p>
|
||||||
|
<footer class="blockquote-footer">Steve Jobs</footer>
|
||||||
|
</blockquote>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="mb-5">
|
||||||
|
<h2 class="mb-3">Grid System</h2>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-md-6 col-lg-4">
|
||||||
|
<div class="p-3 bg-light border">Column 1</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-6 col-lg-4">
|
||||||
|
<div class="p-3 bg-light border">Column 2</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-6 col-lg-4">
|
||||||
|
<div class="p-3 bg-light border">Column 3</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="p-3 bg-light border">Half Width</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="p-3 bg-light border">Half Width</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="mb-5">
|
||||||
|
<h2 class="mb-3">Buttons</h2>
|
||||||
|
<div class="mb-3">
|
||||||
|
{% include 'components/button.twig' with {text: 'Primary Button', class: 'btn-primary'} %}
|
||||||
|
{% include 'components/button.twig' with {text: 'Secondary Button', class: 'btn-secondary'} %}
|
||||||
|
{% include 'components/button.twig' with {text: 'Success Button', class: 'btn-success'} %}
|
||||||
|
{% include 'components/button.twig' with {text: 'Danger Button', class: 'btn-danger'} %}
|
||||||
|
{% include 'components/button.twig' with {text: 'Warning Button', class: 'btn-warning'} %}
|
||||||
|
{% include 'components/button.twig' with {text: 'Info Button', class: 'btn-info'} %}
|
||||||
|
{% include 'components/button.twig' with {text: 'Light Button', class: 'btn-light'} %}
|
||||||
|
{% include 'components/button.twig' with {text: 'Dark Button', class: 'btn-dark'} %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="mb-5">
|
||||||
|
<h2 class="mb-3">Utility Classes</h2>
|
||||||
|
<p class="text-primary">This text is primary colored.</p>
|
||||||
|
<p class="text-secondary">This text is secondary colored.</p>
|
||||||
|
<p class="text-success">This text is success colored.</p>
|
||||||
|
<p class="text-danger">This text is danger colored.</p>
|
||||||
|
<p class="text-warning">This text is warning colored.</p>
|
||||||
|
<p class="text-info">This text is info colored.</p>
|
||||||
|
<p class="text-light bg-dark">This text is light colored on dark background.</p>
|
||||||
|
<p class="text-dark">This text is dark colored.</p>
|
||||||
|
|
||||||
|
<div class="p-3 mb-3 bg-primary text-white">Background Primary</div>
|
||||||
|
<div class="p-3 mb-3 bg-secondary text-white">Background Secondary</div>
|
||||||
|
<div class="p-3 mb-3 bg-success text-white">Background Success</div>
|
||||||
|
<div class="p-3 mb-3 bg-danger text-white">Background Danger</div>
|
||||||
|
<div class="p-3 mb-3 bg-warning text-dark">Background Warning</div>
|
||||||
|
<div class="p-3 mb-3 bg-info text-white">Background Info</div>
|
||||||
|
<div class="p-3 mb-3 bg-light text-dark">Background Light</div>
|
||||||
|
<div class="p-3 mb-3 bg-dark text-white">Background Dark</div>
|
||||||
|
|
||||||
|
<p class="mt-5">Margin Top 5</p>
|
||||||
|
<p class="pb-4">Padding Bottom 4</p>
|
||||||
|
<p class="mx-auto d-block" style="width: 200px;">Centered Block</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="mb-5">
|
||||||
|
<h2 class="mb-3">SVG Icons</h2>
|
||||||
|
<p>
|
||||||
|
<svg class="icon" width="24" height="24"><use xlink:href="#clock"></use></svg>
|
||||||
|
Clock Icon
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<svg class="icon" width="24" height="24"><use xlink:href="#info"></use></svg>
|
||||||
|
Info Icon
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
15
vite-templates/._npmrc
Normal file
15
vite-templates/._npmrc
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Разрешаем выполнение скриптов сборки для необходимых пакетов
|
||||||
|
enable-pre-post-scripts=true
|
||||||
|
|
||||||
|
# Разрешаем скрипты для пакетов оптимизации изображений
|
||||||
|
@parcel/watcher:registry=https://registry.npmjs.org/
|
||||||
|
cwebp-bin:registry=https://registry.npmjs.org/
|
||||||
|
esbuild:registry=https://registry.npmjs.org/
|
||||||
|
gifsicle:registry=https://registry.npmjs.org/
|
||||||
|
jpegtran-bin:registry=https://registry.npmjs.org/
|
||||||
|
mozjpeg:registry=https://registry.npmjs.org/
|
||||||
|
optipng-bin:registry=https://registry.npmjs.org/
|
||||||
|
pngquant-bin:registry=https://registry.npmjs.org/
|
||||||
|
|
||||||
|
# Или глобально разрешить все скрипты (менее безопасно, но проще)
|
||||||
|
# ignore-scripts=false
|
||||||
12
vite-templates/.editorconfig
Normal file
12
vite-templates/.editorconfig
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
4
vite-templates/.eslintignore
Normal file
4
vite-templates/.eslintignore
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
*.min.js
|
||||||
|
vite.config.js
|
||||||
49
vite-templates/.eslintrc.js
Normal file
49
vite-templates/.eslintrc.js
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2021: true,
|
||||||
|
node: true
|
||||||
|
},
|
||||||
|
extends: ['eslint:recommended', 'prettier'],
|
||||||
|
plugins: ['prettier'],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module'
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// Prettier
|
||||||
|
'prettier/prettier': 'error',
|
||||||
|
|
||||||
|
// Общие правила
|
||||||
|
'no-console': 'warn',
|
||||||
|
'no-unused-vars': 'warn',
|
||||||
|
'no-undef': 'error',
|
||||||
|
'prefer-const': 'error',
|
||||||
|
'no-var': 'error',
|
||||||
|
|
||||||
|
// Стиль кода
|
||||||
|
indent: ['error', 2],
|
||||||
|
quotes: ['error', 'single'],
|
||||||
|
semi: ['error', 'always'],
|
||||||
|
'comma-dangle': ['error', 'never'],
|
||||||
|
'object-curly-spacing': ['error', 'always'],
|
||||||
|
'array-bracket-spacing': ['error', 'never'],
|
||||||
|
|
||||||
|
// Функции
|
||||||
|
'arrow-spacing': 'error',
|
||||||
|
'no-duplicate-imports': 'error',
|
||||||
|
'prefer-arrow-callback': 'error',
|
||||||
|
'prefer-template': 'error',
|
||||||
|
|
||||||
|
// Объекты и массивы
|
||||||
|
'object-shorthand': 'error',
|
||||||
|
'prefer-destructuring': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
array: false,
|
||||||
|
object: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
ignorePatterns: ['dist/', 'node_modules/', '*.min.js']
|
||||||
|
};
|
||||||
35
vite-templates/.gitignore
vendored
Normal file
35
vite-templates/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Environment vars
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
.cache/
|
||||||
|
.parcel-cache/
|
||||||
|
.eslintcache
|
||||||
34
vite-templates/.lite/package.json
Normal file
34
vite-templates/.lite/package.json
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"name": "modern-frontend-builder-lite",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Современный frontend сборщик на Vite/Vituum (облегченная версия)",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"lint": "eslint . --ext .js,.ts,.vue --fix",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"lint:style": "stylelint \"src/**/*.{css,scss,sass}\" --fix",
|
||||||
|
"serve": "vite --host --https"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"normalize.css": "^8.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vituum/vite-plugin-pug": "^1.0.15",
|
||||||
|
"@vituum/vite-plugin-twig": "^1.0.15",
|
||||||
|
"vite": "^5.0.0",
|
||||||
|
"vituum": "^1.1.0",
|
||||||
|
"sass": "^1.69.0",
|
||||||
|
"eslint": "^8.55.0",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-plugin-prettier": "^5.0.1",
|
||||||
|
"prettier": "^3.1.0",
|
||||||
|
"stylelint": "^15.11.0",
|
||||||
|
"stylelint-config-standard-scss": "^11.1.0",
|
||||||
|
"stylelint-prettier": "^4.0.2",
|
||||||
|
"@vitejs/plugin-basic-ssl": "^1.0.2",
|
||||||
|
"glob": "^10.3.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
133
vite-templates/.lite/vite.config.js
Normal file
133
vite-templates/.lite/vite.config.js
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import vituum from 'vituum';
|
||||||
|
import pug from '@vituum/vite-plugin-pug';
|
||||||
|
import twig from '@vituum/vite-plugin-twig';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import { glob } from 'glob';
|
||||||
|
import basicSsl from '@vitejs/plugin-basic-ssl';
|
||||||
|
|
||||||
|
// Автоматическое определение входных точек
|
||||||
|
const getInputFiles = () => {
|
||||||
|
const htmlFiles = glob.sync('src/pages/**/*.{html,pug,twig}');
|
||||||
|
const jsFiles = glob.sync('src/js/**/*.js');
|
||||||
|
|
||||||
|
const input = {};
|
||||||
|
|
||||||
|
// HTML/PUG/TWIG файлы
|
||||||
|
htmlFiles.forEach(file => {
|
||||||
|
const name = file
|
||||||
|
.replace('src/pages/', '')
|
||||||
|
.replace(/\.(html|pug|twig)$/, '');
|
||||||
|
input[name] = resolve(__dirname, file);
|
||||||
|
});
|
||||||
|
|
||||||
|
// JS файлы
|
||||||
|
jsFiles.forEach(file => {
|
||||||
|
const name = file.replace('src/js/', '').replace('.js', '');
|
||||||
|
input[`js/${name}`] = resolve(__dirname, file);
|
||||||
|
});
|
||||||
|
|
||||||
|
return input;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vituum({
|
||||||
|
pages: {
|
||||||
|
dir: './src/pages'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Поддержка Pug
|
||||||
|
pug({
|
||||||
|
root: './src',
|
||||||
|
options: {
|
||||||
|
pretty: true,
|
||||||
|
basedir: './src'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Поддержка Twig
|
||||||
|
twig({
|
||||||
|
root: './src',
|
||||||
|
namespaces: {
|
||||||
|
layouts: './src/layouts',
|
||||||
|
components: './src/components',
|
||||||
|
data: './src/data'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
// HTTPS для разработки
|
||||||
|
basicSsl()
|
||||||
|
],
|
||||||
|
|
||||||
|
// Настройки сервера разработки
|
||||||
|
server: {
|
||||||
|
host: true,
|
||||||
|
port: 3000,
|
||||||
|
https: true,
|
||||||
|
open: true
|
||||||
|
},
|
||||||
|
|
||||||
|
// Настройки сборки
|
||||||
|
build: {
|
||||||
|
outDir: 'dist',
|
||||||
|
assetsDir: 'assets',
|
||||||
|
sourcemap: true,
|
||||||
|
rollupOptions: {
|
||||||
|
input: getInputFiles(),
|
||||||
|
output: {
|
||||||
|
chunkFileNames: 'assets/js/[name]-[hash].js',
|
||||||
|
entryFileNames: 'assets/js/[name]-[hash].js',
|
||||||
|
assetFileNames: assetInfo => {
|
||||||
|
const info = assetInfo.name.split('.');
|
||||||
|
const ext = info[info.length - 1];
|
||||||
|
|
||||||
|
if (/\.(png|jpe?g|svg|gif|tiff|bmp|ico)$/i.test(assetInfo.name)) {
|
||||||
|
return `assets/images/[name]-[hash].${ext}`;
|
||||||
|
}
|
||||||
|
if (/\.(woff2?|eot|ttf|otf)$/i.test(assetInfo.name)) {
|
||||||
|
return `assets/fonts/[name]-[hash].${ext}`;
|
||||||
|
}
|
||||||
|
if (/\.css$/i.test(assetInfo.name)) {
|
||||||
|
return `assets/css/[name]-[hash].${ext}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `assets/[name]-[hash].${ext}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Минификация
|
||||||
|
minify: 'terser',
|
||||||
|
terserOptions: {
|
||||||
|
compress: {
|
||||||
|
drop_console: true,
|
||||||
|
drop_debugger: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Настройки CSS
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
additionalData: `@import "./src/styles/_vars.scss"; @import "./src/styles/_scss";`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
devSourcemap: true
|
||||||
|
},
|
||||||
|
|
||||||
|
// Алиасы путей
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': resolve(__dirname, 'src'),
|
||||||
|
'@styles': resolve(__dirname, 'src/styles'),
|
||||||
|
'@js': resolve(__dirname, 'src/js'),
|
||||||
|
'@images': resolve(__dirname, 'src/images'),
|
||||||
|
'@components': resolve(__dirname, 'src/components'),
|
||||||
|
'@layouts': resolve(__dirname, 'src/layouts'),
|
||||||
|
'@data': resolve(__dirname, 'src/data')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
6
vite-templates/.prettierignore
Normal file
6
vite-templates/.prettierignore
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
*.min.js
|
||||||
|
*.min.css
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
28
vite-templates/.prettierrc
Normal file
28
vite-templates/.prettierrc
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 80,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"bracketSameLine": false,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"quoteProps": "as-needed",
|
||||||
|
"jsxSingleQuote": true,
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.scss",
|
||||||
|
"options": {
|
||||||
|
"singleQuote": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": "*.json",
|
||||||
|
"options": {
|
||||||
|
"singleQuote": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
52
vite-templates/.stylelintrc.js
Normal file
52
vite-templates/.stylelintrc.js
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
module.exports = {
|
||||||
|
extends: ['stylelint-config-standard-scss', 'stylelint-prettier/recommended'],
|
||||||
|
plugins: ['stylelint-prettier'],
|
||||||
|
rules: {
|
||||||
|
// Prettier
|
||||||
|
'prettier/prettier': true,
|
||||||
|
|
||||||
|
// SCSS правила
|
||||||
|
'scss/at-rule-no-unknown': null,
|
||||||
|
'scss/at-import-partial-extension': null,
|
||||||
|
'scss/dollar-variable-pattern': '^[a-z][a-zA-Z0-9]*$',
|
||||||
|
'scss/percent-placeholder-pattern': '^[a-z][a-zA-Z0-9]*$',
|
||||||
|
|
||||||
|
// Общие правила
|
||||||
|
'color-hex-case': 'lower',
|
||||||
|
'color-hex-length': 'short',
|
||||||
|
'declaration-block-trailing-semicolon': 'always',
|
||||||
|
indentation: 2,
|
||||||
|
'max-empty-lines': 2,
|
||||||
|
'no-duplicate-selectors': true,
|
||||||
|
'no-empty-source': null,
|
||||||
|
|
||||||
|
// Селекторы
|
||||||
|
'selector-class-pattern':
|
||||||
|
'^[a-z]([a-z0-9-]+)?(__([a-z0-9]+-?)+)?(--([a-z0-9]+-?)+){0,2}$',
|
||||||
|
'selector-max-id': 0,
|
||||||
|
'selector-max-compound-selectors': 4,
|
||||||
|
|
||||||
|
// Свойства
|
||||||
|
'property-no-unknown': true,
|
||||||
|
'declaration-no-important': true,
|
||||||
|
|
||||||
|
// Единицы измерения
|
||||||
|
'unit-allowed-list': [
|
||||||
|
'px',
|
||||||
|
'em',
|
||||||
|
'rem',
|
||||||
|
'%',
|
||||||
|
'vh',
|
||||||
|
'vw',
|
||||||
|
'vmin',
|
||||||
|
'vmax',
|
||||||
|
'deg',
|
||||||
|
's',
|
||||||
|
'ms'
|
||||||
|
],
|
||||||
|
|
||||||
|
// Функции
|
||||||
|
'function-url-quotes': 'always'
|
||||||
|
},
|
||||||
|
ignoreFiles: ['dist/**/*', 'node_modules/**/*']
|
||||||
|
};
|
||||||
271
vite-templates/README.md
Normal file
271
vite-templates/README.md
Normal file
|
|
@ -0,0 +1,271 @@
|
||||||
|
# 🚀 Полное руководство
|
||||||
|
|
||||||
|
## 📋 Подготовка к установке
|
||||||
|
|
||||||
|
### Системные требования
|
||||||
|
|
||||||
|
- **Node.js** 16.0+
|
||||||
|
- **npm** 7.0+ или **yarn** 1.22+
|
||||||
|
- **Git** (для клонирования)
|
||||||
|
|
||||||
|
### Проверка версий
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node --version # v16.0+
|
||||||
|
npm --version # 7.0+
|
||||||
|
git --version # любая актуальная
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Возможности
|
||||||
|
|
||||||
|
- **⚡ Vite + Vituum** - Молниеносная сборка и HMR
|
||||||
|
- **🎨 Шаблонизаторы** - Поддержка Pug и Twig
|
||||||
|
- **💅 SCSS** - Современный препроцессор CSS
|
||||||
|
- **🔍 Линтинг** - ESLint + Prettier + Stylelint
|
||||||
|
- **📱 Адаптивность** - Mobile-first подход
|
||||||
|
- **🖼️ Оптимизация изображений** - Автоматическое сжатие
|
||||||
|
- **🔒 HTTPS** - Безопасная разработка
|
||||||
|
- **📊 JSON данные** - Динамическая загрузка контента
|
||||||
|
- **🎯 Современный JS** - ES6+ модули и функции
|
||||||
|
|
||||||
|
## 📦 Установка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Клонирование репозитория
|
||||||
|
git clone <repository-url>
|
||||||
|
cd modern-frontend-builder
|
||||||
|
|
||||||
|
# Установка зависимостей
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ Использование
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Запуск сервера разработки
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Сборка для продакшена
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Предварительный просмотр сборки
|
||||||
|
npm run preview
|
||||||
|
|
||||||
|
# Линтинг и форматирование
|
||||||
|
npm run lint # ESLint
|
||||||
|
npm run format # Prettier
|
||||||
|
npm run lint:style # Stylelint
|
||||||
|
|
||||||
|
# HTTPS сервер
|
||||||
|
npm run serve
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📁 Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── pages/ # Страницы (HTML/PUG/TWIG)
|
||||||
|
├── layouts/ # Макеты шаблонов
|
||||||
|
├── components/ # Переиспользуемые компоненты
|
||||||
|
├── styles/ # SCSS стили
|
||||||
|
├── js/ # JavaScript модули
|
||||||
|
├── images/ # Изображения
|
||||||
|
├── fonts/ # Шрифты
|
||||||
|
├── data/ # JSON данные
|
||||||
|
└── public/ # Статические файлы
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎨 Работа с шаблонами
|
||||||
|
|
||||||
|
### Pug
|
||||||
|
|
||||||
|
```pug
|
||||||
|
extends ../layouts/base
|
||||||
|
|
||||||
|
block content
|
||||||
|
.hero
|
||||||
|
h1= data.content.hero.title
|
||||||
|
p= data.content.hero.subtitle
|
||||||
|
```
|
||||||
|
|
||||||
|
### Twig
|
||||||
|
|
||||||
|
```twig
|
||||||
|
{% extends 'layouts/main.twig' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="hero">
|
||||||
|
<h1>{{ data.content.hero.title }}</h1>
|
||||||
|
<p>{{ data.content.hero.subtitle }}</p>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 💅 Система стилей
|
||||||
|
|
||||||
|
- **vars** - Централизованные переменные
|
||||||
|
- **Mixins** - Переиспользуемые стили
|
||||||
|
- **Components** - Модульные компоненты
|
||||||
|
- **Utilities** - Вспомогательные классы
|
||||||
|
- **BEM методология** - Структурированное именование
|
||||||
|
|
||||||
|
## 🖼️ Оптимизация изображений
|
||||||
|
|
||||||
|
Автоматическая оптимизация:
|
||||||
|
|
||||||
|
- **JPEG** - mozjpeg сжатие
|
||||||
|
- **PNG** - pngquant оптимизация
|
||||||
|
- **SVG** - SVGO минификация
|
||||||
|
- **WebP** - Современный формат
|
||||||
|
|
||||||
|
## 📊 Работа с данными
|
||||||
|
|
||||||
|
JSON файлы автоматически доступны в шаблонах:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// В JavaScript
|
||||||
|
import { loadData } from './utils/dataLoader.js';
|
||||||
|
const data = await loadData('/src/data/content.json');
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Настройка
|
||||||
|
|
||||||
|
### Vite конфигурация
|
||||||
|
|
||||||
|
Основные настройки в `vite.config.js`:
|
||||||
|
|
||||||
|
- Алиасы путей
|
||||||
|
- Плагины оптимизации
|
||||||
|
- Настройки сборки
|
||||||
|
- HTTPS сертификаты
|
||||||
|
|
||||||
|
### Линтинг
|
||||||
|
|
||||||
|
- **ESLint** - JavaScript код
|
||||||
|
- **Stylelint** - CSS/SCSS стили
|
||||||
|
- **Prettier** - Форматирование
|
||||||
|
|
||||||
|
## 🚀 Развертывание
|
||||||
|
|
||||||
|
### Сборка для продакшена
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Результат будет в папке `dist/` - готов для загрузки на хостинг.
|
||||||
|
|
||||||
|
### Настройка для хостинга
|
||||||
|
|
||||||
|
Большинство хостингов поддерживают статические файлы из папки `dist/`.
|
||||||
|
|
||||||
|
Для **Netlify/Vercel**:
|
||||||
|
|
||||||
|
- Подключите Git репозиторий
|
||||||
|
- Команда сборки: `npm run build`
|
||||||
|
- Папка публикации: `dist`
|
||||||
|
|
||||||
|
Для **GitHub Pages**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Установите gh-pages
|
||||||
|
npm install --save-dev gh-pages
|
||||||
|
|
||||||
|
# Добавьте в package.json
|
||||||
|
"scripts": {
|
||||||
|
"deploy": "npm run build && gh-pages -d dist"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Деплой
|
||||||
|
npm run deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 Рабочий процесс
|
||||||
|
|
||||||
|
### Ежедневная разработка
|
||||||
|
|
||||||
|
1. **Запуск:** `npm run dev`
|
||||||
|
2. **Разработка:** Редактирование файлов в `src/`
|
||||||
|
3. **Проверка:** `npm run lint`
|
||||||
|
4. **Коммит:** Сохранение изменений в Git
|
||||||
|
|
||||||
|
### Перед релизом
|
||||||
|
|
||||||
|
1. **Тестирование:** `npm run build && npm run preview`
|
||||||
|
2. **Проверка качества:**
|
||||||
|
```bash
|
||||||
|
npm run lint
|
||||||
|
npm run format
|
||||||
|
npm run lint:style
|
||||||
|
```
|
||||||
|
3. **Сборка:** `npm run build`
|
||||||
|
4. **Деплой:** Загрузка `dist/` на хостинг
|
||||||
|
|
||||||
|
## 📚 Дополнительные возможности
|
||||||
|
|
||||||
|
### Добавление новых шаблонизаторов
|
||||||
|
|
||||||
|
В `vite.config.js` можно добавить поддержку других шаблонов:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import handlebars from '@vituum/vite-plugin-handlebars';
|
||||||
|
|
||||||
|
// В plugins
|
||||||
|
handlebars({
|
||||||
|
root: './src'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Интеграция с CMS
|
||||||
|
|
||||||
|
Для подключения к Headless CMS:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// src/js/utils/cms.js
|
||||||
|
export const fetchContent = async endpoint => {
|
||||||
|
const response = await fetch(`https://api.your-cms.com/${endpoint}`);
|
||||||
|
return response.json();
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Добавление PWA
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install --save-dev vite-plugin-pwa
|
||||||
|
|
||||||
|
# В vite.config.js
|
||||||
|
import { VitePWA } from 'vite-plugin-pwa';
|
||||||
|
|
||||||
|
// В plugins
|
||||||
|
VitePWA({
|
||||||
|
registerType: 'autoUpdate',
|
||||||
|
workbox: {
|
||||||
|
globPatterns: ['**/*.{js,css,html,ico,png,svg}']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Анализ бандла
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install --save-dev rollup-plugin-visualizer
|
||||||
|
|
||||||
|
# Добавить в vite.config.js
|
||||||
|
import { visualizer } from 'rollup-plugin-visualizer';
|
||||||
|
|
||||||
|
// В plugins
|
||||||
|
visualizer({
|
||||||
|
filename: 'dist/stats.html',
|
||||||
|
open: true
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📖 Полезные ссылки
|
||||||
|
|
||||||
|
- **Vite документация:** https://vitejs.dev/
|
||||||
|
- **Vituum документация:** https://vituum.dev/
|
||||||
|
- **Pug документация:** https://pugjs.org/
|
||||||
|
- **Twig документация:** https://twig.symfony.com/
|
||||||
|
- **SCSS документация:** https://sass-lang.com/
|
||||||
|
- **ESLint правила:** https://eslint.org/docs/rules/
|
||||||
|
- **Prettier опции:** https://prettier.io/docs/en/options.html
|
||||||
57
vite-templates/package.json
Normal file
57
vite-templates/package.json
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
{
|
||||||
|
"name": "modern-frontend-builder",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Современный frontend сборщик на Vite/Vituum",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --mode development",
|
||||||
|
"dev:safe": "vite --mode development --force",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"clean": "rm -rf dist .vite node_modules/.vite",
|
||||||
|
"restart": "npm run clean && npm install && npm run dev",
|
||||||
|
"lint": "eslint . --ext .js,.ts,.vue --fix",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"check": "npm run lint && npm run format",
|
||||||
|
"lint:style": "stylelint \"src/**/*.{css,scss,sass}\" --fix",
|
||||||
|
"serve": "vite --host --https",
|
||||||
|
"postinstall": "node scripts/postinstall.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"normalize.css": "^8.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-basic-ssl": "^1.0.2",
|
||||||
|
"@vituum/vite-plugin-handlebars": "^1.1.0",
|
||||||
|
"@vituum/vite-plugin-pug": "^1.0.15",
|
||||||
|
"@vituum/vite-plugin-twig": "^1.0.15",
|
||||||
|
"eslint": "^8.55.0",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-plugin-prettier": "^5.0.1",
|
||||||
|
"glob": "^10.3.10",
|
||||||
|
"imagemin-mozjpeg": "^10.0.0",
|
||||||
|
"imagemin-pngquant": "^9.0.2",
|
||||||
|
"imagemin-svgo": "^10.0.1",
|
||||||
|
"imagemin-webp": "^8.0.0",
|
||||||
|
"prettier": "^3.1.0",
|
||||||
|
"sass": "^1.69.0",
|
||||||
|
"stylelint": "^15.11.0",
|
||||||
|
"stylelint-config-standard-scss": "^11.1.0",
|
||||||
|
"stylelint-prettier": "^4.0.2",
|
||||||
|
"vite": "^5.0.0",
|
||||||
|
"vite-plugin-imagemin": "^0.6.1",
|
||||||
|
"vituum": "^1.1.0"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"onlyBuiltDependencies": [
|
||||||
|
"@parcel/watcher",
|
||||||
|
"cwebp-bin",
|
||||||
|
"esbuild",
|
||||||
|
"gifsicle",
|
||||||
|
"jpegtran-bin",
|
||||||
|
"mozjpeg",
|
||||||
|
"optipng-bin",
|
||||||
|
"pngquant-bin"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
8194
vite-templates/pnpm-lock.yaml
Normal file
8194
vite-templates/pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
32
vite-templates/scripts/build.js
Normal file
32
vite-templates/scripts/build.js
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { build } from 'vite';
|
||||||
|
import { exec } from 'child_process';
|
||||||
|
import { promisify } from 'util';
|
||||||
|
|
||||||
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
|
async function buildProject() {
|
||||||
|
console.log('🚀 Начинаем сборку проекта...\n');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Линтинг перед сборкой
|
||||||
|
console.log('🔍 Проверка кода...');
|
||||||
|
await execAsync('npm run lint');
|
||||||
|
console.log('✅ Код проверен\n');
|
||||||
|
|
||||||
|
// Сборка
|
||||||
|
console.log('📦 Сборка проекта...');
|
||||||
|
await build();
|
||||||
|
console.log('✅ Проект собран\n');
|
||||||
|
|
||||||
|
// Анализ размера бандла
|
||||||
|
console.log('📊 Анализ размера файлов...');
|
||||||
|
await execAsync('npx vite-bundle-analyzer dist');
|
||||||
|
|
||||||
|
console.log('🎉 Сборка завершена успешно!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Ошибка сборки:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildProject();
|
||||||
26
vite-templates/scripts/dev.js
Normal file
26
vite-templates/scripts/dev.js
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { createServer } from 'vite';
|
||||||
|
import { createRequire } from 'module';
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
const config = require('../vite.config.js');
|
||||||
|
|
||||||
|
async function startDevServer() {
|
||||||
|
try {
|
||||||
|
const server = await createServer(config);
|
||||||
|
await server.listen();
|
||||||
|
|
||||||
|
console.log('🚀 Сервер разработки запущен!');
|
||||||
|
console.log('📱 Локальный адрес: https://localhost:3000');
|
||||||
|
console.log('🌐 Сетевой адрес доступен в терминале');
|
||||||
|
console.log('\n⚡ Готов к разработке!\n');
|
||||||
|
|
||||||
|
// Открытие браузера
|
||||||
|
const { default: open } = await import('open');
|
||||||
|
await open('https://localhost:3000');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Ошибка запуска сервера:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startDevServer();
|
||||||
31
vite-templates/scripts/postinstall.js
Normal file
31
vite-templates/scripts/postinstall.js
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { dirname, join } from 'path';
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
console.log('🔧 Проверка установки пакетов оптимизации изображений...');
|
||||||
|
|
||||||
|
const requiredBinaries = [
|
||||||
|
'node_modules/mozjpeg/vendor/cjpeg',
|
||||||
|
'node_modules/pngquant-bin/vendor/pngquant',
|
||||||
|
'node_modules/gifsicle/vendor/gifsicle'
|
||||||
|
];
|
||||||
|
|
||||||
|
let allInstalled = true;
|
||||||
|
|
||||||
|
requiredBinaries.forEach(binary => {
|
||||||
|
const fullPath = join(__dirname, '..', binary);
|
||||||
|
if (!existsSync(fullPath)) {
|
||||||
|
console.warn(`⚠️ Бинарный файл не найден: ${binary}`);
|
||||||
|
allInstalled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (allInstalled) {
|
||||||
|
console.log('✅ Все пакеты оптимизации изображений установлены корректно');
|
||||||
|
} else {
|
||||||
|
console.log('ℹ️ Некоторые пакеты могут потребовать ручной установки');
|
||||||
|
console.log('💡 Попробуйте: pnpm rebuild или npm rebuild');
|
||||||
|
}
|
||||||
51
vite-templates/src/data/content.json
Normal file
51
vite-templates/src/data/content.json
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
{
|
||||||
|
"hero": {
|
||||||
|
"title": "Современные веб-решения",
|
||||||
|
"subtitle": "Создаем качественные сайты и приложения с использованием передовых технологий"
|
||||||
|
},
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"icon": "speed.svg",
|
||||||
|
"title": "Высокая скорость",
|
||||||
|
"description": "Оптимизированная сборка для максимальной производительности"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "modern.svg",
|
||||||
|
"title": "Современные технологии",
|
||||||
|
"description": "Используем актуальные инструменты и подходы разработки"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "responsive.svg",
|
||||||
|
"title": "Адаптивность",
|
||||||
|
"description": "Идеальное отображение на всех устройствах и экранах"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"about": {
|
||||||
|
"title": "О нашей компании",
|
||||||
|
"description": "Мы команда профессиональных разработчиков, специализирующихся на создании современных веб-приложений."
|
||||||
|
},
|
||||||
|
"team": [
|
||||||
|
{
|
||||||
|
"name": "Анна Иванова",
|
||||||
|
"position": "Frontend разработчик",
|
||||||
|
"photo": "anna.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Максим Петров",
|
||||||
|
"position": "Backend разработчик",
|
||||||
|
"photo": "maxim.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Елена Сидорова",
|
||||||
|
"position": "UX/UI дизайнер",
|
||||||
|
"photo": "elena.jpg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"footer": {
|
||||||
|
"description": "Создаем инновационные веб-решения для вашего бизнеса"
|
||||||
|
},
|
||||||
|
"contacts": {
|
||||||
|
"email": "info@example.com",
|
||||||
|
"phone": "+7 (999) 123-45-67"
|
||||||
|
}
|
||||||
|
}
|
||||||
46
vite-templates/src/data/navigation.json
Normal file
46
vite-templates/src/data/navigation.json
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"main": [
|
||||||
|
{
|
||||||
|
"title": "Главная",
|
||||||
|
"url": "/",
|
||||||
|
"active": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "О нас",
|
||||||
|
"url": "/about.html",
|
||||||
|
"active": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Услуги",
|
||||||
|
"url": "/services.html",
|
||||||
|
"active": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Контакты",
|
||||||
|
"url": "/contact.html",
|
||||||
|
"active": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"footer": [
|
||||||
|
{
|
||||||
|
"title": "Главная",
|
||||||
|
"url": "/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "О компании",
|
||||||
|
"url": "/about.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Услуги",
|
||||||
|
"url": "/services.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Блог",
|
||||||
|
"url": "/blog.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Контакты",
|
||||||
|
"url": "/contact.html"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
54
vite-templates/src/js/components/header.js
Normal file
54
vite-templates/src/js/components/header.js
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { debounce } from '../utils/helpers.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициализация шапки сайта
|
||||||
|
*/
|
||||||
|
export const initHeader = () => {
|
||||||
|
const header = document.querySelector('.header');
|
||||||
|
const burger = document.querySelector('.header__burger');
|
||||||
|
const nav = document.querySelector('.header__nav');
|
||||||
|
|
||||||
|
if (!header) return;
|
||||||
|
|
||||||
|
// Скрытие/показ шапки при скролле
|
||||||
|
let lastScrollY = window.scrollY;
|
||||||
|
|
||||||
|
const handleScroll = debounce(() => {
|
||||||
|
const currentScrollY = window.scrollY;
|
||||||
|
|
||||||
|
if (currentScrollY > 100) {
|
||||||
|
if (currentScrollY > lastScrollY) {
|
||||||
|
header.style.transform = 'translateY(-100%)';
|
||||||
|
} else {
|
||||||
|
header.style.transform = 'translateY(0)';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
header.style.transform = 'translateY(0)';
|
||||||
|
}
|
||||||
|
|
||||||
|
lastScrollY = currentScrollY;
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
window.addEventListener('scroll', handleScroll);
|
||||||
|
|
||||||
|
// Мобильное меню
|
||||||
|
if (burger && nav) {
|
||||||
|
burger.addEventListener('click', () => {
|
||||||
|
nav.classList.toggle('header__nav--open');
|
||||||
|
burger.classList.toggle('header__burger--active');
|
||||||
|
document.body.classList.toggle('nav-open');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Закрытие меню при клике вне его
|
||||||
|
document.addEventListener('click', e => {
|
||||||
|
if (
|
||||||
|
!header.contains(e.target) &&
|
||||||
|
nav.classList.contains('header__nav--open')
|
||||||
|
) {
|
||||||
|
nav.classList.remove('header__nav--open');
|
||||||
|
burger.classList.remove('header__burger--active');
|
||||||
|
document.body.classList.remove('nav-open');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
166
vite-templates/src/js/components/modal.js
Normal file
166
vite-templates/src/js/components/modal.js
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
/**
|
||||||
|
* Модальные окна
|
||||||
|
*/
|
||||||
|
export class Modal {
|
||||||
|
constructor(selector, options = {}) {
|
||||||
|
this.modal =
|
||||||
|
typeof selector === 'string'
|
||||||
|
? document.querySelector(selector)
|
||||||
|
: selector;
|
||||||
|
|
||||||
|
if (!this.modal) {
|
||||||
|
console.warn('Модальное окно не найдено:', selector);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.options = {
|
||||||
|
closeOnEscape: true,
|
||||||
|
closeOnOverlay: true,
|
||||||
|
focusTrap: true,
|
||||||
|
animation: 'fade',
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isOpen = false;
|
||||||
|
this.previousFocus = null;
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.createOverlay();
|
||||||
|
this.bindEvents();
|
||||||
|
this.setupAccessibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
createOverlay() {
|
||||||
|
if (!this.modal.querySelector('.modal__overlay')) {
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
overlay.className = 'modal__overlay';
|
||||||
|
this.modal.insertBefore(overlay, this.modal.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.overlay = this.modal.querySelector('.modal__overlay');
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEvents() {
|
||||||
|
// Кнопки открытия
|
||||||
|
document
|
||||||
|
.querySelectorAll(`[data-modal-open="${this.modal.id}"]`)
|
||||||
|
.forEach(btn => {
|
||||||
|
btn.addEventListener('click', e => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.open();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Кнопки закрытия
|
||||||
|
this.modal.querySelectorAll('[data-modal-close]').forEach(btn => {
|
||||||
|
btn.addEventListener('click', e => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Закрытие по оверлею
|
||||||
|
if (this.options.closeOnOverlay) {
|
||||||
|
this.overlay.addEventListener('click', () => this.close());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Закрытие по Escape
|
||||||
|
if (this.options.closeOnEscape) {
|
||||||
|
document.addEventListener('keydown', e => {
|
||||||
|
if (e.key === 'Escape' && this.isOpen) {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupAccessibility() {
|
||||||
|
this.modal.setAttribute('role', 'dialog');
|
||||||
|
this.modal.setAttribute('aria-modal', 'true');
|
||||||
|
|
||||||
|
if (!this.modal.getAttribute('aria-labelledby')) {
|
||||||
|
const title = this.modal.querySelector('.modal__title');
|
||||||
|
if (title) {
|
||||||
|
title.id = title.id || `modal-title-${Date.now()}`;
|
||||||
|
this.modal.setAttribute('aria-labelledby', title.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open() {
|
||||||
|
if (this.isOpen) return;
|
||||||
|
|
||||||
|
this.previousFocus = document.activeElement;
|
||||||
|
|
||||||
|
document.body.classList.add('modal-open');
|
||||||
|
this.modal.classList.add('modal--open');
|
||||||
|
|
||||||
|
this.isOpen = true;
|
||||||
|
|
||||||
|
// Фокус на модальном окне
|
||||||
|
if (this.options.focusTrap) {
|
||||||
|
this.trapFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Событие открытия
|
||||||
|
this.modal.dispatchEvent(new CustomEvent('modal:open'));
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
if (!this.isOpen) return;
|
||||||
|
|
||||||
|
document.body.classList.remove('modal-open');
|
||||||
|
this.modal.classList.remove('modal--open');
|
||||||
|
|
||||||
|
this.isOpen = false;
|
||||||
|
|
||||||
|
// Возврат фокуса
|
||||||
|
if (this.previousFocus) {
|
||||||
|
this.previousFocus.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Событие закрытия
|
||||||
|
this.modal.dispatchEvent(new CustomEvent('modal:close'));
|
||||||
|
}
|
||||||
|
|
||||||
|
trapFocus() {
|
||||||
|
const focusableElements = this.modal.querySelectorAll(
|
||||||
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
||||||
|
);
|
||||||
|
|
||||||
|
const firstElement = focusableElements[0];
|
||||||
|
const lastElement = focusableElements[focusableElements.length - 1];
|
||||||
|
|
||||||
|
if (firstElement) {
|
||||||
|
firstElement.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.modal.addEventListener('keydown', e => {
|
||||||
|
if (e.key === 'Tab') {
|
||||||
|
if (e.shiftKey) {
|
||||||
|
if (document.activeElement === firstElement) {
|
||||||
|
e.preventDefault();
|
||||||
|
lastElement.focus();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (document.activeElement === lastElement) {
|
||||||
|
e.preventDefault();
|
||||||
|
firstElement.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициализация всех модальных окон
|
||||||
|
*/
|
||||||
|
export const initModal = () => {
|
||||||
|
document.querySelectorAll('.modal').forEach(modal => {
|
||||||
|
new Modal(modal);
|
||||||
|
});
|
||||||
|
};
|
||||||
82
vite-templates/src/js/main.js
Normal file
82
vite-templates/src/js/main.js
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
// import './utils/polyfills.js';
|
||||||
|
import { initHeader } from './components/header.js';
|
||||||
|
import { initModal } from './components/modal.js';
|
||||||
|
import { loadData } from './utils/dataLoader.js';
|
||||||
|
|
||||||
|
// Инициализация приложения
|
||||||
|
class App {
|
||||||
|
constructor() {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
try {
|
||||||
|
// Загрузка данных
|
||||||
|
await this.loadAppData();
|
||||||
|
|
||||||
|
// Инициализация компонентов
|
||||||
|
this.initComponents();
|
||||||
|
|
||||||
|
// Обработчики событий
|
||||||
|
this.bindEvents();
|
||||||
|
|
||||||
|
console.log('✅ Приложение инициализировано');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Ошибка инициализации:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadAppData() {
|
||||||
|
try {
|
||||||
|
const data = await loadData('/src/data/content.json');
|
||||||
|
window.appData = data;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('⚠️ Не удалось загрузить данные:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initComponents() {
|
||||||
|
initHeader();
|
||||||
|
initModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEvents() {
|
||||||
|
// Плавная прокрутка для якорных ссылок
|
||||||
|
document.addEventListener('click', e => {
|
||||||
|
const link = e.target.closest('a[href^="#"]');
|
||||||
|
if (link) {
|
||||||
|
e.preventDefault();
|
||||||
|
const target = document.querySelector(link.getAttribute('href'));
|
||||||
|
if (target) {
|
||||||
|
target.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'start'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ленивая загрузка изображений
|
||||||
|
if ('IntersectionObserver' in window) {
|
||||||
|
const imageObserver = new IntersectionObserver(entries => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
const img = entry.target;
|
||||||
|
img.src = img.dataset.src;
|
||||||
|
img.classList.remove('lazy');
|
||||||
|
imageObserver.unobserve(img);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('img[data-src]').forEach(img => {
|
||||||
|
imageObserver.observe(img);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запуск приложения
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
new App();
|
||||||
|
});
|
||||||
71
vite-templates/src/js/utils/dataLoader.js
Normal file
71
vite-templates/src/js/utils/dataLoader.js
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
/**
|
||||||
|
* Загрузчик JSON данных
|
||||||
|
*/
|
||||||
|
export class DataLoader {
|
||||||
|
constructor() {
|
||||||
|
this.cache = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Загрузка данных с кешированием
|
||||||
|
* @param {string} url - URL для загрузки
|
||||||
|
* @param {boolean} useCache - Использовать кеш
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
async load(url, useCache = true) {
|
||||||
|
if (useCache && this.cache.has(url)) {
|
||||||
|
return this.cache.get(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (useCache) {
|
||||||
|
this.cache.set(url, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Ошибка загрузки данных из ${url}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Загрузка нескольких источников данных
|
||||||
|
* @param {string[]} urls - Массив URL
|
||||||
|
* @returns {Promise<any[]>}
|
||||||
|
*/
|
||||||
|
async loadMultiple(urls) {
|
||||||
|
try {
|
||||||
|
const promises = urls.map(url => this.load(url));
|
||||||
|
return await Promise.all(promises);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка загрузки множественных данных:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Очистка кеша
|
||||||
|
*/
|
||||||
|
clearCache() {
|
||||||
|
this.cache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Экземпляр загрузчика
|
||||||
|
const dataLoader = new DataLoader();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Простая функция загрузки данных
|
||||||
|
* @param {string} url - URL для загрузки
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
export const loadData = url => dataLoader.load(url);
|
||||||
92
vite-templates/src/js/utils/helpers.js
Normal file
92
vite-templates/src/js/utils/helpers.js
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
/**
|
||||||
|
* Дебаунс функция
|
||||||
|
* @param {Function} func - Функция для дебаунса
|
||||||
|
* @param {number} wait - Время ожидания в мс
|
||||||
|
* @returns {Function}
|
||||||
|
*/
|
||||||
|
export const debounce = (func, wait) => {
|
||||||
|
let timeout;
|
||||||
|
return function executedFunction(...args) {
|
||||||
|
const later = () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
func(...args);
|
||||||
|
};
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(later, wait);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Троттлинг функция
|
||||||
|
* @param {Function} func - Функция для троттлинга
|
||||||
|
* @param {number} limit - Лимит времени в мс
|
||||||
|
* @returns {Function}
|
||||||
|
*/
|
||||||
|
export const throttle = (func, limit) => {
|
||||||
|
let inThrottle;
|
||||||
|
return function (...args) {
|
||||||
|
if (!inThrottle) {
|
||||||
|
func.apply(this, args);
|
||||||
|
inThrottle = true;
|
||||||
|
setTimeout(() => (inThrottle = false), limit);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение параметров URL
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
export const getUrlParams = () => {
|
||||||
|
return Object.fromEntries(new URLSearchParams(window.location.search));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Форматирование даты
|
||||||
|
* @param {Date|string} date - Дата для форматирования
|
||||||
|
* @param {string} locale - Локаль
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export const formatDate = (date, locale = 'ru-RU') => {
|
||||||
|
return new Intl.DateTimeFormat(locale, {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
}).format(new Date(date));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка поддержки WebP
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
export const supportsWebP = () => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const webP = new Image();
|
||||||
|
webP.onload = webP.onerror = () => {
|
||||||
|
resolve(webP.height === 2);
|
||||||
|
};
|
||||||
|
webP.src =
|
||||||
|
'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Анимация счетчика
|
||||||
|
* @param {HTMLElement} element - Элемент счетчика
|
||||||
|
* @param {number} end - Конечное значение
|
||||||
|
* @param {number} duration - Длительность анимации
|
||||||
|
*/
|
||||||
|
export const animateCounter = (element, end, duration = 2000) => {
|
||||||
|
let start = 0;
|
||||||
|
const increment = end / (duration / 16);
|
||||||
|
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
start += increment;
|
||||||
|
element.textContent = Math.floor(start);
|
||||||
|
|
||||||
|
if (start >= end) {
|
||||||
|
element.textContent = end;
|
||||||
|
clearInterval(timer);
|
||||||
|
}
|
||||||
|
}, 16);
|
||||||
|
};
|
||||||
85
vite-templates/src/js/utils/polyfills.js
Normal file
85
vite-templates/src/js/utils/polyfills.js
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
/**
|
||||||
|
* Полифиллы для старых браузеров
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Intersection Observer
|
||||||
|
if (!('IntersectionObserver' in window)) {
|
||||||
|
import('intersection-observer');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Резервный вариант для fetch
|
||||||
|
if (!window.fetch) {
|
||||||
|
window.fetch = async (url, options = {}) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open(options.method || 'GET', url);
|
||||||
|
|
||||||
|
Object.keys(options.headers || {}).forEach(key => {
|
||||||
|
xhr.setRequestHeader(key, options.headers[key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.onload = () => {
|
||||||
|
resolve({
|
||||||
|
ok: xhr.status >= 200 && xhr.status < 300,
|
||||||
|
status: xhr.status,
|
||||||
|
statusText: xhr.statusText,
|
||||||
|
json: () => Promise.resolve(JSON.parse(xhr.responseText)),
|
||||||
|
text: () => Promise.resolve(xhr.responseText)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onerror = () => reject(new Error('Network Error'));
|
||||||
|
xhr.send(options.body);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object.assign полифилл
|
||||||
|
if (typeof Object.assign !== 'function') {
|
||||||
|
Object.assign = function (target, ...sources) {
|
||||||
|
if (target == null) {
|
||||||
|
throw new TypeError('Cannot convert undefined or null to object');
|
||||||
|
}
|
||||||
|
|
||||||
|
const to = Object(target);
|
||||||
|
|
||||||
|
sources.forEach(source => {
|
||||||
|
if (source != null) {
|
||||||
|
for (const key in source) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||||
|
to[key] = source[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return to;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array.from полифилл
|
||||||
|
if (!Array.from) {
|
||||||
|
Array.from = function (arrayLike, mapFn, thisArg) {
|
||||||
|
const C = this;
|
||||||
|
const items = Object(arrayLike);
|
||||||
|
const len = parseInt(items.length) || 0;
|
||||||
|
const A = typeof C === 'function' ? Object(new C(len)) : new Array(len);
|
||||||
|
|
||||||
|
let k = 0;
|
||||||
|
while (k < len) {
|
||||||
|
const kValue = items[k];
|
||||||
|
if (mapFn) {
|
||||||
|
A[k] =
|
||||||
|
typeof thisArg === 'undefined'
|
||||||
|
? mapFn(kValue, k)
|
||||||
|
: mapFn.call(thisArg, kValue, k);
|
||||||
|
} else {
|
||||||
|
A[k] = kValue;
|
||||||
|
}
|
||||||
|
k += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
A.length = len;
|
||||||
|
return A;
|
||||||
|
};
|
||||||
|
}
|
||||||
84
vite-templates/src/styles/_base.scss
Normal file
84
vite-templates/src/styles/_base.scss
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
@use "./variables" as *;
|
||||||
|
@use "./mixins" as *;
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 16px;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: $font-family-base;
|
||||||
|
font-size: $font-size-base;
|
||||||
|
font-weight: $font-weight-normal;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: $text-primary;
|
||||||
|
background-color: $bg-primary;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
@include container;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Типографика
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
font-family: $font-family-heading;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: $text-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: $font-size-4xl;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: $font-size-3xl;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: $font-size-2xl;
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
font-size: $font-size-xl;
|
||||||
|
}
|
||||||
|
h5 {
|
||||||
|
font-size: $font-size-lg;
|
||||||
|
}
|
||||||
|
h6 {
|
||||||
|
font-size: $font-size-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
color: $text-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $primary;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color $transition-base;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
color: $primary-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
131
vite-templates/src/styles/_mixins.scss
Normal file
131
vite-templates/src/styles/_mixins.scss
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
@use "./variables" as *;
|
||||||
|
|
||||||
|
// Контейнер
|
||||||
|
@mixin container {
|
||||||
|
max-width: $container-max-width;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 $container-padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Медиа-запросы
|
||||||
|
@mixin media($breakpoint) {
|
||||||
|
@if $breakpoint == sm {
|
||||||
|
@media (min-width: $breakpoint-sm) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@if $breakpoint == md {
|
||||||
|
@media (min-width: $breakpoint-md) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@if $breakpoint == lg {
|
||||||
|
@media (min-width: $breakpoint-lg) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@if $breakpoint == xl {
|
||||||
|
@media (min-width: $breakpoint-xl) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@if $breakpoint == 2xl {
|
||||||
|
@media (min-width: $breakpoint-2xl) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Флексбокс
|
||||||
|
@mixin flex(
|
||||||
|
$direction: row,
|
||||||
|
$justify: flex-start,
|
||||||
|
$align: stretch,
|
||||||
|
$wrap: nowrap
|
||||||
|
) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: $direction;
|
||||||
|
justify-content: $justify;
|
||||||
|
align-items: $align;
|
||||||
|
flex-wrap: $wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Центрирование
|
||||||
|
@mixin center($type: both) {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
@if $type == both {
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
@if $type == horizontal {
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
@if $type == vertical {
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Скрытие текста
|
||||||
|
@mixin visually-hidden {
|
||||||
|
position: absolute !important;
|
||||||
|
width: 1px !important;
|
||||||
|
height: 1px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: -1px !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
clip: rect(0, 0, 0, 0) !important;
|
||||||
|
white-space: nowrap !important;
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Кнопка сброса
|
||||||
|
@mixin button-reset {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: 2px solid $primary;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ссылка сброса
|
||||||
|
@mixin link-reset {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Грид
|
||||||
|
@mixin grid($columns: 1, $gap: 1rem) {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat($columns, 1fr);
|
||||||
|
gap: $gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обрезка текста
|
||||||
|
@mixin text-truncate {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Многострочное обрезание
|
||||||
|
@mixin text-clamp($lines: 2) {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: $lines;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
75
vite-templates/src/styles/_variables.scss
Normal file
75
vite-templates/src/styles/_variables.scss
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
// Цвета
|
||||||
|
$primary: #3b82f6;
|
||||||
|
$primary-dark: #1d4ed8;
|
||||||
|
$primary-light: #93c5fd;
|
||||||
|
$secondary: #10b981;
|
||||||
|
$accent: #f59e0b;
|
||||||
|
|
||||||
|
$text-primary: #111827;
|
||||||
|
$text-secondary: #6b7280;
|
||||||
|
$text-muted: #9ca3af;
|
||||||
|
|
||||||
|
$bg-primary: #ffffff;
|
||||||
|
$bg-secondary: #f9fafb;
|
||||||
|
$bg-dark: #111827;
|
||||||
|
|
||||||
|
$border-color: #e5e7eb;
|
||||||
|
$border-color-hover: #d1d5db;
|
||||||
|
|
||||||
|
// Типографика
|
||||||
|
$font-family-base:
|
||||||
|
"Inter",
|
||||||
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
sans-serif;
|
||||||
|
$font-family-heading: "Poppins", sans-serif;
|
||||||
|
|
||||||
|
$font-size-xs: 0.75rem;
|
||||||
|
$font-size-sm: 0.875rem;
|
||||||
|
$font-size-base: 1rem;
|
||||||
|
$font-size-lg: 1.125rem;
|
||||||
|
$font-size-xl: 1.25rem;
|
||||||
|
$font-size-2xl: 1.5rem;
|
||||||
|
$font-size-3xl: 1.875rem;
|
||||||
|
$font-size-4xl: 2.25rem;
|
||||||
|
|
||||||
|
$font-weight-normal: 400;
|
||||||
|
$font-weight-medium: 500;
|
||||||
|
$font-weight-semibold: 600;
|
||||||
|
$font-weight-bold: 700;
|
||||||
|
|
||||||
|
// Размеры
|
||||||
|
$container-max-width: 1200px;
|
||||||
|
$container-padding: 1rem;
|
||||||
|
|
||||||
|
// Брейкпоинты
|
||||||
|
$breakpoint-sm: 576px;
|
||||||
|
$breakpoint-md: 768px;
|
||||||
|
$breakpoint-lg: 992px;
|
||||||
|
$breakpoint-xl: 1200px;
|
||||||
|
$breakpoint-2xl: 1400px;
|
||||||
|
|
||||||
|
// Z-index
|
||||||
|
$z-dropdown: 1000;
|
||||||
|
$z-modal: 1050;
|
||||||
|
$z-tooltip: 1070;
|
||||||
|
|
||||||
|
// Анимации
|
||||||
|
$transition-base: 0.3s ease;
|
||||||
|
$transition-fast: 0.15s ease;
|
||||||
|
$transition-slow: 0.5s ease;
|
||||||
|
|
||||||
|
// Радиусы
|
||||||
|
$border-radius-sm: 0.25rem;
|
||||||
|
$border-radius: 0.375rem;
|
||||||
|
$border-radius-lg: 0.5rem;
|
||||||
|
$border-radius-xl: 0.75rem;
|
||||||
|
|
||||||
|
// Тени
|
||||||
|
$shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||||
|
$shadow:
|
||||||
|
0 1px 3px 0 rgb(0 0 0 / 0.1),
|
||||||
|
0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||||
|
$shadow-lg:
|
||||||
|
0 10px 15px -3px rgb(0 0 0 / 0.1),
|
||||||
|
0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||||
6
vite-templates/src/styles/components/+.css
Normal file
6
vite-templates/src/styles/components/+.css
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
@import "./_buttons.scss";
|
||||||
|
@import "./_footer.scss";
|
||||||
|
@import "./_header.scss";
|
||||||
|
@import "./_main.scss";
|
||||||
|
@import "./_modal.scss";
|
||||||
|
@import "./_section.scss";
|
||||||
84
vite-templates/src/styles/components/_buttons.scss
Normal file
84
vite-templates/src/styles/components/_buttons.scss
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
@use "sass:color";
|
||||||
|
@use "../variables" as *;
|
||||||
|
@use "../mixins" as *;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
font-size: $font-size-base;
|
||||||
|
font-weight: $font-weight-medium;
|
||||||
|
line-height: 1;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all $transition-base;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: 2px solid transparent;
|
||||||
|
outline-offset: 2px;
|
||||||
|
box-shadow: 0 0 0 2px $primary-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Основная кнопка
|
||||||
|
&--primary {
|
||||||
|
color: white;
|
||||||
|
background-color: $primary;
|
||||||
|
border-color: $primary;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background-color: $primary-dark;
|
||||||
|
border-color: $primary-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вторичная кнопка
|
||||||
|
&--secondary {
|
||||||
|
color: $primary;
|
||||||
|
background-color: transparent;
|
||||||
|
border-color: $primary;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
color: white;
|
||||||
|
background-color: $primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Кнопка успеха
|
||||||
|
&--success {
|
||||||
|
color: white;
|
||||||
|
background-color: $secondary;
|
||||||
|
border-color: $secondary;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background-color: color.adjust($secondary, $lightness: -10%);
|
||||||
|
border-color: color.adjust($secondary, $lightness: -10%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Размеры
|
||||||
|
&--sm {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--lg {
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
font-size: $font-size-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Полная ширина
|
||||||
|
&--block {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
vite-templates/src/styles/components/_footer.scss
Normal file
10
vite-templates/src/styles/components/_footer.scss
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
@use "../variables" as *;
|
||||||
|
@use "../mixins" as *;
|
||||||
|
|
||||||
|
.b-footer {
|
||||||
|
background-color: $bg-dark;
|
||||||
|
color: white;
|
||||||
|
margin-top: auto;
|
||||||
|
padding: 2rem 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
32
vite-templates/src/styles/components/_header.scss
Normal file
32
vite-templates/src/styles/components/_header.scss
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
@use "../variables" as *;
|
||||||
|
@use "../mixins" as *;
|
||||||
|
|
||||||
|
.b-header {
|
||||||
|
background-color: $bg-dark;
|
||||||
|
color: white;
|
||||||
|
padding: 2rem 0;
|
||||||
|
text-align: center;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: $z-dropdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-list {
|
||||||
|
@include flex(row, center, center);
|
||||||
|
gap: 2rem;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
&__link {
|
||||||
|
@include link-reset;
|
||||||
|
font-weight: $font-weight-medium;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
transition: color $transition-base;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&--active {
|
||||||
|
color: $primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
vite-templates/src/styles/components/_main.scss
Normal file
22
vite-templates/src/styles/components/_main.scss
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
.l-wrapper {
|
||||||
|
position: relative;
|
||||||
|
min-height: 1%;
|
||||||
|
height: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100dvh;
|
||||||
|
// overflow: hidden;
|
||||||
|
main {
|
||||||
|
// overflow: hidden;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
-webkit-flex: 1 1 auto;
|
||||||
|
-ms-flex: 1 1 auto;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
-webkit-align-self: stretch;
|
||||||
|
-ms-flex-item-align: stretch;
|
||||||
|
align-self: stretch;
|
||||||
|
}
|
||||||
|
}
|
||||||
100
vite-templates/src/styles/components/_modal.scss
Normal file
100
vite-templates/src/styles/components/_modal.scss
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
@use "../variables" as *;
|
||||||
|
@use "../mixins" as *;
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: $z-modal;
|
||||||
|
// z-index: 1050;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: all $transition-base;
|
||||||
|
|
||||||
|
&--open {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(black, 0.5);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__container {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
max-width: 90vw;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
background: white;
|
||||||
|
border-radius: $border-radius-lg;
|
||||||
|
box-shadow: $shadow-lg;
|
||||||
|
padding: 2rem;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
@include media(md) {
|
||||||
|
padding: 3rem;
|
||||||
|
min-width: 500px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
@include flex(row, space-between, center);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: 1px solid $border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: $font-size-2xl;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__close {
|
||||||
|
@include button-reset;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
@include flex(row, center, center);
|
||||||
|
border-radius: 50%;
|
||||||
|
background: $bg-secondary;
|
||||||
|
color: $text-secondary;
|
||||||
|
font-size: 18px;
|
||||||
|
transition: all $transition-base;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $border-color;
|
||||||
|
color: $text-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__body {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
@include flex(row, flex-end, center);
|
||||||
|
gap: 1rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid $border-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запрет скролла при открытом модальном окне
|
||||||
|
.modal-open {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
5
vite-templates/src/styles/components/_section.scss
Normal file
5
vite-templates/src/styles/components/_section.scss
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
.section {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
padding: 3rem;
|
||||||
|
}
|
||||||
20
vite-templates/src/styles/main.scss
Normal file
20
vite-templates/src/styles/main.scss
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Используем @use вместо @import (современный стандарт)
|
||||||
|
// @use 'sass:color';
|
||||||
|
// @use './variables' as *;
|
||||||
|
// @use './mixins' as *;
|
||||||
|
@use "./base";
|
||||||
|
|
||||||
|
// Компоненты
|
||||||
|
@use "components/main";
|
||||||
|
@use "components/header";
|
||||||
|
@use "components/footer";
|
||||||
|
@use "components/modal";
|
||||||
|
@use "components/section";
|
||||||
|
@use "components/buttons";
|
||||||
|
|
||||||
|
// // Страницы
|
||||||
|
// // @import 'pages/home';
|
||||||
|
// // @import 'pages/about';
|
||||||
|
|
||||||
|
// // Утилиты
|
||||||
|
@use "utilities/text";
|
||||||
1
vite-templates/src/styles/utilities/+.css
Normal file
1
vite-templates/src/styles/utilities/+.css
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
@import "./_text.scss";
|
||||||
3
vite-templates/src/styles/utilities/_text.scss
Normal file
3
vite-templates/src/styles/utilities/_text.scss
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
3
vite-templates/src/twig/components/footer.twig
Normal file
3
vite-templates/src/twig/components/footer.twig
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<footer class="b-footer">
|
||||||
|
copyright © {{ "now"|date("Y") }}
|
||||||
|
</footer>
|
||||||
3
vite-templates/src/twig/components/header.twig
Normal file
3
vite-templates/src/twig/components/header.twig
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<header class="b-header">
|
||||||
|
header...
|
||||||
|
</header>
|
||||||
18
vite-templates/src/twig/components/modal.twig
Normal file
18
vite-templates/src/twig/components/modal.twig
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<div class="modal" id="example-modal">
|
||||||
|
<div class="modal__overlay"></div>
|
||||||
|
<div class="modal__container">
|
||||||
|
<div class="modal__content">
|
||||||
|
<div class="modal__header">
|
||||||
|
<h2 class="modal__title">Заголовок модального окна</h2>
|
||||||
|
<button class="modal__close" data-modal-close>×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal__body">
|
||||||
|
<p>Содержимое модального окна...</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal__footer">
|
||||||
|
<button class="btn btn--secondary" data-modal-close>Отмена</button>
|
||||||
|
<button class="btn btn--primary">Сохранить</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
34
vite-templates/src/twig/layouts/main.twig
Normal file
34
vite-templates/src/twig/layouts/main.twig
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{ lang | default('ru') }}">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{ title | default('Мой сайт') }}</title>
|
||||||
|
<link rel="stylesheet" href="/src/styles/main.scss">
|
||||||
|
{% block head %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body class="{% block body_class %}{% endblock %}">
|
||||||
|
|
||||||
|
{% block modal %}
|
||||||
|
{% include '../components/modal.twig' %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<div class="l-wrapper">
|
||||||
|
{% block header %}
|
||||||
|
{% include '../components/header.twig' %}
|
||||||
|
{%endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
<main class="main">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{% block footer %}
|
||||||
|
{% include '../components/footer.twig' %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<script type="module" src="/src/js/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
30
vite-templates/src/twig/pages/about.twig
Normal file
30
vite-templates/src/twig/pages/about.twig
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
{% extends 'layouts/main.twig' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="about">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="about__title">{{ data.content.about.title }}</h1>
|
||||||
|
<div class="about__content">
|
||||||
|
<div class="about__text">
|
||||||
|
<p>{{ data.content.about.description }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="about__image">
|
||||||
|
<img src="/src/images/about-hero.jpg" alt="О нас">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="team">
|
||||||
|
<h2 class="team__title">Наша команда</h2>
|
||||||
|
<div class="team__grid">
|
||||||
|
{% for member in data.content.team %}
|
||||||
|
<div class="team-card">
|
||||||
|
<img class="team-card__photo" src="/src/images/team/{{ member.photo }}" alt="{{ member.name }}">
|
||||||
|
<h3 class="team-card__name">{{ member.name }}</h3>
|
||||||
|
<p class="team-card__position">{{ member.position }}</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
13
vite-templates/src/twig/pages/demo.twig
Normal file
13
vite-templates/src/twig/pages/demo.twig
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{% extends 'layouts/main.twig' %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="section text-center">
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<h1 class="sectiont__title">Пример модального окна</h1>
|
||||||
|
<button data-modal-open="example-modal">Открыть модальное окно</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
12
vite-templates/src/twig/pages/index.twig
Normal file
12
vite-templates/src/twig/pages/index.twig
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{% extends 'layouts/main.twig' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="section text-center">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="section__title">Главная страница</h1>
|
||||||
|
<h1 class="sectiont__title">Пример модального окна</h1>
|
||||||
|
<button data-modal-open="example-modal">Открыть модальное окно</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
168
vite-templates/vite.config copy.js
Normal file
168
vite-templates/vite.config copy.js
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import vituum from 'vituum';
|
||||||
|
import pug from '@vituum/vite-plugin-pug';
|
||||||
|
import twig from '@vituum/vite-plugin-twig';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import { glob } from 'glob';
|
||||||
|
import basicSsl from '@vitejs/plugin-basic-ssl';
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason, promise) => {
|
||||||
|
console.warn('⚠️ Unhandled Promise Rejection:', reason);
|
||||||
|
// Не останавливать процесс - просто логировать
|
||||||
|
});
|
||||||
|
|
||||||
|
// Автоматическое определение всех Twig страниц
|
||||||
|
const getTwigPages = () => {
|
||||||
|
const pages = glob.sync('src/twig/pages/**/*.twig');
|
||||||
|
const input = {};
|
||||||
|
|
||||||
|
pages.forEach(page => {
|
||||||
|
const name = page
|
||||||
|
.replace('src/twig/pages/', '')
|
||||||
|
.replace('.twig', '')
|
||||||
|
.replace(/\//g, '-'); // Заменяем слеши на дефисы для вложенных папок
|
||||||
|
|
||||||
|
input[name] = resolve(__dirname, page);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('📄 Найденные страницы для сборки:', Object.keys(input));
|
||||||
|
|
||||||
|
return input;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vituum({
|
||||||
|
pages: {
|
||||||
|
dir: './src/twig/pages', // Указываем правильный путь к Twig страницам
|
||||||
|
formats: ['twig']
|
||||||
|
},
|
||||||
|
// Настройки для стабильной работы
|
||||||
|
options: {
|
||||||
|
reload: true,
|
||||||
|
verbose: true
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Twig конфигурация
|
||||||
|
twig({
|
||||||
|
root: './src/twig',
|
||||||
|
namespaces: {
|
||||||
|
layouts: './src/twig/layouts',
|
||||||
|
components: './src/twig/components',
|
||||||
|
data: './src/data'
|
||||||
|
},
|
||||||
|
// Более мягкие настройки для разработки
|
||||||
|
options: {
|
||||||
|
autoescape: false,
|
||||||
|
strict_vars: false
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Pug для других страниц (если нужно)
|
||||||
|
pug({
|
||||||
|
root: './src',
|
||||||
|
options: {
|
||||||
|
pretty: true,
|
||||||
|
basedir: './src'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
basicSsl()
|
||||||
|
],
|
||||||
|
|
||||||
|
server: {
|
||||||
|
host: true,
|
||||||
|
port: 3000,
|
||||||
|
https: true,
|
||||||
|
open: true,
|
||||||
|
// Настройки для стабильной работы HMR
|
||||||
|
hmr: {
|
||||||
|
overlay: true
|
||||||
|
},
|
||||||
|
// Не останавливать сервер при ошибках
|
||||||
|
middlewareMode: false
|
||||||
|
},
|
||||||
|
|
||||||
|
// Настройки сборки
|
||||||
|
build: {
|
||||||
|
outDir: 'dist',
|
||||||
|
assetsDir: 'assets',
|
||||||
|
sourcemap: false, // Отключаем sourcemaps для чистоты
|
||||||
|
|
||||||
|
rollupOptions: {
|
||||||
|
input: getTwigPages(), // Автоматически находим все страницы
|
||||||
|
output: {
|
||||||
|
// Группируем CSS в один файл
|
||||||
|
assetFileNames: (assetInfo) => {
|
||||||
|
const info = assetInfo.name.split('.');
|
||||||
|
const ext = info[info.length - 1];
|
||||||
|
|
||||||
|
if (ext === 'css') {
|
||||||
|
return 'assets/css/style-[hash].css';
|
||||||
|
}
|
||||||
|
if (/\.(png|jpe?g|svg|gif|tiff|bmp|ico)$/i.test(assetInfo.name)) {
|
||||||
|
return 'assets/images/[name]-[hash][extname]';
|
||||||
|
}
|
||||||
|
if (/\.(woff2?|eot|ttf|otf)$/i.test(assetInfo.name)) {
|
||||||
|
return 'assets/fonts/[name]-[hash][extname]';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'assets/[name]-[hash][extname]';
|
||||||
|
},
|
||||||
|
|
||||||
|
// Группируем JS
|
||||||
|
chunkFileNames: 'assets/js/[name]-[hash].js',
|
||||||
|
entryFileNames: 'assets/js/[name]-[hash].js'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Минификация
|
||||||
|
minify: 'terser',
|
||||||
|
terserOptions: {
|
||||||
|
compress: {
|
||||||
|
drop_console: true,
|
||||||
|
drop_debugger: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Минимальная обработка ошибок
|
||||||
|
esbuild: {
|
||||||
|
// Продолжать работу при ошибках
|
||||||
|
logOverride: { 'this-is-undefined-in-esm': 'silent' }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Обработка ошибок
|
||||||
|
optimizeDeps: {
|
||||||
|
force: true
|
||||||
|
},
|
||||||
|
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
silenceDeprecations: ['legacy-js-api']
|
||||||
|
// silenceDeprecations: ['legacy-js-api', 'import', 'global-builtin', 'color-functions'],
|
||||||
|
// quietDeps: true,
|
||||||
|
// logger: {
|
||||||
|
// warn: () => {}
|
||||||
|
// }
|
||||||
|
// additionalData: `
|
||||||
|
// @import "./src/styles/_vars.scss";
|
||||||
|
// @import "./src/styles/_scss";
|
||||||
|
// `
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': resolve(process.cwd(), 'src'),
|
||||||
|
'@twig': resolve(process.cwd(), 'src/twig'),
|
||||||
|
'@styles': resolve(process.cwd(), 'src/styles'),
|
||||||
|
'@js': resolve(process.cwd(), 'src/js'),
|
||||||
|
'@images': resolve(process.cwd(), 'src/images'),
|
||||||
|
'@data': resolve(process.cwd(), 'src/data')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
143
vite-templates/vite.config.js
Normal file
143
vite-templates/vite.config.js
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import vituum from 'vituum';
|
||||||
|
import pug from '@vituum/vite-plugin-pug';
|
||||||
|
import twig from '@vituum/vite-plugin-twig';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import basicSsl from '@vitejs/plugin-basic-ssl';
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason, promise) => {
|
||||||
|
console.warn('⚠️ Unhandled Promise Rejection:', reason);
|
||||||
|
// Не останавливать процесс - просто логировать
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vituum({
|
||||||
|
pages: {
|
||||||
|
dir: './src/twig/pages', // Указываем правильный путь к Twig страницам
|
||||||
|
formats: ['twig']
|
||||||
|
},
|
||||||
|
// Настройки для стабильной работы
|
||||||
|
options: {
|
||||||
|
reload: true,
|
||||||
|
verbose: true
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Twig конфигурация
|
||||||
|
twig({
|
||||||
|
root: './src/twig',
|
||||||
|
namespaces: {
|
||||||
|
layouts: './src/twig/layouts',
|
||||||
|
components: './src/twig/components',
|
||||||
|
data: './src/data'
|
||||||
|
},
|
||||||
|
// Более мягкие настройки для разработки
|
||||||
|
options: {
|
||||||
|
autoescape: false,
|
||||||
|
strict_vars: false
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Pug для других страниц (если нужно)
|
||||||
|
pug({
|
||||||
|
root: './src',
|
||||||
|
options: {
|
||||||
|
pretty: true,
|
||||||
|
basedir: './src'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
basicSsl()
|
||||||
|
],
|
||||||
|
|
||||||
|
server: {
|
||||||
|
host: true,
|
||||||
|
port: 3000,
|
||||||
|
https: true,
|
||||||
|
open: true,
|
||||||
|
// Настройки для стабильной работы HMR
|
||||||
|
hmr: {
|
||||||
|
overlay: true
|
||||||
|
},
|
||||||
|
// Не останавливать сервер при ошибках
|
||||||
|
middlewareMode: false
|
||||||
|
},
|
||||||
|
|
||||||
|
// Настройки сборки
|
||||||
|
build: {
|
||||||
|
outDir: 'dist',
|
||||||
|
assetsDir: 'assets',
|
||||||
|
sourcemap: false, // Отключаем sourcemaps для чистоты
|
||||||
|
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
// Группируем CSS в один файл
|
||||||
|
assetFileNames: (assetInfo) => {
|
||||||
|
const info = assetInfo.name.split('.');
|
||||||
|
const ext = info[info.length - 1];
|
||||||
|
|
||||||
|
if (ext === 'css') {
|
||||||
|
return 'assets/css/style-[hash].css'; // Один CSS файл
|
||||||
|
}
|
||||||
|
if (/\.(png|jpe?g|svg|gif|tiff|bmp|ico)$/i.test(assetInfo.name)) {
|
||||||
|
return 'assets/images/[name]-[hash][extname]';
|
||||||
|
}
|
||||||
|
if (/\.(woff2?|eot|ttf|otf)$/i.test(assetInfo.name)) {
|
||||||
|
return 'assets/fonts/[name]-[hash][extname]';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'assets/[name]-[hash][extname]';
|
||||||
|
},
|
||||||
|
|
||||||
|
// Группируем JS
|
||||||
|
chunkFileNames: 'assets/js/[name]-[hash].js',
|
||||||
|
entryFileNames: 'assets/js/[name]-[hash].js'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Минификация
|
||||||
|
minify: 'terser',
|
||||||
|
terserOptions: {
|
||||||
|
compress: {
|
||||||
|
drop_console: true,
|
||||||
|
drop_debugger: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Минимальная обработка ошибок
|
||||||
|
esbuild: {
|
||||||
|
// Продолжать работу при ошибках
|
||||||
|
logOverride: { 'this-is-undefined-in-esm': 'silent' }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Обработка ошибок
|
||||||
|
optimizeDeps: {
|
||||||
|
force: true
|
||||||
|
},
|
||||||
|
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
silenceDeprecations: ['legacy-js-api']
|
||||||
|
// additionalData: `
|
||||||
|
// @import "./src/styles/_vars.scss";
|
||||||
|
// @import "./src/styles/_scss";
|
||||||
|
// `
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': resolve(process.cwd(), 'src'),
|
||||||
|
'@twig': resolve(process.cwd(), 'src/twig'),
|
||||||
|
'@styles': resolve(process.cwd(), 'src/styles'),
|
||||||
|
'@js': resolve(process.cwd(), 'src/js'),
|
||||||
|
'@images': resolve(process.cwd(), 'src/images'),
|
||||||
|
'@data': resolve(process.cwd(), 'src/data')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
50
vite-templates/vite.config.simple.js
Normal file
50
vite-templates/vite.config.simple.js
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import vituum from 'vituum';
|
||||||
|
import pug from '@vituum/vite-plugin-pug';
|
||||||
|
import twig from '@vituum/vite-plugin-twig';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import basicSsl from '@vitejs/plugin-basic-ssl';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vituum(),
|
||||||
|
|
||||||
|
pug({
|
||||||
|
root: './src'
|
||||||
|
}),
|
||||||
|
|
||||||
|
twig({
|
||||||
|
root: './src',
|
||||||
|
namespaces: {
|
||||||
|
layouts: './src/layouts',
|
||||||
|
components: './src/components'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
basicSsl()
|
||||||
|
],
|
||||||
|
|
||||||
|
server: {
|
||||||
|
host: true,
|
||||||
|
port: 3000,
|
||||||
|
https: true,
|
||||||
|
open: true
|
||||||
|
},
|
||||||
|
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
additionalData: `
|
||||||
|
@import "./src/styles/_vars.scss";
|
||||||
|
@import "./src/styles/_scss";
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': resolve(process.cwd(), 'src')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
31
vitekit-claude/.eslintrc.cjs
Normal file
31
vitekit-claude/.eslintrc.cjs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2021: true,
|
||||||
|
node: true
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'prettier'
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module'
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-console': 'warn',
|
||||||
|
'no-unused-vars': 'warn',
|
||||||
|
'prefer-const': 'error',
|
||||||
|
'no-var': 'error',
|
||||||
|
'object-shorthand': 'error',
|
||||||
|
'prefer-arrow-callback': 'error',
|
||||||
|
'prefer-template': 'error',
|
||||||
|
'template-curly-spacing': 'error',
|
||||||
|
'quotes': ['error', 'single'],
|
||||||
|
'semi': ['error', 'always']
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
MicroModal: 'readonly',
|
||||||
|
Toastify: 'readonly'
|
||||||
|
}
|
||||||
|
};
|
||||||
91
vitekit-claude/.gitignore
vendored
Normal file
91
vitekit-claude/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# Production builds
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Development
|
||||||
|
.cache/
|
||||||
|
.parcel-cache/
|
||||||
|
.vite/
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage/
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# ESLint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# IDE files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS generated files
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Temporary folders
|
||||||
|
tmp/
|
||||||
|
temp/
|
||||||
19
vitekit-claude/.prettierrc
Normal file
19
vitekit-claude/.prettierrc
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 80,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.twig",
|
||||||
|
"options": {
|
||||||
|
"parser": "html"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
32
vitekit-claude/.stylelintrc.cjs
Normal file
32
vitekit-claude/.stylelintrc.cjs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
module.exports = {
|
||||||
|
extends: [
|
||||||
|
'stylelint-config-standard-scss'
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
'stylelint-order'
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
'order/properties-alphabetical-order': true,
|
||||||
|
'scss/at-import-partial-extension': null,
|
||||||
|
'scss/at-import-no-partial-leading-underscore': null,
|
||||||
|
'selector-class-pattern': null,
|
||||||
|
'custom-property-pattern': null,
|
||||||
|
'keyframes-name-pattern': null,
|
||||||
|
'scss/dollar-variable-pattern': null,
|
||||||
|
'scss/percent-placeholder-pattern': null,
|
||||||
|
'scss/at-mixin-pattern': null,
|
||||||
|
'scss/at-function-pattern': null,
|
||||||
|
'declaration-empty-line-before': null,
|
||||||
|
'rule-empty-line-before': [
|
||||||
|
'always-multi-line',
|
||||||
|
{
|
||||||
|
except: ['first-nested'],
|
||||||
|
ignore: ['after-comment']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
ignoreFiles: [
|
||||||
|
'dist/**/*',
|
||||||
|
'node_modules/**/*'
|
||||||
|
]
|
||||||
|
};
|
||||||
190
vitekit-claude/CLAUDE.md
Normal file
190
vitekit-claude/CLAUDE.md
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
ViteKit Universal - современный универсальный сборщик проектов с Vite + Twig + компонентами. Полнофункциональная система для быстрой разработки веб-приложений с готовыми UI компонентами и модульной архитектурой.
|
||||||
|
|
||||||
|
## Common Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development
|
||||||
|
npm run dev # Запуск сервера разработки (localhost:3000)
|
||||||
|
npm run build # Сборка для продакшена
|
||||||
|
npm run preview # Предварительный просмотр сборки
|
||||||
|
|
||||||
|
# Code Quality
|
||||||
|
npm run lint # ESLint проверка JavaScript
|
||||||
|
npm run lint:fix # Автоисправление ESLint
|
||||||
|
npm run lint:css # Stylelint проверка SCSS
|
||||||
|
npm run lint:css:fix # Автоисправление Stylelint
|
||||||
|
npm run format # Prettier форматирование всех файлов
|
||||||
|
|
||||||
|
# Utilities
|
||||||
|
npm run clean # Очистка папки dist
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Architecture
|
||||||
|
|
||||||
|
### Technology Stack
|
||||||
|
- **Vite** - быстрый сборщик с HMR
|
||||||
|
- **@vituum/vite-plugin-twig** - поддержка Twig шаблонов
|
||||||
|
- **SCSS** - CSS препроцессор с модульной архитектурой
|
||||||
|
- **Vanilla JavaScript** - нативный JS без фреймворков
|
||||||
|
- **MicroModal** - доступные модальные окна (~1.9kb)
|
||||||
|
- **Toastify** - уведомления
|
||||||
|
|
||||||
|
### Directory Structure
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── components/ # UI компоненты (modal, tabs, accordion, toast)
|
||||||
|
├── layouts/ # Twig шаблоны (base.twig)
|
||||||
|
├── pages/ # Страницы (index.twig, components-demo.twig)
|
||||||
|
├── data/ # JSON данные для Twig
|
||||||
|
├── assets/ # Статические ресурсы (images, icons, fonts)
|
||||||
|
├── styles/ # SCSS архитектура
|
||||||
|
│ ├── abstracts/ # Variables, mixins
|
||||||
|
│ ├── base/ # Reset, typography
|
||||||
|
│ ├── components/ # Component styles
|
||||||
|
│ ├── layout/ # Layout styles
|
||||||
|
│ └── pages/ # Page-specific styles
|
||||||
|
└── scripts/ # JavaScript модули
|
||||||
|
├── components/ # UI component logic
|
||||||
|
└── utils/ # Utilities and helpers
|
||||||
|
```
|
||||||
|
|
||||||
|
### Component System
|
||||||
|
- **Модульная архитектура** - каждый компонент изолирован
|
||||||
|
- **Data attributes** - инициализация через `data-*` атрибуты
|
||||||
|
- **Event-driven** - компоненты используют custom events
|
||||||
|
- **Accessibility** - полная поддержка ARIA и клавиатурной навигации
|
||||||
|
- **Responsive** - адаптивное поведение (табы → аккордеон на мобильных)
|
||||||
|
|
||||||
|
### SCSS Architecture
|
||||||
|
- **Abstracts**: переменные, миксины, функции
|
||||||
|
- **Base**: reset, типографика, базовые стили
|
||||||
|
- **Components**: стили UI компонентов
|
||||||
|
- **Layout**: сетка, контейнеры, навигация
|
||||||
|
- **Pages**: специфичные стили страниц
|
||||||
|
|
||||||
|
### Key Features
|
||||||
|
- Автоматическая оптимизация изображений
|
||||||
|
- SVG sprite injection для иконок
|
||||||
|
- Live reload и HMR
|
||||||
|
- Code splitting и tree shaking
|
||||||
|
- Legacy browser support
|
||||||
|
- Automated linting с pre-commit hooks
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### Adding New Components
|
||||||
|
1. Создать SCSS в `src/styles/components/_component.scss`
|
||||||
|
2. Создать JS в `src/scripts/components/component.js`
|
||||||
|
3. Добавить импорт в `src/styles/main.scss`
|
||||||
|
4. Добавить в компонентную систему через data attributes
|
||||||
|
|
||||||
|
### Twig Development
|
||||||
|
- Использовать JSON данные из `src/data/` для динамического контента
|
||||||
|
- Шаблоны наследуются от `layouts/base.twig`
|
||||||
|
- Все страницы автоматически обрабатываются Vite
|
||||||
|
|
||||||
|
### SCSS Best Practices
|
||||||
|
- Использовать предопределенные миксины для медиа-запросов
|
||||||
|
- Следовать БЭМ методологии для классов
|
||||||
|
- Использовать CSS custom properties для динамических значений
|
||||||
|
- Все цвета и размеры через переменные
|
||||||
|
|
||||||
|
### JavaScript Guidelines
|
||||||
|
- ES6+ modules с импортами
|
||||||
|
- Page Controller Pattern для архитектуры
|
||||||
|
- Event-driven компоненты через EventBus
|
||||||
|
- Класс-ориентированные компоненты наследуются от Component
|
||||||
|
- Performance monitoring встроен
|
||||||
|
- Graceful degradation без JavaScript
|
||||||
|
|
||||||
|
## Testing & Deployment
|
||||||
|
|
||||||
|
### Quality Assurance
|
||||||
|
- ESLint + Prettier для JavaScript
|
||||||
|
- Stylelint для SCSS
|
||||||
|
- Husky pre-commit hooks
|
||||||
|
- Автоматическое форматирование
|
||||||
|
|
||||||
|
### Build Process
|
||||||
|
- Vite handles bundling and optimization
|
||||||
|
- Automatic asset optimization (images, SVG)
|
||||||
|
- CSS/JS minification and tree shaking
|
||||||
|
- Legacy browser polyfills via @vitejs/plugin-legacy
|
||||||
|
|
||||||
|
### Performance Optimization
|
||||||
|
- Image optimization с vite-plugin-image-optimizer
|
||||||
|
- SVG sprite generation для иконок
|
||||||
|
- CSS/JS code splitting
|
||||||
|
- Preload critical resources
|
||||||
|
|
||||||
|
## Page Controller Architecture
|
||||||
|
|
||||||
|
### Создание нового page controller
|
||||||
|
```javascript
|
||||||
|
// src/scripts/pages/mypage.js
|
||||||
|
import { Component } from '../core/Component.js';
|
||||||
|
import { eventBus } from '../core/EventBus.js';
|
||||||
|
|
||||||
|
export default class MyPage extends Component {
|
||||||
|
constructor() {
|
||||||
|
super(document.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeInit() {
|
||||||
|
// Инициализация перед загрузкой
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEvents() {
|
||||||
|
// Привязка событий страницы
|
||||||
|
}
|
||||||
|
|
||||||
|
afterInit() {
|
||||||
|
// Действия после инициализации
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Page detection
|
||||||
|
- Автоматическое определение по filename (mypage.html → mypage.js)
|
||||||
|
- Или через data-page атрибут: `<body data-page="custom-name">`
|
||||||
|
|
||||||
|
### Component API
|
||||||
|
```javascript
|
||||||
|
// Создание компонента
|
||||||
|
const myComponent = Component.create(element, options);
|
||||||
|
|
||||||
|
// EventBus для связи между компонентами
|
||||||
|
eventBus.on('custom:event', callback);
|
||||||
|
eventBus.emit('custom:event', data);
|
||||||
|
|
||||||
|
// Performance monitoring
|
||||||
|
performanceMonitor.mark('my-action');
|
||||||
|
performanceMonitor.measure('my-action', 'start-mark', 'end-mark');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Utilities
|
||||||
|
```javascript
|
||||||
|
// DOM utilities
|
||||||
|
import { ready, createElement, scrollToElement } from '@scripts/utils/dom.js';
|
||||||
|
|
||||||
|
// Performance utilities
|
||||||
|
import { debounce, throttle, memoize } from '@scripts/utils/performance.js';
|
||||||
|
|
||||||
|
// Helper utilities
|
||||||
|
import { deepClone, formatDate, copyToClipboard } from '@scripts/utils/helpers.js';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Special Considerations
|
||||||
|
|
||||||
|
- Команды терминала запускаются в фоновом режиме (не используйте циклические команды)
|
||||||
|
- Все пути используют алиасы (@, @styles, @components, @scripts, @assets)
|
||||||
|
- Responsive-first подход с мобильными брейкпоинтами
|
||||||
|
- Accessibility-first разработка с ARIA и keyboard support
|
||||||
|
- Page controllers загружаются автоматически по имени файла/страницы
|
||||||
|
- Используйте EventBus для связи между компонентами разных страниц
|
||||||
290
vitekit-claude/README.md
Normal file
290
vitekit-claude/README.md
Normal file
|
|
@ -0,0 +1,290 @@
|
||||||
|
# ViteKit Universal
|
||||||
|
|
||||||
|
Современный универсальный сборщик проектов с Vite + Twig + компонентами для быстрой разработки веб-приложений.
|
||||||
|
|
||||||
|
## 🚀 Особенности
|
||||||
|
|
||||||
|
- **Быстрая сборка** с Vite
|
||||||
|
- **Шаблонизация** с Twig
|
||||||
|
- **Готовые UI компоненты** (модалы, табы, аккордеон, уведомления)
|
||||||
|
- **Адаптивная верстка** с SCSS и Flexbox/Grid
|
||||||
|
- **Оптимизация изображений** и SVG спрайты
|
||||||
|
- **Качество кода** с ESLint, Prettier, Stylelint
|
||||||
|
- **Модульная архитектура**
|
||||||
|
|
||||||
|
## 📦 Установка и запуск
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Установка зависимостей
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Запуск сервера разработки
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Сборка для продакшена
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Предварительный просмотр сборки
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🏗️ Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── components/ # UI компоненты
|
||||||
|
│ ├── ui/ # Базовые компоненты
|
||||||
|
│ └── blocks/ # Блоки страниц
|
||||||
|
├── layouts/ # Шаблоны страниц
|
||||||
|
├── pages/ # Страницы Twig
|
||||||
|
├── data/ # JSON данные для Twig
|
||||||
|
├── assets/ # Статические ресурсы
|
||||||
|
│ ├── images/ # Изображения
|
||||||
|
│ ├── icons/ # SVG иконки для спрайтов
|
||||||
|
│ └── fonts/ # Шрифты
|
||||||
|
├── styles/ # SCSS стили
|
||||||
|
│ ├── abstracts/ # Переменные, миксины
|
||||||
|
│ ├── base/ # Сброс стилей, типографика
|
||||||
|
│ ├── components/ # Стили компонентов
|
||||||
|
│ ├── layout/ # Стили макета
|
||||||
|
│ └── pages/ # Стили страниц
|
||||||
|
└── scripts/ # JavaScript
|
||||||
|
├── components/ # JS компоненты
|
||||||
|
└── utils/ # Утилиты
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 Компоненты
|
||||||
|
|
||||||
|
### Toast Уведомления ✨
|
||||||
|
- **Исправлены стили позиционирования** - правильный z-index и анимации
|
||||||
|
- **Автоматическое скрытие** через настраиваемое время
|
||||||
|
- **Адаптивность** для мобильных устройств
|
||||||
|
- **Типизация**: success, error, warning, info
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
ToastComponent.success('Операция успешна!', {
|
||||||
|
title: 'Готово!',
|
||||||
|
duration: 4000
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Модальные окна (MicroModal)
|
||||||
|
- Доступные модальные окна с ARIA
|
||||||
|
- Анимации открытия/закрытия
|
||||||
|
- Поддержка клавиатуры и фокуса
|
||||||
|
|
||||||
|
```html
|
||||||
|
<button data-micromodal-trigger="modal-id">Открыть модал</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Табы (Адаптивные)
|
||||||
|
- **Автоматическое превращение в аккордеон** на мобильных
|
||||||
|
- Клавиатурная навигация (Arrow keys, Home, End)
|
||||||
|
- Плавные анимации переключения
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="tabs" data-tabs>
|
||||||
|
<div class="tabs__nav">
|
||||||
|
<button class="tabs__tab tabs__tab--active">Вкладка 1</button>
|
||||||
|
</div>
|
||||||
|
<div class="tabs__content">
|
||||||
|
<div class="tabs__panel tabs__panel--active">Содержимое</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Аккордеон
|
||||||
|
- Плавные CSS анимации с автоматическим расчетом высоты
|
||||||
|
- Поддержка множественного раскрытия
|
||||||
|
- Полная клавиатурная навигация и ARIA
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="accordion" data-accordion>
|
||||||
|
<div class="accordion__item">
|
||||||
|
<button class="accordion__header">Заголовок</button>
|
||||||
|
<div class="accordion__content">Содержимое</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎨 SCSS Утилиты
|
||||||
|
|
||||||
|
### Миксины для медиа-запросов
|
||||||
|
```scss
|
||||||
|
@include media-up('md') { /* >= 768px */ }
|
||||||
|
@include media-down('lg') { /* < 992px */ }
|
||||||
|
@include media-only('sm') { /* только для small */ }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flexbox утилиты
|
||||||
|
```scss
|
||||||
|
@include flex-center; // выравнивание по центру
|
||||||
|
@include flex-between; // space-between
|
||||||
|
@include flex-column; // flex-direction: column
|
||||||
|
```
|
||||||
|
|
||||||
|
### Grid утилиты
|
||||||
|
```scss
|
||||||
|
@include grid-container(12, $spacing-base); // 12 колонок
|
||||||
|
@include grid-column(6); // span 6 колонок
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Конфигурация
|
||||||
|
|
||||||
|
### Vite
|
||||||
|
Настроен для работы с:
|
||||||
|
- Twig шаблонами
|
||||||
|
- SCSS с автоимпортом переменных
|
||||||
|
- Оптимизацией изображений
|
||||||
|
- SVG спрайтами
|
||||||
|
- Legacy поддержкой для старых браузеров
|
||||||
|
|
||||||
|
### Linting
|
||||||
|
- **ESLint** для JavaScript
|
||||||
|
- **Stylelint** для SCSS
|
||||||
|
- **Prettier** для форматирования
|
||||||
|
- **Husky** для pre-commit хуков
|
||||||
|
|
||||||
|
## 📱 Адаптивность
|
||||||
|
|
||||||
|
Проект использует mobile-first подход с брейкпоинтами:
|
||||||
|
- `sm`: 576px
|
||||||
|
- `md`: 768px
|
||||||
|
- `lg`: 992px
|
||||||
|
- `xl`: 1200px
|
||||||
|
- `2xl`: 1400px
|
||||||
|
|
||||||
|
## 🏗️ Page Controller Архитектура
|
||||||
|
|
||||||
|
ViteKit использует **Page Controller Pattern** для организации кода по страницам:
|
||||||
|
|
||||||
|
### Автоматическая загрузка
|
||||||
|
```javascript
|
||||||
|
// Файл: src/scripts/pages/about.js
|
||||||
|
// Загружается автоматически для about.html
|
||||||
|
|
||||||
|
export default class AboutPage extends Component {
|
||||||
|
constructor() {
|
||||||
|
super(document.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeInit() {
|
||||||
|
// Логика до инициализации
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEvents() {
|
||||||
|
// События страницы
|
||||||
|
this.addEventListener('click', this.handleClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
afterInit() {
|
||||||
|
// Логика после инициализации
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### EventBus для связи компонентов
|
||||||
|
```javascript
|
||||||
|
import { eventBus } from '../core/EventBus.js';
|
||||||
|
|
||||||
|
// Подписка на события
|
||||||
|
eventBus.on('user:login', this.onUserLogin.bind(this));
|
||||||
|
|
||||||
|
// Испускание событий
|
||||||
|
eventBus.emit('page:ready', { page: 'home' });
|
||||||
|
```
|
||||||
|
|
||||||
|
### Утилиты производительности
|
||||||
|
```javascript
|
||||||
|
import { debounce, throttle, memoize } from '@scripts/utils/performance.js';
|
||||||
|
|
||||||
|
// Debounce для поиска
|
||||||
|
const search = debounce(this.performSearch.bind(this), 300);
|
||||||
|
|
||||||
|
// Throttle для скролла
|
||||||
|
const onScroll = throttle(this.updateScrollPosition.bind(this), 16);
|
||||||
|
|
||||||
|
// Мемоизация тяжелых вычислений
|
||||||
|
const expensiveCalculation = memoize(this.calculate.bind(this));
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎛️ API компонентов
|
||||||
|
|
||||||
|
### TabsComponent
|
||||||
|
```javascript
|
||||||
|
const tabs = new TabsComponent(element, {
|
||||||
|
activeClass: 'tabs__tab--active',
|
||||||
|
keyboardNavigation: true,
|
||||||
|
autoResponsive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
tabs.next(); // следующая вкладка
|
||||||
|
tabs.prev(); // предыдущая вкладка
|
||||||
|
tabs.goTo(2); // перейти к вкладке по индексу
|
||||||
|
```
|
||||||
|
|
||||||
|
### AccordionComponent
|
||||||
|
```javascript
|
||||||
|
const accordion = new AccordionComponent(element, {
|
||||||
|
allowMultiple: false,
|
||||||
|
animationDuration: 300,
|
||||||
|
keyboardNavigation: true
|
||||||
|
});
|
||||||
|
|
||||||
|
accordion.open(0); // открыть первый элемент
|
||||||
|
accordion.close(0); // закрыть первый элемент
|
||||||
|
accordion.closeAll(); // закрыть все элементы
|
||||||
|
```
|
||||||
|
|
||||||
|
### ToastComponent
|
||||||
|
```javascript
|
||||||
|
// Базовые методы
|
||||||
|
ToastComponent.success('Успех!');
|
||||||
|
ToastComponent.error('Ошибка!');
|
||||||
|
ToastComponent.warning('Предупреждение!');
|
||||||
|
ToastComponent.info('Информация');
|
||||||
|
|
||||||
|
// Расширенные возможности
|
||||||
|
ToastComponent.promise(fetchData(), {
|
||||||
|
loading: 'Загрузка...',
|
||||||
|
success: 'Данные загружены!',
|
||||||
|
error: 'Ошибка загрузки'
|
||||||
|
});
|
||||||
|
|
||||||
|
ToastComponent.confirm('Удалить элемент?').then(confirmed => {
|
||||||
|
if (confirmed) {
|
||||||
|
// Действие подтверждено
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Развертывание
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Сборка для продакшена
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Файлы сборки будут в папке dist/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🤝 Вклад в проект
|
||||||
|
|
||||||
|
1. Fork проект
|
||||||
|
2. Создайте feature ветку
|
||||||
|
3. Commit изменения
|
||||||
|
4. Push в ветку
|
||||||
|
5. Создайте Pull Request
|
||||||
|
|
||||||
|
## 📝 Лицензия
|
||||||
|
|
||||||
|
MIT License - смотри файл [LICENSE](LICENSE) для деталей.
|
||||||
|
|
||||||
|
## 🛠️ Технологии
|
||||||
|
|
||||||
|
- [Vite](https://vitejs.dev/) - Сборщик
|
||||||
|
- [Twig](https://twig.symfony.com/) - Шаблонизатор
|
||||||
|
- [SCSS](https://sass-lang.com/) - CSS препроцессор
|
||||||
|
- [MicroModal](https://micromodal.vercel.app/) - Модальные окна
|
||||||
|
- [Toastify](https://apvarun.github.io/toastify-js/) - Уведомления
|
||||||
|
- [ESLint](https://eslint.org/) - Линтер JavaScript
|
||||||
|
- [Prettier](https://prettier.io/) - Форматирование кода
|
||||||
234
vitekit-claude/components-demo.html
Normal file
234
vitekit-claude/components-demo.html
Normal file
|
|
@ -0,0 +1,234 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Демонстрация компонентов - ViteKit Universal</title>
|
||||||
|
<link rel="stylesheet" href="/src/styles/main.scss">
|
||||||
|
</head>
|
||||||
|
<body data-page="showcase">
|
||||||
|
<header class="header">
|
||||||
|
<div class="container">
|
||||||
|
<nav class="nav">
|
||||||
|
<div class="nav__brand">
|
||||||
|
<a href="/" class="nav__logo">ViteKit Universal</a>
|
||||||
|
</div>
|
||||||
|
<ul class="nav__menu">
|
||||||
|
<li class="nav__item">
|
||||||
|
<a href="/" class="nav__link">Главная</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav__item">
|
||||||
|
<a href="/components-demo.html" class="nav__link nav__link--active">Компоненты</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="main">
|
||||||
|
<div class="demo-header">
|
||||||
|
<div class="container">
|
||||||
|
<h1>Демонстрация компонентов</h1>
|
||||||
|
<p>Интерактивные примеры всех доступных UI компонентов</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Toast Уведомления -->
|
||||||
|
<section class="showcase-section">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Toast Уведомления</h2>
|
||||||
|
<p>Современные всплывающие уведомления с автоматическим скрытием</p>
|
||||||
|
|
||||||
|
<div class="demo-grid">
|
||||||
|
<button class="btn btn--primary" data-toast-trigger="success"
|
||||||
|
data-toast-title="Успех!" data-toast-message="Операция выполнена успешно">
|
||||||
|
Показать Success
|
||||||
|
</button>
|
||||||
|
<button class="btn btn--danger" data-toast-trigger="error"
|
||||||
|
data-toast-title="Ошибка!" data-toast-message="Что-то пошло не так">
|
||||||
|
Показать Error
|
||||||
|
</button>
|
||||||
|
<button class="btn btn--warning" data-toast-trigger="warning"
|
||||||
|
data-toast-title="Внимание!" data-toast-message="Будьте осторожны">
|
||||||
|
Показать Warning
|
||||||
|
</button>
|
||||||
|
<button class="btn btn--info" data-toast-trigger="info"
|
||||||
|
data-toast-title="Информация" data-toast-message="Полезная информация">
|
||||||
|
Показать Info
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-code">
|
||||||
|
<pre><code>// Показать toast уведомление
|
||||||
|
ToastComponent.success('Операция успешна!', {
|
||||||
|
title: 'Готово!',
|
||||||
|
duration: 4000
|
||||||
|
});
|
||||||
|
|
||||||
|
// Использование через data-атрибуты
|
||||||
|
<button data-toast-trigger="success"
|
||||||
|
data-toast-title="Успех!"
|
||||||
|
data-toast-message="Операция выполнена">
|
||||||
|
Показать уведомление
|
||||||
|
</button></code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Модальные окна -->
|
||||||
|
<section class="showcase-section">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Модальные окна</h2>
|
||||||
|
<p>Доступные модальные окна с поддержкой клавиатуры и фокуса</p>
|
||||||
|
|
||||||
|
<div class="demo-grid">
|
||||||
|
<button class="btn btn--primary" data-micromodal-trigger="modal-demo">
|
||||||
|
Открыть модальное окно
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-code">
|
||||||
|
<pre><code>// Открыть модальное окно программно
|
||||||
|
MicroModal.show('modal-id');
|
||||||
|
|
||||||
|
// Использование через data-атрибуты
|
||||||
|
<button data-micromodal-trigger="modal-id">
|
||||||
|
Открыть модал
|
||||||
|
</button></code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Табы -->
|
||||||
|
<section class="showcase-section">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Адаптивные табы</h2>
|
||||||
|
<p>Табы, которые превращаются в аккордеон на мобильных устройствах</p>
|
||||||
|
|
||||||
|
<div class="tabs" data-tabs>
|
||||||
|
<div class="tabs__nav">
|
||||||
|
<button class="tabs__tab tabs__tab--active" data-tab="demo-tab-1">Первая вкладка</button>
|
||||||
|
<button class="tabs__tab" data-tab="demo-tab-2">Вторая вкладка</button>
|
||||||
|
<button class="tabs__tab" data-tab="demo-tab-3">Третья вкладка</button>
|
||||||
|
</div>
|
||||||
|
<div class="tabs__content">
|
||||||
|
<div class="tabs__panel tabs__panel--active" id="demo-tab-1-panel" data-panel="demo-tab-1">
|
||||||
|
<p>Содержимое первой вкладки. Здесь может быть любой контент.</p>
|
||||||
|
</div>
|
||||||
|
<div class="tabs__panel" id="demo-tab-2-panel" data-panel="demo-tab-2">
|
||||||
|
<p>Содержимое второй вкладки с другой информацией.</p>
|
||||||
|
</div>
|
||||||
|
<div class="tabs__panel" id="demo-tab-3-panel" data-panel="demo-tab-3">
|
||||||
|
<p>Содержимое третьей вкладки для демонстрации функциональности.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-code">
|
||||||
|
<pre><code>// HTML структура табов
|
||||||
|
<div class="tabs" data-tabs>
|
||||||
|
<div class="tabs__nav">
|
||||||
|
<button class="tabs__tab tabs__tab--active" data-tab="tab-1">
|
||||||
|
Первая вкладка
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="tabs__content">
|
||||||
|
<div class="tabs__panel tabs__panel--active" data-panel="tab-1">
|
||||||
|
Содержимое вкладки
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div></code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Аккордеон -->
|
||||||
|
<section class="showcase-section">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Аккордеон</h2>
|
||||||
|
<p>Складывающиеся секции с плавными анимациями</p>
|
||||||
|
|
||||||
|
<div class="accordion" data-accordion>
|
||||||
|
<div class="accordion__item">
|
||||||
|
<button class="accordion__header" data-accordion-trigger="demo-acc-1" aria-expanded="false">
|
||||||
|
<span>Что такое ViteKit Universal?</span>
|
||||||
|
<span class="accordion__icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="accordion__content" id="demo-acc-1-content" data-accordion-content="demo-acc-1" aria-hidden="true">
|
||||||
|
<div class="accordion__body">
|
||||||
|
<p>ViteKit Universal - это современный универсальный сборщик проектов, который объединяет Vite, Twig, SCSS и готовые UI компоненты для быстрой разработки.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accordion__item">
|
||||||
|
<button class="accordion__header" data-accordion-trigger="demo-acc-2" aria-expanded="false">
|
||||||
|
<span>Какие компоненты включены?</span>
|
||||||
|
<span class="accordion__icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="accordion__content" id="demo-acc-2-content" data-accordion-content="demo-acc-2" aria-hidden="true">
|
||||||
|
<div class="accordion__body">
|
||||||
|
<p>В комплект входят: модальные окна, табы, аккордеон, toast уведомления, формы, кнопки и многое другое.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accordion__item">
|
||||||
|
<button class="accordion__header" data-accordion-trigger="demo-acc-3" aria-expanded="false">
|
||||||
|
<span>Как начать использовать?</span>
|
||||||
|
<span class="accordion__icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="accordion__content" id="demo-acc-3-content" data-accordion-content="demo-acc-3" aria-hidden="true">
|
||||||
|
<div class="accordion__body">
|
||||||
|
<p>Просто клонируйте репозиторий, установите зависимости командой <code>npm install</code> и запустите <code>npm run dev</code>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-code">
|
||||||
|
<pre><code>// HTML структура аккордеона
|
||||||
|
<div class="accordion" data-accordion>
|
||||||
|
<div class="accordion__item">
|
||||||
|
<button class="accordion__header" data-accordion-trigger="acc-1">
|
||||||
|
<span>Заголовок</span>
|
||||||
|
<span class="accordion__icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="accordion__content" data-accordion-content="acc-1">
|
||||||
|
<div class="accordion__body">
|
||||||
|
Содержимое аккордеона
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div></code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Модальное окно для демонстрации -->
|
||||||
|
<div class="modal micromodal-slide" id="modal-demo" aria-hidden="true">
|
||||||
|
<div class="modal__overlay" tabindex="-1" data-micromodal-close>
|
||||||
|
<div class="modal__container" role="dialog" aria-modal="true">
|
||||||
|
<header class="modal__header">
|
||||||
|
<h2 class="modal__title">Демонстрационное модальное окно</h2>
|
||||||
|
<button class="modal__close" aria-label="Закрыть" data-micromodal-close></button>
|
||||||
|
</header>
|
||||||
|
<main class="modal__content">
|
||||||
|
<p>Это пример модального окна с полной поддержкой accessibility:</p>
|
||||||
|
<ul>
|
||||||
|
<li>✅ Управление клавиатурой (Tab, Esc)</li>
|
||||||
|
<li>✅ Фокус и ARIA атрибуты</li>
|
||||||
|
<li>✅ Плавные анимации</li>
|
||||||
|
<li>✅ Закрытие по клику вне окна</li>
|
||||||
|
</ul>
|
||||||
|
</main>
|
||||||
|
<footer class="modal__footer">
|
||||||
|
<button class="btn btn--secondary" data-micromodal-close>Закрыть</button>
|
||||||
|
<button class="btn btn--primary">Действие</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="module" src="/src/scripts/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
167
vitekit-claude/docs/ROADMAP.md
Normal file
167
vitekit-claude/docs/ROADMAP.md
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
# План развития ViteKit Universal
|
||||||
|
|
||||||
|
## 🎯 Текущее состояние (v1.0.0)
|
||||||
|
|
||||||
|
✅ **Завершено:**
|
||||||
|
- Базовая архитектура проекта
|
||||||
|
- Конфигурация Vite + Twig
|
||||||
|
- Система сборки и оптимизации
|
||||||
|
- UI компоненты: модалы, табы, аккордеон, уведомления
|
||||||
|
- SCSS архитектура с утилитами
|
||||||
|
- Система линтинга и форматирования
|
||||||
|
- Адаптивная верстка
|
||||||
|
- Документация
|
||||||
|
|
||||||
|
## 🚀 Ближайшие планы (v1.1.0)
|
||||||
|
|
||||||
|
### Дополнительные компоненты
|
||||||
|
- [ ] **Carousel/Slider** - слайдер с touch поддержкой
|
||||||
|
- [ ] **Dropdown** - выпадающие меню с позиционированием
|
||||||
|
- [ ] **Tooltip** - всплывающие подсказки
|
||||||
|
- [ ] **Progress Bar** - индикаторы прогресса
|
||||||
|
- [ ] **Loading Spinner** - индикаторы загрузки
|
||||||
|
|
||||||
|
### Улучшения UX
|
||||||
|
- [ ] **Темная тема** - переключатель светлой/темной темы
|
||||||
|
- [ ] **Анимации** - расширенная библиотека анимаций
|
||||||
|
- [ ] **Skeleton Loading** - каркасная загрузка для контента
|
||||||
|
- [ ] **Infinite Scroll** - бесконечная прокрутка
|
||||||
|
- [ ] **Lazy Loading** - отложенная загрузка изображений
|
||||||
|
|
||||||
|
### Функциональность
|
||||||
|
- [ ] **Form Validation** - валидация форм в реальном времени
|
||||||
|
- [ ] **Cookie Consent** - управление согласием на cookie
|
||||||
|
- [ ] **Search/Filter** - поиск и фильтрация контента
|
||||||
|
- [ ] **Copy to Clipboard** - копирование в буфер обмена
|
||||||
|
|
||||||
|
## 🔧 Технические улучшения (v1.2.0)
|
||||||
|
|
||||||
|
### TypeScript поддержка
|
||||||
|
- [ ] Миграция JavaScript на TypeScript
|
||||||
|
- [ ] Типизация всех компонентов
|
||||||
|
- [ ] Автогенерация документации типов
|
||||||
|
|
||||||
|
### PWA возможности
|
||||||
|
- [ ] Service Worker для кэширования
|
||||||
|
- [ ] Web App Manifest
|
||||||
|
- [ ] Offline режим
|
||||||
|
- [ ] Push уведомления
|
||||||
|
|
||||||
|
### Производительность
|
||||||
|
- [ ] Bundle analyzer интеграция
|
||||||
|
- [ ] Critical CSS extraction
|
||||||
|
- [ ] Resource hints optimization
|
||||||
|
- [ ] WebP/AVIF поддержка для изображений
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- [ ] Unit тесты для компонентов
|
||||||
|
- [ ] E2E тестирование с Playwright
|
||||||
|
- [ ] Visual regression testing
|
||||||
|
- [ ] Accessibility testing
|
||||||
|
|
||||||
|
## 📦 Экосистема (v1.3.0)
|
||||||
|
|
||||||
|
### Интеграции
|
||||||
|
- [ ] **Strapi CMS** - готовые шаблоны для Strapi
|
||||||
|
- [ ] **Contentful** - интеграция с Contentful API
|
||||||
|
- [ ] **WordPress** - мост для WordPress тем
|
||||||
|
- [ ] **Shopify** - e-commerce шаблоны
|
||||||
|
|
||||||
|
### Developer Experience
|
||||||
|
- [ ] **CLI инструмент** - генератор проектов
|
||||||
|
- [ ] **VS Code extension** - сниппеты и автодополнение
|
||||||
|
- [ ] **Figma plugin** - экспорт компонентов из Figma
|
||||||
|
- [ ] **Storybook** - документация компонентов
|
||||||
|
|
||||||
|
### Шаблоны и стартеры
|
||||||
|
- [ ] **Blog template** - шаблон для блога
|
||||||
|
- [ ] **Portfolio template** - портфолио
|
||||||
|
- [ ] **E-commerce template** - интернет-магазин
|
||||||
|
- [ ] **Dashboard template** - админ панель
|
||||||
|
|
||||||
|
## 🌐 Расширенные возможности (v2.0.0)
|
||||||
|
|
||||||
|
### Многоязычность
|
||||||
|
- [ ] i18n поддержка
|
||||||
|
- [ ] RTL языки поддержка
|
||||||
|
- [ ] Автопереводы через API
|
||||||
|
|
||||||
|
### Advanced UI
|
||||||
|
- [ ] **Data Tables** - таблицы с сортировкой и фильтрацией
|
||||||
|
- [ ] **Calendar/Date Picker** - календарь и выбор дат
|
||||||
|
- [ ] **Rich Text Editor** - WYSIWYG редактор
|
||||||
|
- [ ] **File Upload** - загрузка файлов с drag&drop
|
||||||
|
|
||||||
|
### Build System
|
||||||
|
- [ ] **Multiple outputs** - различные сборки для разных целей
|
||||||
|
- [ ] **Micro-frontends** - поддержка микрофронтендов
|
||||||
|
- [ ] **WebAssembly** - интеграция WASM модулей
|
||||||
|
|
||||||
|
### Performance Monitoring
|
||||||
|
- [ ] **Core Web Vitals** - мониторинг производительности
|
||||||
|
- [ ] **Error tracking** - отслеживание ошибок
|
||||||
|
- [ ] **Analytics** - интеграция аналитики
|
||||||
|
|
||||||
|
## 🎨 Design System (v2.1.0)
|
||||||
|
|
||||||
|
### Система дизайна
|
||||||
|
- [ ] **Design tokens** - централизованные токены дизайна
|
||||||
|
- [ ] **Component variants** - множественные варианты компонентов
|
||||||
|
- [ ] **Theme builder** - конструктор тем
|
||||||
|
- [ ] **Brand guidelines** - руководство по бренду
|
||||||
|
|
||||||
|
### Accessibility
|
||||||
|
- [ ] **Screen reader** - полная поддержка скрин-ридеров
|
||||||
|
- [ ] **High contrast** - режим высокой контрастности
|
||||||
|
- [ ] **Motion preferences** - уважение к предпочтениям анимации
|
||||||
|
- [ ] **WCAG 2.1 AAA** - соответствие стандартам доступности
|
||||||
|
|
||||||
|
## 🚀 Экспериментальные возможности
|
||||||
|
|
||||||
|
### Cutting Edge
|
||||||
|
- [ ] **Web Components** - нативные веб-компоненты
|
||||||
|
- [ ] **View Transitions API** - плавные переходы между страницами
|
||||||
|
- [ ] **Container Queries** - контейнерные запросы
|
||||||
|
- [ ] **CSS @layer** - слои каскада
|
||||||
|
|
||||||
|
### AI Integration
|
||||||
|
- [ ] **AI Content Generation** - генерация контента с помощью ИИ
|
||||||
|
- [ ] **Smart Optimization** - автоматическая оптимизация производительности
|
||||||
|
- [ ] **Accessibility Checker** - ИИ проверка доступности
|
||||||
|
|
||||||
|
## 📊 Метрики успеха
|
||||||
|
|
||||||
|
### Производительность
|
||||||
|
- Lighthouse Score > 95
|
||||||
|
- Core Web Vitals в зеленой зоне
|
||||||
|
- Bundle size < 50KB (gzipped)
|
||||||
|
- First Contentful Paint < 1.5s
|
||||||
|
|
||||||
|
### Developer Experience
|
||||||
|
- Setup time < 5 минут
|
||||||
|
- Build time < 30 секунд
|
||||||
|
- Hot reload < 100ms
|
||||||
|
- 95%+ test coverage
|
||||||
|
|
||||||
|
### Community
|
||||||
|
- 1000+ GitHub stars
|
||||||
|
- 100+ contributors
|
||||||
|
- 50+ ecosystem packages
|
||||||
|
- 10000+ weekly downloads
|
||||||
|
|
||||||
|
## 🤝 Вклад в развитие
|
||||||
|
|
||||||
|
Приветствуется участие сообщества в развитии проекта:
|
||||||
|
|
||||||
|
1. **Обратная связь** - отчеты об ошибках и предложения
|
||||||
|
2. **Код** - pull requests с новыми возможностями
|
||||||
|
3. **Документация** - улучшение документации
|
||||||
|
4. **Тестирование** - тестирование новых возможностей
|
||||||
|
5. **Дизайн** - улучшение UX/UI
|
||||||
|
|
||||||
|
### Приоритеты
|
||||||
|
- 🔥 **Высокий** - критически важно для пользователей
|
||||||
|
- 🚀 **Средний** - улучшает опыт разработки
|
||||||
|
- 💡 **Низкий** - экспериментальные возможности
|
||||||
|
|
||||||
|
Roadmap обновляется ежемесячно на основе обратной связи от сообщества и анализа использования.
|
||||||
123
vitekit-claude/index.html
Normal file
123
vitekit-claude/index.html
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>ViteKit Universal - Test</title>
|
||||||
|
<link rel="stylesheet" href="src/styles/main.scss">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="header">
|
||||||
|
<div class="container">
|
||||||
|
<nav class="nav">
|
||||||
|
<div class="nav__brand">
|
||||||
|
<a href="/" class="nav__logo">ViteKit Universal</a>
|
||||||
|
</div>
|
||||||
|
<ul class="nav__menu">
|
||||||
|
<li class="nav__item">
|
||||||
|
<a href="/" class="nav__link nav__link--active">Главная</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav__item">
|
||||||
|
<a href="/components-demo.html" class="nav__link">Компоненты</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="main">
|
||||||
|
<div class="hero">
|
||||||
|
<div class="container">
|
||||||
|
<div class="hero__content">
|
||||||
|
<h1 class="hero__title">ViteKit Universal</h1>
|
||||||
|
<p class="hero__subtitle">
|
||||||
|
Современный универсальный сборщик проектов с Vite + Twig + компонентами
|
||||||
|
</p>
|
||||||
|
<div class="hero__actions">
|
||||||
|
<button class="btn btn--primary btn--lg" data-micromodal-trigger="modal-basic">
|
||||||
|
Открыть модал
|
||||||
|
</button>
|
||||||
|
<button class="btn btn--outline btn--lg" data-toast-trigger="success"
|
||||||
|
data-toast-title="Успех!" data-toast-message="Тест уведомления">
|
||||||
|
Показать уведомление
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section id="components" style="padding: 4rem 0;">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Тестирование компонентов</h2>
|
||||||
|
|
||||||
|
<!-- Табы -->
|
||||||
|
<div class="tabs" data-tabs style="margin-bottom: 2rem;">
|
||||||
|
<div class="tabs__nav">
|
||||||
|
<button class="tabs__tab tabs__tab--active" data-tab="tab-1">Первая вкладка</button>
|
||||||
|
<button class="tabs__tab" data-tab="tab-2">Вторая вкладка</button>
|
||||||
|
<button class="tabs__tab" data-tab="tab-3">Третья вкладка</button>
|
||||||
|
</div>
|
||||||
|
<div class="tabs__content">
|
||||||
|
<div class="tabs__panel tabs__panel--active" id="tab-1-panel" data-panel="tab-1">
|
||||||
|
<p>Содержимое первой вкладки</p>
|
||||||
|
</div>
|
||||||
|
<div class="tabs__panel" id="tab-2-panel" data-panel="tab-2">
|
||||||
|
<p>Содержимое второй вкладки</p>
|
||||||
|
</div>
|
||||||
|
<div class="tabs__panel" id="tab-3-panel" data-panel="tab-3">
|
||||||
|
<p>Содержимое третьей вкладки</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Аккордеон -->
|
||||||
|
<div class="accordion" data-accordion>
|
||||||
|
<div class="accordion__item">
|
||||||
|
<button class="accordion__header" data-accordion-trigger="acc-1" aria-expanded="false">
|
||||||
|
<span>Что такое ViteKit?</span>
|
||||||
|
<span class="accordion__icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="accordion__content" id="acc-1-content" data-accordion-content="acc-1" aria-hidden="true">
|
||||||
|
<div class="accordion__body">
|
||||||
|
<p>ViteKit - это современный сборщик проектов.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accordion__item">
|
||||||
|
<button class="accordion__header" data-accordion-trigger="acc-2" aria-expanded="false">
|
||||||
|
<span>Как использовать?</span>
|
||||||
|
<span class="accordion__icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="accordion__content" id="acc-2-content" data-accordion-content="acc-2" aria-hidden="true">
|
||||||
|
<div class="accordion__body">
|
||||||
|
<p>Просто установите зависимости и запустите сервер разработки.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Модальное окно -->
|
||||||
|
<div class="modal micromodal-slide" id="modal-basic" aria-hidden="true">
|
||||||
|
<div class="modal__overlay" tabindex="-1" data-micromodal-close>
|
||||||
|
<div class="modal__container" role="dialog" aria-modal="true">
|
||||||
|
<header class="modal__header">
|
||||||
|
<h2 class="modal__title">Тестовое модальное окно</h2>
|
||||||
|
<button class="modal__close" aria-label="Закрыть" data-micromodal-close></button>
|
||||||
|
</header>
|
||||||
|
<main class="modal__content">
|
||||||
|
<p>Это тестовое модальное окно для проверки функциональности.</p>
|
||||||
|
</main>
|
||||||
|
<footer class="modal__footer">
|
||||||
|
<button class="btn btn--secondary" data-micromodal-close>Закрыть</button>
|
||||||
|
<button class="btn btn--primary">Подтвердить</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="module" src="src/scripts/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
10273
vitekit-claude/package-lock.json
generated
Normal file
10273
vitekit-claude/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
58
vitekit-claude/package.json
Normal file
58
vitekit-claude/package.json
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
{
|
||||||
|
"name": "vitekit-universal",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Modern universal project builder with Vite + Twig + Components",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --host",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"lint": "eslint src --ext .js",
|
||||||
|
"lint:fix": "eslint src --ext .js --fix",
|
||||||
|
"lint:css": "stylelint 'src/**/*.scss'",
|
||||||
|
"lint:css:fix": "stylelint 'src/**/*.scss' --fix",
|
||||||
|
"format": "prettier --write 'src/**/*.{js,scss,twig,json}'",
|
||||||
|
"prepare": "husky install",
|
||||||
|
"clean": "rm -rf dist"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vituum/vite-plugin-twig": "^1.1.0",
|
||||||
|
"@pivanov/vite-plugin-svg-sprite": "^3.0.0",
|
||||||
|
"vite-plugin-image-optimizer": "^2.0.1",
|
||||||
|
"@vitejs/plugin-legacy": "^5.4.2",
|
||||||
|
"vite": "^5.4.8",
|
||||||
|
"eslint": "^8.57.1",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-plugin-import": "^2.30.0",
|
||||||
|
"prettier": "^3.3.3",
|
||||||
|
"stylelint": "^16.9.0",
|
||||||
|
"stylelint-config-standard-scss": "^13.1.0",
|
||||||
|
"stylelint-order": "^6.0.4",
|
||||||
|
"husky": "^9.1.6",
|
||||||
|
"lint-staged": "^15.2.10",
|
||||||
|
"sass": "^1.79.4",
|
||||||
|
"glob": "^11.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"micromodal": "^0.4.10",
|
||||||
|
"toastify-js": "^1.12.0"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.js": [
|
||||||
|
"eslint --fix",
|
||||||
|
"prettier --write"
|
||||||
|
],
|
||||||
|
"*.scss": [
|
||||||
|
"stylelint --fix",
|
||||||
|
"prettier --write"
|
||||||
|
],
|
||||||
|
"*.{twig,json}": [
|
||||||
|
"prettier --write"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions",
|
||||||
|
"not dead"
|
||||||
|
]
|
||||||
|
}
|
||||||
496
vitekit-claude/showcase.html
Normal file
496
vitekit-claude/showcase.html
Normal file
|
|
@ -0,0 +1,496 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Components Showcase - ViteKit Universal</title>
|
||||||
|
<link rel="stylesheet" href="src/styles/main.scss">
|
||||||
|
<style>
|
||||||
|
/* Showcase specific styles */
|
||||||
|
.showcase-header {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 4rem 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.showcase-section {
|
||||||
|
padding: 3rem 0;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.showcase-section:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-demo {
|
||||||
|
background: #f9fafb;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-code {
|
||||||
|
background: #1f2937;
|
||||||
|
color: #e5e7eb;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card {
|
||||||
|
background: white;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.interactive-demo {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 2rem;
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.interactive-demo {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Navigation -->
|
||||||
|
<header class="header">
|
||||||
|
<div class="container">
|
||||||
|
<nav class="nav">
|
||||||
|
<div class="nav__brand">
|
||||||
|
<a href="/" class="nav__logo">ViteKit Universal</a>
|
||||||
|
</div>
|
||||||
|
<ul class="nav__menu">
|
||||||
|
<li class="nav__item">
|
||||||
|
<a href="index.html" class="nav__link">Главная</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav__item">
|
||||||
|
<a href="showcase.html" class="nav__link nav__link--active">Showcase</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Showcase Header -->
|
||||||
|
<div class="showcase-header">
|
||||||
|
<div class="container">
|
||||||
|
<h1>Components Showcase</h1>
|
||||||
|
<p>Интерактивная демонстрация всех компонентов ViteKit Universal</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<main class="main">
|
||||||
|
<!-- Toast Notifications -->
|
||||||
|
<section class="showcase-section">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Toast Уведомления</h2>
|
||||||
|
<p>Всплывающие уведомления с автоматическим скрытием и анимациями</p>
|
||||||
|
|
||||||
|
<div class="component-demo">
|
||||||
|
<h3>Типы уведомлений</h3>
|
||||||
|
<div class="demo-controls">
|
||||||
|
<button class="btn btn--success" data-toast-trigger="success"
|
||||||
|
data-toast-title="Успешно!" data-toast-message="Операция выполнена успешно">
|
||||||
|
Success Toast
|
||||||
|
</button>
|
||||||
|
<button class="btn btn--error" data-toast-trigger="error"
|
||||||
|
data-toast-title="Ошибка!" data-toast-message="Произошла ошибка при выполнении операции">
|
||||||
|
Error Toast
|
||||||
|
</button>
|
||||||
|
<button class="btn btn--warning" data-toast-trigger="warning"
|
||||||
|
data-toast-title="Предупреждение!" data-toast-message="Проверьте введенные данные">
|
||||||
|
Warning Toast
|
||||||
|
</button>
|
||||||
|
<button class="btn btn--secondary" data-toast-trigger="info"
|
||||||
|
data-toast-title="Информация" data-toast-message="Новая версия доступна для скачивания">
|
||||||
|
Info Toast
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-code">
|
||||||
|
// JavaScript API
|
||||||
|
ToastComponent.success('Операция выполнена!');
|
||||||
|
ToastComponent.error('Произошла ошибка!');
|
||||||
|
ToastComponent.warning('Внимание!');
|
||||||
|
ToastComponent.info('Новая информация');
|
||||||
|
|
||||||
|
// HTML атрибуты
|
||||||
|
<button data-toast-trigger="success"
|
||||||
|
data-toast-title="Заголовок"
|
||||||
|
data-toast-message="Сообщение">
|
||||||
|
Показать уведомление
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature-grid">
|
||||||
|
<div class="feature-card">
|
||||||
|
<h4>✨ Автоматическое скрытие</h4>
|
||||||
|
<p>Уведомления автоматически исчезают через 4 секунды</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature-card">
|
||||||
|
<h4>📱 Адаптивность</h4>
|
||||||
|
<p>Корректное отображение на всех устройствах</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature-card">
|
||||||
|
<h4>🎨 Типизация</h4>
|
||||||
|
<p>4 типа: success, error, warning, info</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature-card">
|
||||||
|
<h4>🔧 API</h4>
|
||||||
|
<p>Простой JavaScript API и HTML атрибуты</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Modal Windows -->
|
||||||
|
<section class="showcase-section">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Модальные окна</h2>
|
||||||
|
<p>Доступные модальные окна с поддержкой ARIA и клавиатурной навигации</p>
|
||||||
|
|
||||||
|
<div class="component-demo">
|
||||||
|
<h3>Типы модальных окон</h3>
|
||||||
|
<div class="demo-controls">
|
||||||
|
<button class="btn btn--primary" data-micromodal-trigger="modal-basic">
|
||||||
|
Базовое модальное окно
|
||||||
|
</button>
|
||||||
|
<button class="btn btn--secondary" data-micromodal-trigger="modal-form">
|
||||||
|
Модальное окно с формой
|
||||||
|
</button>
|
||||||
|
<button class="btn btn--outline" data-micromodal-trigger="modal-confirmation">
|
||||||
|
Окно подтверждения
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-code">
|
||||||
|
// JavaScript API
|
||||||
|
MicroModal.show('modal-id');
|
||||||
|
MicroModal.close('modal-id');
|
||||||
|
|
||||||
|
// HTML разметка
|
||||||
|
<div class="modal micromodal-slide" id="modal-id">
|
||||||
|
<div class="modal__overlay" data-micromodal-close>
|
||||||
|
<div class="modal__container">
|
||||||
|
<header class="modal__header">
|
||||||
|
<h2 class="modal__title">Заголовок</h2>
|
||||||
|
<button class="modal__close" data-micromodal-close></button>
|
||||||
|
</header>
|
||||||
|
<main class="modal__content">Содержимое</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature-grid">
|
||||||
|
<div class="feature-card">
|
||||||
|
<h4>♿ Доступность</h4>
|
||||||
|
<p>Полная поддержка ARIA и скрин-ридеров</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature-card">
|
||||||
|
<h4>⌨️ Клавиатура</h4>
|
||||||
|
<p>Навигация через Tab, Enter, Escape</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature-card">
|
||||||
|
<h4>🎬 Анимации</h4>
|
||||||
|
<p>Плавные анимации открытия и закрытия</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature-card">
|
||||||
|
<h4>🔒 Фокус</h4>
|
||||||
|
<p>Автоматическое управление фокусом</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Tabs -->
|
||||||
|
<section class="showcase-section">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Система табов</h2>
|
||||||
|
<p>Адаптивные табы с поддержкой клавиатурной навигации</p>
|
||||||
|
|
||||||
|
<div class="component-demo">
|
||||||
|
<h3>Интерактивные табы</h3>
|
||||||
|
<div class="tabs" data-tabs>
|
||||||
|
<div class="tabs__nav">
|
||||||
|
<button class="tabs__tab tabs__tab--active" data-tab="features">Возможности</button>
|
||||||
|
<button class="tabs__tab" data-tab="usage">Использование</button>
|
||||||
|
<button class="tabs__tab" data-tab="examples">Примеры</button>
|
||||||
|
<button class="tabs__tab" data-tab="api">API</button>
|
||||||
|
</div>
|
||||||
|
<div class="tabs__content">
|
||||||
|
<div class="tabs__panel tabs__panel--active" data-panel="features">
|
||||||
|
<h4>Основные возможности</h4>
|
||||||
|
<ul>
|
||||||
|
<li>📱 <strong>Адаптивность:</strong> Автоматическое превращение в аккордеон на мобильных</li>
|
||||||
|
<li>⌨️ <strong>Клавиатурная навигация:</strong> Arrow keys, Home, End</li>
|
||||||
|
<li>🎨 <strong>Кастомизация:</strong> Легко настраиваемые стили</li>
|
||||||
|
<li>🚀 <strong>Производительность:</strong> Минимальный overhead</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="tabs__panel" data-panel="usage">
|
||||||
|
<h4>Как использовать</h4>
|
||||||
|
<p>Создайте HTML структуру с нужными классами и добавьте атрибут <code>data-tabs</code>:</p>
|
||||||
|
<div class="demo-code">
|
||||||
|
<div class="tabs" data-tabs>
|
||||||
|
<div class="tabs__nav">
|
||||||
|
<button class="tabs__tab tabs__tab--active">Вкладка 1</button>
|
||||||
|
<button class="tabs__tab">Вкладка 2</button>
|
||||||
|
</div>
|
||||||
|
<div class="tabs__content">
|
||||||
|
<div class="tabs__panel tabs__panel--active">Содержимое 1</div>
|
||||||
|
<div class="tabs__panel">Содержимое 2</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tabs__panel" data-panel="examples">
|
||||||
|
<h4>Примеры использования</h4>
|
||||||
|
<p>Табы отлично подходят для:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Навигации по разделам контента</li>
|
||||||
|
<li>Организации форм по шагам</li>
|
||||||
|
<li>Переключения между видами данных</li>
|
||||||
|
<li>Фильтрации контента по категориям</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="tabs__panel" data-panel="api">
|
||||||
|
<h4>JavaScript API</h4>
|
||||||
|
<div class="demo-code">
|
||||||
|
// Создание экземпляра
|
||||||
|
const tabs = new TabsComponent(element, options);
|
||||||
|
|
||||||
|
// Методы
|
||||||
|
tabs.next(); // Следующая вкладка
|
||||||
|
tabs.prev(); // Предыдущая вкладка
|
||||||
|
tabs.goTo(2); // Перейти к вкладке по индексу
|
||||||
|
|
||||||
|
// События
|
||||||
|
element.addEventListener('tabchange', (e) => {
|
||||||
|
console.log('Активна вкладка:', e.detail.index);
|
||||||
|
});
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Accordion -->
|
||||||
|
<section class="showcase-section">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Аккордеон</h2>
|
||||||
|
<p>Сворачиваемые панели для экономии места с плавными анимациями</p>
|
||||||
|
|
||||||
|
<div class="interactive-demo">
|
||||||
|
<div>
|
||||||
|
<h3>Интерактивный аккордеон</h3>
|
||||||
|
<div class="accordion" data-accordion>
|
||||||
|
<div class="accordion__item">
|
||||||
|
<button class="accordion__header" data-accordion-trigger="acc-features" aria-expanded="false">
|
||||||
|
<span>🚀 Возможности аккордеона</span>
|
||||||
|
<span class="accordion__icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="accordion__content" data-accordion-content="acc-features" aria-hidden="true">
|
||||||
|
<div class="accordion__body">
|
||||||
|
<ul>
|
||||||
|
<li><strong>Плавные анимации:</strong> CSS transitions для smooth UX</li>
|
||||||
|
<li><strong>Множественное раскрытие:</strong> Настраиваемое поведение</li>
|
||||||
|
<li><strong>Клавиатурная навигация:</strong> Полная поддержка a11y</li>
|
||||||
|
<li><strong>Автоматические ARIA атрибуты:</strong> Доступность из коробки</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="accordion__item">
|
||||||
|
<button class="accordion__header" data-accordion-trigger="acc-usage" aria-expanded="false">
|
||||||
|
<span>💡 Использование в проектах</span>
|
||||||
|
<span class="accordion__icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="accordion__content" data-accordion-content="acc-usage" aria-hidden="true">
|
||||||
|
<div class="accordion__body">
|
||||||
|
<p>Аккордеон идеален для:</p>
|
||||||
|
<ul>
|
||||||
|
<li>FAQ секций</li>
|
||||||
|
<li>Списков продуктов с детальной информацией</li>
|
||||||
|
<li>Документации с разворачиваемыми разделами</li>
|
||||||
|
<li>Фильтров в боковых панелях</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="accordion__item">
|
||||||
|
<button class="accordion__header" data-accordion-trigger="acc-config" aria-expanded="false">
|
||||||
|
<span>⚙️ Настройки и конфигурация</span>
|
||||||
|
<span class="accordion__icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="accordion__content" data-accordion-content="acc-config" aria-hidden="true">
|
||||||
|
<div class="accordion__body">
|
||||||
|
<div class="demo-code">
|
||||||
|
// Опции конфигурации
|
||||||
|
const accordion = new AccordionComponent(element, {
|
||||||
|
allowMultiple: false, // Разрешить открытие нескольких панелей
|
||||||
|
animationDuration: 300, // Длительность анимации в мс
|
||||||
|
autoClose: true, // Автозакрытие других панелей
|
||||||
|
keyboardNavigation: true // Клавиатурная навигация
|
||||||
|
});
|
||||||
|
|
||||||
|
// API методы
|
||||||
|
accordion.open(0); // Открыть панель по индексу
|
||||||
|
accordion.close(1); // Закрыть панель
|
||||||
|
accordion.toggle(2); // Переключить панель
|
||||||
|
accordion.closeAll(); // Закрыть все панели
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3>Особенности реализации</h3>
|
||||||
|
<div class="feature-grid" style="grid-template-columns: 1fr;">
|
||||||
|
<div class="feature-card">
|
||||||
|
<h4>🎭 Анимации</h4>
|
||||||
|
<p>Плавные CSS transitions с автоматическим расчетом высоты контента</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature-card">
|
||||||
|
<h4>♿ Доступность</h4>
|
||||||
|
<p>Полная поддержка ARIA атрибутов и клавиатурной навигации</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature-card">
|
||||||
|
<h4>📐 Гибкость</h4>
|
||||||
|
<p>Настраиваемое поведение: единичное или множественное раскрытие</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature-card">
|
||||||
|
<h4>🎨 Стилизация</h4>
|
||||||
|
<p>Легко настраиваемые стили через CSS переменные и модификаторы</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>События</h4>
|
||||||
|
<div class="demo-code">
|
||||||
|
// Слушаем события аккордеона
|
||||||
|
element.addEventListener('accordionopen', (e) => {
|
||||||
|
console.log('Открыта панель:', e.detail.index);
|
||||||
|
});
|
||||||
|
|
||||||
|
element.addEventListener('accordionclose', (e) => {
|
||||||
|
console.log('Закрыта панель:', e.detail.index);
|
||||||
|
});
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Modals -->
|
||||||
|
<div class="modal micromodal-slide" id="modal-basic" aria-hidden="true">
|
||||||
|
<div class="modal__overlay" tabindex="-1" data-micromodal-close>
|
||||||
|
<div class="modal__container" role="dialog" aria-modal="true">
|
||||||
|
<header class="modal__header">
|
||||||
|
<h2 class="modal__title">Базовое модальное окно</h2>
|
||||||
|
<button class="modal__close" aria-label="Закрыть" data-micromodal-close></button>
|
||||||
|
</header>
|
||||||
|
<main class="modal__content">
|
||||||
|
<p>Это базовое модальное окно с минимальным содержимым. Оно демонстрирует:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Правильную структуру HTML</li>
|
||||||
|
<li>ARIA атрибуты для доступности</li>
|
||||||
|
<li>Анимации открытия и закрытия</li>
|
||||||
|
<li>Управление фокусом</li>
|
||||||
|
</ul>
|
||||||
|
<p>Закрыть окно можно кликом на overlay, кнопку закрытия или нажатием Escape.</p>
|
||||||
|
</main>
|
||||||
|
<footer class="modal__footer">
|
||||||
|
<button class="btn btn--secondary" data-micromodal-close>Закрыть</button>
|
||||||
|
<button class="btn btn--primary">Понятно</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal micromodal-slide" id="modal-form" aria-hidden="true">
|
||||||
|
<div class="modal__overlay" tabindex="-1" data-micromodal-close>
|
||||||
|
<div class="modal__container" role="dialog" aria-modal="true">
|
||||||
|
<header class="modal__header">
|
||||||
|
<h2 class="modal__title">Модальное окно с формой</h2>
|
||||||
|
<button class="modal__close" aria-label="Закрыть" data-micromodal-close></button>
|
||||||
|
</header>
|
||||||
|
<main class="modal__content">
|
||||||
|
<form class="modal-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="modal-name" class="form-label">Имя</label>
|
||||||
|
<input type="text" id="modal-name" class="form-control" placeholder="Введите ваше имя">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="modal-email" class="form-label">Email</label>
|
||||||
|
<input type="email" id="modal-email" class="form-control" placeholder="example@email.com">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="modal-message" class="form-label">Сообщение</label>
|
||||||
|
<textarea id="modal-message" class="form-control" rows="3" placeholder="Ваше сообщение"></textarea>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
<footer class="modal__footer">
|
||||||
|
<button class="btn btn--secondary" data-micromodal-close>Отмена</button>
|
||||||
|
<button class="btn btn--primary">Отправить</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal micromodal-slide" id="modal-confirmation" aria-hidden="true">
|
||||||
|
<div class="modal__overlay" tabindex="-1" data-micromodal-close>
|
||||||
|
<div class="modal__container" role="dialog" aria-modal="true">
|
||||||
|
<header class="modal__header">
|
||||||
|
<h2 class="modal__title">Подтверждение действия</h2>
|
||||||
|
<button class="modal__close" aria-label="Закрыть" data-micromodal-close></button>
|
||||||
|
</header>
|
||||||
|
<main class="modal__content">
|
||||||
|
<p>Вы уверены, что хотите выполнить это действие?</p>
|
||||||
|
<p><strong>Внимание:</strong> Это действие нельзя будет отменить.</p>
|
||||||
|
</main>
|
||||||
|
<footer class="modal__footer">
|
||||||
|
<button class="btn btn--secondary" data-micromodal-close>Отмена</button>
|
||||||
|
<button class="btn btn--error">Подтвердить</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="module" src="src/scripts/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
86
vitekit-claude/src/data/components.json
Normal file
86
vitekit-claude/src/data/components.json
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
{
|
||||||
|
"modal": {
|
||||||
|
"title": "Модальные окна",
|
||||||
|
"description": "Доступные модальные окна с поддержкой ARIA и фокуса",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"id": "modal-basic",
|
||||||
|
"title": "Базовое модальное окно",
|
||||||
|
"content": "Это простое модальное окно с базовым функционалом."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "modal-form",
|
||||||
|
"title": "Модальное окно с формой",
|
||||||
|
"content": "Модальное окно, содержащее форму для ввода данных."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tabs": {
|
||||||
|
"title": "Вкладки",
|
||||||
|
"description": "Система табов с поддержкой клавиатурной навигации",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"id": "tab-1",
|
||||||
|
"title": "Первая вкладка",
|
||||||
|
"content": "Содержимое первой вкладки с демонстрацией функционала."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tab-2",
|
||||||
|
"title": "Вторая вкладка",
|
||||||
|
"content": "Содержимое второй вкладки с дополнительной информацией."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tab-3",
|
||||||
|
"title": "Третья вкладка",
|
||||||
|
"content": "Содержимое третьей вкладки с примерами использования."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"accordion": {
|
||||||
|
"title": "Аккордеон",
|
||||||
|
"description": "Сворачиваемые панели для экономии места",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"id": "accordion-1",
|
||||||
|
"title": "Что такое ViteKit?",
|
||||||
|
"content": "ViteKit - это современный сборщик проектов, созданный для быстрой разработки."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "accordion-2",
|
||||||
|
"title": "Как использовать компоненты?",
|
||||||
|
"content": "Компоненты легко интегрируются в любой проект и настраиваются через атрибуты."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "accordion-3",
|
||||||
|
"title": "Поддержка браузеров",
|
||||||
|
"content": "Все компоненты поддерживают современные браузеры и имеют полифиллы для старых версий."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"title": "Уведомления",
|
||||||
|
"description": "Всплывающие уведомления для информирования пользователей",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"type": "success",
|
||||||
|
"title": "Успешно!",
|
||||||
|
"message": "Операция выполнена успешно."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"title": "Ошибка!",
|
||||||
|
"message": "Произошла ошибка при выполнении операции."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "warning",
|
||||||
|
"title": "Внимание!",
|
||||||
|
"message": "Проверьте введенные данные."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "info",
|
||||||
|
"title": "Информация",
|
||||||
|
"message": "Новая версия доступна для скачивания."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
24
vitekit-claude/src/data/site.json
Normal file
24
vitekit-claude/src/data/site.json
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"title": "ViteKit Universal",
|
||||||
|
"description": "Modern universal project builder with Vite + Twig + Components",
|
||||||
|
"author": "ViteKit Team",
|
||||||
|
"url": "http://localhost:3000",
|
||||||
|
"language": "ru",
|
||||||
|
"navigation": [
|
||||||
|
{
|
||||||
|
"title": "Главная",
|
||||||
|
"url": "/",
|
||||||
|
"active": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Компоненты",
|
||||||
|
"url": "/components-demo",
|
||||||
|
"active": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"viewport": "width=device-width, initial-scale=1.0",
|
||||||
|
"robots": "index, follow",
|
||||||
|
"theme-color": "#3b82f6"
|
||||||
|
}
|
||||||
|
}
|
||||||
64
vitekit-claude/src/layouts/base.twig
Normal file
64
vitekit-claude/src/layouts/base.twig
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{ site.language }}">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="{{ site.meta.viewport }}">
|
||||||
|
<meta name="robots" content="{{ site.meta.robots }}">
|
||||||
|
<meta name="theme-color" content="{{ site.meta.theme_color }}">
|
||||||
|
|
||||||
|
<title>{% block title %}{{ site.title }}{% endblock %}</title>
|
||||||
|
<meta name="description" content="{% block description %}{{ site.description }}{% endblock %}">
|
||||||
|
|
||||||
|
<!-- Preload critical resources -->
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
|
||||||
|
<!-- Styles -->
|
||||||
|
<link rel="stylesheet" href="/src/styles/main.scss">
|
||||||
|
|
||||||
|
{% block head %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body class="{% block body_class %}{% endblock %}">
|
||||||
|
<!-- Header -->
|
||||||
|
{% block header %}
|
||||||
|
<header class="header">
|
||||||
|
<div class="container">
|
||||||
|
<nav class="nav">
|
||||||
|
<div class="nav__brand">
|
||||||
|
<a href="/" class="nav__logo">{{ site.title }}</a>
|
||||||
|
</div>
|
||||||
|
<ul class="nav__menu">
|
||||||
|
{% for item in site.navigation %}
|
||||||
|
<li class="nav__item">
|
||||||
|
<a href="{{ item.url }}" class="nav__link{% if item.active %} nav__link--active{% endif %}">
|
||||||
|
{{ item.title }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="main">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
{% block footer %}
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="container">
|
||||||
|
<div class="footer__content">
|
||||||
|
<p>© 2024 {{ site.title }}. Все права защищены.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<!-- Scripts -->
|
||||||
|
<script type="module" src="/src/scripts/main.js"></script>
|
||||||
|
{% block scripts %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
162
vitekit-claude/src/pages/components-demo.twig
Normal file
162
vitekit-claude/src/pages/components-demo.twig
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
{% extends "layouts/base.twig" %}
|
||||||
|
|
||||||
|
{% block title %}Демонстрация компонентов - {{ site.title }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="demo-header">
|
||||||
|
<div class="container">
|
||||||
|
<h1>Демонстрация компонентов</h1>
|
||||||
|
<p>Интерактивные примеры всех доступных UI компонентов</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Модальные окна -->
|
||||||
|
<section class="demo-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="demo-title">
|
||||||
|
<h2>{{ components.modal.title }}</h2>
|
||||||
|
<p>{{ components.modal.description }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-content">
|
||||||
|
<div class="demo-buttons">
|
||||||
|
{% for example in components.modal.examples %}
|
||||||
|
<button class="btn btn--primary" data-micromodal-trigger="{{ example.id }}">
|
||||||
|
{{ example.title }}
|
||||||
|
</button>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Модальные окна -->
|
||||||
|
{% for example in components.modal.examples %}
|
||||||
|
<div class="modal micromodal-slide" id="{{ example.id }}" aria-hidden="true">
|
||||||
|
<div class="modal__overlay" tabindex="-1" data-micromodal-close>
|
||||||
|
<div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="{{ example.id }}-title">
|
||||||
|
<header class="modal__header">
|
||||||
|
<h2 class="modal__title" id="{{ example.id }}-title">
|
||||||
|
{{ example.title }}
|
||||||
|
</h2>
|
||||||
|
<button class="modal__close" aria-label="Закрыть модальное окно" data-micromodal-close></button>
|
||||||
|
</header>
|
||||||
|
<main class="modal__content" id="{{ example.id }}-content">
|
||||||
|
<p>{{ example.content }}</p>
|
||||||
|
{% if example.id == 'modal-form' %}
|
||||||
|
<form class="modal-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name" class="form-label">Имя</label>
|
||||||
|
<input type="text" id="name" class="form-control" placeholder="Введите ваше имя">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email" class="form-label">Email</label>
|
||||||
|
<input type="email" id="email" class="form-control" placeholder="example@email.com">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="message" class="form-label">Сообщение</label>
|
||||||
|
<textarea id="message" class="form-control" rows="3" placeholder="Ваше сообщение"></textarea>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</main>
|
||||||
|
<footer class="modal__footer">
|
||||||
|
<button class="btn btn--secondary" data-micromodal-close aria-label="Закрыть это модальное окно">Закрыть</button>
|
||||||
|
{% if example.id == 'modal-form' %}
|
||||||
|
<button class="btn btn--primary">Отправить</button>
|
||||||
|
{% endif %}
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Вкладки -->
|
||||||
|
<section class="demo-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="demo-title">
|
||||||
|
<h2>{{ components.tabs.title }}</h2>
|
||||||
|
<p>{{ components.tabs.description }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-content">
|
||||||
|
<div class="tabs" data-tabs>
|
||||||
|
<div class="tabs__nav">
|
||||||
|
{% for example in components.tabs.examples %}
|
||||||
|
<button class="tabs__tab{% if loop.first %} tabs__tab--active{% endif %}"
|
||||||
|
data-tab="{{ example.id }}"
|
||||||
|
aria-controls="{{ example.id }}-panel">
|
||||||
|
{{ example.title }}
|
||||||
|
</button>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="tabs__content">
|
||||||
|
{% for example in components.tabs.examples %}
|
||||||
|
<div class="tabs__panel{% if loop.first %} tabs__panel--active{% endif %}"
|
||||||
|
id="{{ example.id }}-panel"
|
||||||
|
data-panel="{{ example.id }}">
|
||||||
|
<p>{{ example.content }}</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Аккордеон -->
|
||||||
|
<section class="demo-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="demo-title">
|
||||||
|
<h2>{{ components.accordion.title }}</h2>
|
||||||
|
<p>{{ components.accordion.description }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-content">
|
||||||
|
<div class="accordion" data-accordion>
|
||||||
|
{% for example in components.accordion.examples %}
|
||||||
|
<div class="accordion__item">
|
||||||
|
<button class="accordion__header"
|
||||||
|
data-accordion-trigger="{{ example.id }}"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="{{ example.id }}-content">
|
||||||
|
<span>{{ example.title }}</span>
|
||||||
|
<span class="accordion__icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="accordion__content"
|
||||||
|
id="{{ example.id }}-content"
|
||||||
|
data-accordion-content="{{ example.id }}">
|
||||||
|
<div class="accordion__body">
|
||||||
|
<p>{{ example.content }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Уведомления -->
|
||||||
|
<section class="demo-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="demo-title">
|
||||||
|
<h2>{{ components.toast.title }}</h2>
|
||||||
|
<p>{{ components.toast.description }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="demo-content">
|
||||||
|
<div class="demo-buttons">
|
||||||
|
{% for example in components.toast.examples %}
|
||||||
|
<button class="btn btn--{{ example.type }}"
|
||||||
|
data-toast-trigger="{{ example.type }}"
|
||||||
|
data-toast-title="{{ example.title }}"
|
||||||
|
data-toast-message="{{ example.message }}">
|
||||||
|
{{ example.title }}
|
||||||
|
</button>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
114
vitekit-claude/src/pages/index.twig
Normal file
114
vitekit-claude/src/pages/index.twig
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
{% extends "layouts/base.twig" %}
|
||||||
|
|
||||||
|
{% block title %}{{ site.title }} - Главная страница{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="hero">
|
||||||
|
<div class="container">
|
||||||
|
<div class="hero__content">
|
||||||
|
<h1 class="hero__title">ViteKit Universal</h1>
|
||||||
|
<p class="hero__subtitle">
|
||||||
|
Современный универсальный сборщик проектов с Vite + Twig + компонентами
|
||||||
|
</p>
|
||||||
|
<div class="hero__actions">
|
||||||
|
<a href="/components-demo" class="btn btn--primary btn--lg">
|
||||||
|
Посмотреть компоненты
|
||||||
|
</a>
|
||||||
|
<a href="#features" class="btn btn--outline btn--lg">
|
||||||
|
Узнать больше
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section id="features" class="features">
|
||||||
|
<div class="container">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>Возможности</h2>
|
||||||
|
<p>Все необходимое для современной веб-разработки</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="feature">
|
||||||
|
<div class="feature__icon">
|
||||||
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="feature__title">Быстрая сборка</h3>
|
||||||
|
<p class="feature__description">
|
||||||
|
Основанный на Vite для молниеносной разработки и сборки проектов
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="feature">
|
||||||
|
<div class="feature__icon">
|
||||||
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="feature__title">Готовые компоненты</h3>
|
||||||
|
<p class="feature__description">
|
||||||
|
Набор протестированных UI компонентов для быстрого прототипирования
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="feature">
|
||||||
|
<div class="feature__icon">
|
||||||
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 00-2.91-.09z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 15l8.5-8.5a2.12 2.12 0 000-3L18 1l-8.5 8.5a2.12 2.12 0 000 3L12 15z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="feature__title">Легкая настройка</h3>
|
||||||
|
<p class="feature__description">
|
||||||
|
Гибкая архитектура и конфигурация под любые потребности проекта
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="technologies">
|
||||||
|
<div class="container">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>Технологический стек</h2>
|
||||||
|
<p>Современные инструменты для эффективной разработки</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tech-grid">
|
||||||
|
<div class="tech-item">
|
||||||
|
<h4>Vite</h4>
|
||||||
|
<p>Быстрый сборщик проектов</p>
|
||||||
|
</div>
|
||||||
|
<div class="tech-item">
|
||||||
|
<h4>Twig</h4>
|
||||||
|
<p>Мощный шаблонизатор</p>
|
||||||
|
</div>
|
||||||
|
<div class="tech-item">
|
||||||
|
<h4>SCSS</h4>
|
||||||
|
<p>Расширенный CSS</p>
|
||||||
|
</div>
|
||||||
|
<div class="tech-item">
|
||||||
|
<h4>Vanilla JS</h4>
|
||||||
|
<p>Нативный JavaScript</p>
|
||||||
|
</div>
|
||||||
|
<div class="tech-item">
|
||||||
|
<h4>ESLint</h4>
|
||||||
|
<p>Качество кода</p>
|
||||||
|
</div>
|
||||||
|
<div class="tech-item">
|
||||||
|
<h4>Prettier</h4>
|
||||||
|
<p>Форматирование</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
317
vitekit-claude/src/scripts/components/accordion.js
Normal file
317
vitekit-claude/src/scripts/components/accordion.js
Normal file
|
|
@ -0,0 +1,317 @@
|
||||||
|
export class AccordionComponent {
|
||||||
|
constructor(element, options = {}) {
|
||||||
|
this.element = element;
|
||||||
|
this.options = {
|
||||||
|
allowMultiple: false,
|
||||||
|
animationDuration: 300,
|
||||||
|
autoClose: true,
|
||||||
|
keyboardNavigation: true,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
this.items = [];
|
||||||
|
this.activeItems = new Set();
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.bindElements();
|
||||||
|
this.bindEvents();
|
||||||
|
this.setInitialState();
|
||||||
|
this.setupAccessibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
bindElements() {
|
||||||
|
const itemElements = this.element.querySelectorAll('.accordion__item');
|
||||||
|
|
||||||
|
this.items = Array.from(itemElements).map(item => {
|
||||||
|
const trigger = item.querySelector('.accordion__header');
|
||||||
|
const content = item.querySelector('.accordion__content');
|
||||||
|
const id = trigger?.dataset.accordionTrigger || this.generateId();
|
||||||
|
|
||||||
|
return {
|
||||||
|
element: item,
|
||||||
|
trigger,
|
||||||
|
content,
|
||||||
|
id,
|
||||||
|
isOpen: false
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.items.length === 0) {
|
||||||
|
console.warn('AccordionComponent: No accordion items found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEvents() {
|
||||||
|
this.items.forEach((item, index) => {
|
||||||
|
if (!item.trigger || !item.content) return;
|
||||||
|
|
||||||
|
// Click events
|
||||||
|
item.trigger.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.toggle(index);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keyboard events
|
||||||
|
if (this.options.keyboardNavigation) {
|
||||||
|
item.trigger.addEventListener('keydown', (e) => {
|
||||||
|
this.handleKeyboard(e, index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setInitialState() {
|
||||||
|
this.items.forEach((item, index) => {
|
||||||
|
const isInitiallyOpen = item.trigger?.getAttribute('aria-expanded') === 'true';
|
||||||
|
|
||||||
|
if (isInitiallyOpen) {
|
||||||
|
this.open(index, false); // Open without animation initially
|
||||||
|
} else {
|
||||||
|
this.close(index, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setupAccessibility() {
|
||||||
|
this.items.forEach((item, index) => {
|
||||||
|
if (!item.trigger || !item.content) return;
|
||||||
|
|
||||||
|
const triggerId = `accordion-trigger-${item.id}`;
|
||||||
|
const contentId = `accordion-content-${item.id}`;
|
||||||
|
|
||||||
|
// Set up ARIA attributes
|
||||||
|
item.trigger.setAttribute('id', triggerId);
|
||||||
|
item.trigger.setAttribute('aria-controls', contentId);
|
||||||
|
item.trigger.setAttribute('tabindex', '0');
|
||||||
|
|
||||||
|
item.content.setAttribute('id', contentId);
|
||||||
|
item.content.setAttribute('aria-labelledby', triggerId);
|
||||||
|
item.content.setAttribute('role', 'region');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle(index) {
|
||||||
|
const item = this.items[index];
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
if (item.isOpen) {
|
||||||
|
this.close(index);
|
||||||
|
} else {
|
||||||
|
this.open(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open(index, animate = true) {
|
||||||
|
const item = this.items[index];
|
||||||
|
if (!item || item.isOpen) return;
|
||||||
|
|
||||||
|
// Close other items if multiple is not allowed
|
||||||
|
if (!this.options.allowMultiple && this.options.autoClose) {
|
||||||
|
this.closeAll(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
item.isOpen = true;
|
||||||
|
this.activeItems.add(index);
|
||||||
|
|
||||||
|
// Update ARIA attributes
|
||||||
|
item.trigger.setAttribute('aria-expanded', 'true');
|
||||||
|
item.content.setAttribute('aria-hidden', 'false');
|
||||||
|
|
||||||
|
// Add active class
|
||||||
|
item.element.classList.add('accordion__item--active');
|
||||||
|
|
||||||
|
if (animate) {
|
||||||
|
this.animateOpen(item);
|
||||||
|
} else {
|
||||||
|
item.content.style.maxHeight = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit custom event
|
||||||
|
this.element.dispatchEvent(new CustomEvent('accordionopen', {
|
||||||
|
detail: { index, item }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
close(index, animate = true) {
|
||||||
|
const item = this.items[index];
|
||||||
|
if (!item || !item.isOpen) return;
|
||||||
|
|
||||||
|
item.isOpen = false;
|
||||||
|
this.activeItems.delete(index);
|
||||||
|
|
||||||
|
// Update ARIA attributes
|
||||||
|
item.trigger.setAttribute('aria-expanded', 'false');
|
||||||
|
item.content.setAttribute('aria-hidden', 'true');
|
||||||
|
|
||||||
|
// Remove active class
|
||||||
|
item.element.classList.remove('accordion__item--active');
|
||||||
|
|
||||||
|
if (animate) {
|
||||||
|
this.animateClose(item);
|
||||||
|
} else {
|
||||||
|
item.content.style.maxHeight = '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit custom event
|
||||||
|
this.element.dispatchEvent(new CustomEvent('accordionclose', {
|
||||||
|
detail: { index, item }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
closeAll(except = -1) {
|
||||||
|
this.items.forEach((item, index) => {
|
||||||
|
if (index !== except && item.isOpen) {
|
||||||
|
this.close(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openAll() {
|
||||||
|
if (!this.options.allowMultiple) return;
|
||||||
|
|
||||||
|
this.items.forEach((item, index) => {
|
||||||
|
if (!item.isOpen) {
|
||||||
|
this.open(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
animateOpen(item) {
|
||||||
|
const content = item.content;
|
||||||
|
const scrollHeight = content.scrollHeight;
|
||||||
|
|
||||||
|
// Set initial state
|
||||||
|
content.style.maxHeight = '0';
|
||||||
|
content.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
// Force reflow
|
||||||
|
content.offsetHeight;
|
||||||
|
|
||||||
|
// Add transition
|
||||||
|
content.style.transition = `max-height ${this.options.animationDuration}ms ease-out`;
|
||||||
|
|
||||||
|
// Animate to full height
|
||||||
|
content.style.maxHeight = `${scrollHeight}px`;
|
||||||
|
|
||||||
|
// Clean up after animation
|
||||||
|
setTimeout(() => {
|
||||||
|
content.style.maxHeight = 'none';
|
||||||
|
content.style.overflow = '';
|
||||||
|
content.style.transition = '';
|
||||||
|
}, this.options.animationDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
animateClose(item) {
|
||||||
|
const content = item.content;
|
||||||
|
const scrollHeight = content.scrollHeight;
|
||||||
|
|
||||||
|
// Set initial state
|
||||||
|
content.style.maxHeight = `${scrollHeight}px`;
|
||||||
|
content.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
// Force reflow
|
||||||
|
content.offsetHeight;
|
||||||
|
|
||||||
|
// Add transition
|
||||||
|
content.style.transition = `max-height ${this.options.animationDuration}ms ease-in`;
|
||||||
|
|
||||||
|
// Animate to zero height
|
||||||
|
content.style.maxHeight = '0';
|
||||||
|
|
||||||
|
// Clean up after animation
|
||||||
|
setTimeout(() => {
|
||||||
|
content.style.overflow = '';
|
||||||
|
content.style.transition = '';
|
||||||
|
}, this.options.animationDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyboard(e, index) {
|
||||||
|
const currentItem = this.items[index];
|
||||||
|
if (!currentItem) return;
|
||||||
|
|
||||||
|
switch (e.key) {
|
||||||
|
case 'Enter':
|
||||||
|
case ' ':
|
||||||
|
e.preventDefault();
|
||||||
|
this.toggle(index);
|
||||||
|
break;
|
||||||
|
case 'ArrowDown':
|
||||||
|
e.preventDefault();
|
||||||
|
this.focusNext(index);
|
||||||
|
break;
|
||||||
|
case 'ArrowUp':
|
||||||
|
e.preventDefault();
|
||||||
|
this.focusPrev(index);
|
||||||
|
break;
|
||||||
|
case 'Home':
|
||||||
|
e.preventDefault();
|
||||||
|
this.focusFirst();
|
||||||
|
break;
|
||||||
|
case 'End':
|
||||||
|
e.preventDefault();
|
||||||
|
this.focusLast();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
focusNext(currentIndex) {
|
||||||
|
const nextIndex = currentIndex < this.items.length - 1 ?
|
||||||
|
currentIndex + 1 : 0;
|
||||||
|
this.items[nextIndex]?.trigger?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
focusPrev(currentIndex) {
|
||||||
|
const prevIndex = currentIndex > 0 ?
|
||||||
|
currentIndex - 1 : this.items.length - 1;
|
||||||
|
this.items[prevIndex]?.trigger?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
focusFirst() {
|
||||||
|
this.items[0]?.trigger?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
focusLast() {
|
||||||
|
this.items[this.items.length - 1]?.trigger?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
generateId() {
|
||||||
|
return `accordion-${Math.random().toString(36).substr(2, 9)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public API
|
||||||
|
getActiveItems() {
|
||||||
|
return Array.from(this.activeItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
isOpen(index) {
|
||||||
|
return this.items[index]?.isOpen || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
// Remove event listeners and reset state
|
||||||
|
this.items.forEach(item => {
|
||||||
|
if (item.trigger) {
|
||||||
|
item.trigger.removeAttribute('aria-expanded');
|
||||||
|
item.trigger.removeAttribute('aria-controls');
|
||||||
|
item.trigger.removeAttribute('tabindex');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.content) {
|
||||||
|
item.content.removeAttribute('aria-hidden');
|
||||||
|
item.content.removeAttribute('aria-labelledby');
|
||||||
|
item.content.removeAttribute('role');
|
||||||
|
item.content.style.maxHeight = '';
|
||||||
|
item.content.style.overflow = '';
|
||||||
|
item.content.style.transition = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
item.element.classList.remove('accordion__item--active');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.activeItems.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
233
vitekit-claude/src/scripts/components/tabs.js
Normal file
233
vitekit-claude/src/scripts/components/tabs.js
Normal file
|
|
@ -0,0 +1,233 @@
|
||||||
|
export class TabsComponent {
|
||||||
|
constructor(element, options = {}) {
|
||||||
|
this.element = element;
|
||||||
|
this.options = {
|
||||||
|
activeClass: 'tabs__tab--active',
|
||||||
|
panelActiveClass: 'tabs__panel--active',
|
||||||
|
keyboardNavigation: true,
|
||||||
|
autoResponsive: true,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
this.tabs = [];
|
||||||
|
this.panels = [];
|
||||||
|
this.activeIndex = 0;
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.bindElements();
|
||||||
|
this.bindEvents();
|
||||||
|
this.setInitialState();
|
||||||
|
|
||||||
|
if (this.options.autoResponsive) {
|
||||||
|
this.handleResponsive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bindElements() {
|
||||||
|
this.tabNav = this.element.querySelector('.tabs__nav');
|
||||||
|
this.tabContent = this.element.querySelector('.tabs__content');
|
||||||
|
this.tabs = Array.from(this.element.querySelectorAll('.tabs__tab'));
|
||||||
|
this.panels = Array.from(this.element.querySelectorAll('.tabs__panel'));
|
||||||
|
|
||||||
|
if (!this.tabNav || !this.tabContent || this.tabs.length === 0) {
|
||||||
|
console.warn('TabsComponent: Missing required elements');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEvents() {
|
||||||
|
// Tab click events
|
||||||
|
this.tabs.forEach((tab, index) => {
|
||||||
|
tab.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.activateTab(index);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keyboard navigation
|
||||||
|
if (this.options.keyboardNavigation) {
|
||||||
|
this.tabNav.addEventListener('keydown', (e) => {
|
||||||
|
this.handleKeyboard(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsive handling
|
||||||
|
if (this.options.autoResponsive) {
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.handleResponsive();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setInitialState() {
|
||||||
|
// Find initially active tab or default to first
|
||||||
|
const activeTab = this.tabs.find(tab =>
|
||||||
|
tab.classList.contains(this.options.activeClass)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (activeTab) {
|
||||||
|
this.activeIndex = this.tabs.indexOf(activeTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activateTab(this.activeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
activateTab(index) {
|
||||||
|
if (index < 0 || index >= this.tabs.length) return;
|
||||||
|
|
||||||
|
// Remove active state from all tabs and panels
|
||||||
|
this.tabs.forEach(tab => {
|
||||||
|
tab.classList.remove(this.options.activeClass);
|
||||||
|
tab.setAttribute('aria-selected', 'false');
|
||||||
|
tab.setAttribute('tabindex', '-1');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.panels.forEach(panel => {
|
||||||
|
panel.classList.remove(this.options.panelActiveClass);
|
||||||
|
panel.setAttribute('aria-hidden', 'true');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add active state to selected tab and panel
|
||||||
|
const activeTab = this.tabs[index];
|
||||||
|
const activePanel = this.panels[index];
|
||||||
|
|
||||||
|
if (activeTab && activePanel) {
|
||||||
|
activeTab.classList.add(this.options.activeClass);
|
||||||
|
activeTab.setAttribute('aria-selected', 'true');
|
||||||
|
activeTab.setAttribute('tabindex', '0');
|
||||||
|
|
||||||
|
activePanel.classList.add(this.options.panelActiveClass);
|
||||||
|
activePanel.setAttribute('aria-hidden', 'false');
|
||||||
|
|
||||||
|
this.activeIndex = index;
|
||||||
|
|
||||||
|
// Emit custom event
|
||||||
|
this.element.dispatchEvent(new CustomEvent('tabchange', {
|
||||||
|
detail: {
|
||||||
|
index,
|
||||||
|
tab: activeTab,
|
||||||
|
panel: activePanel
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyboard(e) {
|
||||||
|
const { activeElement } = document;
|
||||||
|
const currentIndex = this.tabs.indexOf(activeElement);
|
||||||
|
|
||||||
|
if (currentIndex === -1) return;
|
||||||
|
|
||||||
|
let newIndex = currentIndex;
|
||||||
|
|
||||||
|
switch (e.key) {
|
||||||
|
case 'ArrowLeft':
|
||||||
|
e.preventDefault();
|
||||||
|
newIndex = currentIndex > 0 ? currentIndex - 1 : this.tabs.length - 1;
|
||||||
|
break;
|
||||||
|
case 'ArrowRight':
|
||||||
|
e.preventDefault();
|
||||||
|
newIndex = currentIndex < this.tabs.length - 1 ? currentIndex + 1 : 0;
|
||||||
|
break;
|
||||||
|
case 'Home':
|
||||||
|
e.preventDefault();
|
||||||
|
newIndex = 0;
|
||||||
|
break;
|
||||||
|
case 'End':
|
||||||
|
e.preventDefault();
|
||||||
|
newIndex = this.tabs.length - 1;
|
||||||
|
break;
|
||||||
|
case 'Enter':
|
||||||
|
case ' ':
|
||||||
|
e.preventDefault();
|
||||||
|
this.activateTab(currentIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newIndex !== currentIndex) {
|
||||||
|
this.tabs[newIndex].focus();
|
||||||
|
this.activateTab(newIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleResponsive() {
|
||||||
|
const isMobile = window.innerWidth < 768;
|
||||||
|
|
||||||
|
if (isMobile && !this.element.classList.contains('tabs--responsive')) {
|
||||||
|
this.element.classList.add('tabs--responsive');
|
||||||
|
this.convertToAccordion();
|
||||||
|
} else if (!isMobile && this.element.classList.contains('tabs--responsive')) {
|
||||||
|
this.element.classList.remove('tabs--responsive');
|
||||||
|
this.convertToTabs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
convertToAccordion() {
|
||||||
|
// Add mobile accordion behavior
|
||||||
|
this.panels.forEach((panel, index) => {
|
||||||
|
const tab = this.tabs[index];
|
||||||
|
if (tab && panel) {
|
||||||
|
panel.setAttribute('data-tab-title', tab.textContent);
|
||||||
|
|
||||||
|
panel.addEventListener('click', (e) => {
|
||||||
|
if (e.target === panel.querySelector('::before') ||
|
||||||
|
e.target.closest('.tabs__panel') === panel) {
|
||||||
|
this.togglePanel(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
convertToTabs() {
|
||||||
|
// Remove mobile accordion behavior
|
||||||
|
this.panels.forEach(panel => {
|
||||||
|
panel.removeAttribute('data-tab-title');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
togglePanel(index) {
|
||||||
|
const panel = this.panels[index];
|
||||||
|
const isActive = panel.classList.contains(this.options.panelActiveClass);
|
||||||
|
|
||||||
|
if (isActive) {
|
||||||
|
panel.classList.remove(this.options.panelActiveClass);
|
||||||
|
} else {
|
||||||
|
this.activateTab(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public API
|
||||||
|
next() {
|
||||||
|
const nextIndex = this.activeIndex < this.tabs.length - 1 ?
|
||||||
|
this.activeIndex + 1 : 0;
|
||||||
|
this.activateTab(nextIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
prev() {
|
||||||
|
const prevIndex = this.activeIndex > 0 ?
|
||||||
|
this.activeIndex - 1 : this.tabs.length - 1;
|
||||||
|
this.activateTab(prevIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
goTo(index) {
|
||||||
|
this.activateTab(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
// Remove event listeners and reset state
|
||||||
|
this.tabs.forEach(tab => {
|
||||||
|
tab.removeAttribute('aria-selected');
|
||||||
|
tab.removeAttribute('tabindex');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.panels.forEach(panel => {
|
||||||
|
panel.removeAttribute('aria-hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.element.classList.remove('tabs--responsive');
|
||||||
|
}
|
||||||
|
}
|
||||||
268
vitekit-claude/src/scripts/components/toast.js
Normal file
268
vitekit-claude/src/scripts/components/toast.js
Normal file
|
|
@ -0,0 +1,268 @@
|
||||||
|
import Toastify from 'toastify-js';
|
||||||
|
|
||||||
|
export class ToastComponent {
|
||||||
|
static defaultOptions = {
|
||||||
|
duration: 4000,
|
||||||
|
position: 'right',
|
||||||
|
gravity: 'top',
|
||||||
|
close: true,
|
||||||
|
stopOnFocus: true,
|
||||||
|
style: {
|
||||||
|
background: 'transparent',
|
||||||
|
boxShadow: 'none',
|
||||||
|
padding: '0',
|
||||||
|
borderRadius: '0'
|
||||||
|
},
|
||||||
|
offset: {
|
||||||
|
x: 16,
|
||||||
|
y: 16
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static types = {
|
||||||
|
success: {
|
||||||
|
className: 'toast-success',
|
||||||
|
icon: '✓'
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
className: 'toast-error',
|
||||||
|
icon: '✕'
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
className: 'toast-warning',
|
||||||
|
icon: '⚠'
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
className: 'toast-info',
|
||||||
|
icon: 'ℹ'
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
className: 'toast-default',
|
||||||
|
icon: '●'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static show(message, type = 'default', options = {}) {
|
||||||
|
if (!message) {
|
||||||
|
console.warn('ToastComponent: Message is required');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeConfig = this.types[type] || this.types.default;
|
||||||
|
const config = { ...this.defaultOptions, ...options };
|
||||||
|
|
||||||
|
// Build toast content
|
||||||
|
const content = this.buildToastContent(message, config.title, typeConfig.icon);
|
||||||
|
|
||||||
|
const toastOptions = {
|
||||||
|
text: content,
|
||||||
|
duration: config.duration,
|
||||||
|
gravity: config.gravity,
|
||||||
|
position: config.position,
|
||||||
|
stopOnFocus: config.stopOnFocus,
|
||||||
|
className: `toastify ${typeConfig.className}`,
|
||||||
|
escapeMarkup: false,
|
||||||
|
offset: config.offset,
|
||||||
|
style: {
|
||||||
|
...this.defaultOptions.style,
|
||||||
|
...config.style
|
||||||
|
},
|
||||||
|
onClick: config.onClick,
|
||||||
|
onClose: config.onClose
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create and show toast
|
||||||
|
const toast = Toastify(toastOptions);
|
||||||
|
toast.showToast();
|
||||||
|
|
||||||
|
// Add progress bar if duration is set
|
||||||
|
if (config.duration > 0 && config.showProgress !== false) {
|
||||||
|
this.addProgressBar(toast, config.duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toast;
|
||||||
|
}
|
||||||
|
|
||||||
|
static buildToastContent(message, title, icon) {
|
||||||
|
let content = '<div class="toast-enhanced">';
|
||||||
|
|
||||||
|
if (icon) {
|
||||||
|
content += `<div class="toast-enhanced__icon">${icon}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
content += '<div class="toast-enhanced__content">';
|
||||||
|
|
||||||
|
if (title) {
|
||||||
|
content += `<div class="toast-enhanced__title">${this.escapeHtml(title)}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
content += `<div class="toast-enhanced__message">${this.escapeHtml(message)}</div>`;
|
||||||
|
content += '</div>';
|
||||||
|
|
||||||
|
content += '<button class="toast-enhanced__close" onclick="this.closest(\'.toastify\').remove()"></button>';
|
||||||
|
content += '</div>';
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
static addProgressBar(toast, duration) {
|
||||||
|
const toastElement = toast.toastElement;
|
||||||
|
if (!toastElement) return;
|
||||||
|
|
||||||
|
const progressBar = document.createElement('div');
|
||||||
|
progressBar.className = 'toast-progress';
|
||||||
|
progressBar.innerHTML = '<div class="toast-progress__bar"></div>';
|
||||||
|
|
||||||
|
toastElement.appendChild(progressBar);
|
||||||
|
|
||||||
|
const bar = progressBar.querySelector('.toast-progress__bar');
|
||||||
|
if (bar) {
|
||||||
|
bar.style.animationDuration = `${duration}ms`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static escapeHtml(text) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience methods
|
||||||
|
static success(message, options = {}) {
|
||||||
|
return this.show(message, 'success', options);
|
||||||
|
}
|
||||||
|
|
||||||
|
static error(message, options = {}) {
|
||||||
|
return this.show(message, 'error', options);
|
||||||
|
}
|
||||||
|
|
||||||
|
static warning(message, options = {}) {
|
||||||
|
return this.show(message, 'warning', options);
|
||||||
|
}
|
||||||
|
|
||||||
|
static info(message, options = {}) {
|
||||||
|
return this.show(message, 'info', options);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advanced toast types
|
||||||
|
static promise(promise, messages = {}, options = {}) {
|
||||||
|
const {
|
||||||
|
loading = 'Загрузка...',
|
||||||
|
success = 'Успешно!',
|
||||||
|
error = 'Ошибка!'
|
||||||
|
} = messages;
|
||||||
|
|
||||||
|
// Show loading toast
|
||||||
|
const loadingToast = this.show(loading, 'info', {
|
||||||
|
duration: 0, // Don't auto-close
|
||||||
|
showProgress: false,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise
|
||||||
|
.then(result => {
|
||||||
|
// Close loading toast
|
||||||
|
if (loadingToast && loadingToast.toastElement) {
|
||||||
|
loadingToast.toastElement.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show success toast
|
||||||
|
const successMessage = typeof success === 'function' ? success(result) : success;
|
||||||
|
this.success(successMessage, options);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
// Close loading toast
|
||||||
|
if (loadingToast && loadingToast.toastElement) {
|
||||||
|
loadingToast.toastElement.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show error toast
|
||||||
|
const errorMessage = typeof error === 'function' ? error(err) : error;
|
||||||
|
this.error(errorMessage, options);
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static confirm(message, options = {}) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const confirmOptions = {
|
||||||
|
duration: 0,
|
||||||
|
title: 'Подтверждение',
|
||||||
|
showProgress: false,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = `
|
||||||
|
<div class="toast-enhanced__content">
|
||||||
|
<div class="toast-enhanced__icon">?</div>
|
||||||
|
<div class="toast-enhanced__text">
|
||||||
|
<div class="toast-enhanced__title">${this.escapeHtml(confirmOptions.title)}</div>
|
||||||
|
<div class="toast-enhanced__message">${this.escapeHtml(message)}</div>
|
||||||
|
<div class="toast-enhanced__actions" style="margin-top: 12px; display: flex; gap: 8px;">
|
||||||
|
<button class="btn btn--sm btn--primary toast-confirm-yes">Да</button>
|
||||||
|
<button class="btn btn--sm btn--secondary toast-confirm-no">Нет</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const toast = Toastify({
|
||||||
|
text: content,
|
||||||
|
gravity: 'top',
|
||||||
|
position: 'center',
|
||||||
|
className: 'toastify toast-warning toast-enhanced toast-confirm',
|
||||||
|
escapeMarkup: false,
|
||||||
|
duration: 0,
|
||||||
|
stopOnFocus: true
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.showToast();
|
||||||
|
|
||||||
|
// Add event listeners
|
||||||
|
const toastElement = toast.toastElement;
|
||||||
|
if (toastElement) {
|
||||||
|
const yesBtn = toastElement.querySelector('.toast-confirm-yes');
|
||||||
|
const noBtn = toastElement.querySelector('.toast-confirm-no');
|
||||||
|
|
||||||
|
if (yesBtn) {
|
||||||
|
yesBtn.addEventListener('click', () => {
|
||||||
|
toastElement.remove();
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noBtn) {
|
||||||
|
noBtn.addEventListener('click', () => {
|
||||||
|
toastElement.remove();
|
||||||
|
resolve(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility method to remove all toasts
|
||||||
|
static clear() {
|
||||||
|
const toasts = document.querySelectorAll('.toastify');
|
||||||
|
toasts.forEach(toast => toast.remove());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to update toast position for responsive design
|
||||||
|
static updatePosition() {
|
||||||
|
const isMobile = window.innerWidth < 768;
|
||||||
|
this.defaultOptions.position = isMobile ? 'center' : 'right';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update position on resize
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
ToastComponent.updatePosition();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set initial position
|
||||||
|
ToastComponent.updatePosition();
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user