Commit 75a1b189 authored by Evgeny Talagaev's avatar Evgeny Talagaev

Start project

parents
Pipeline #88 failed with stages
# Универсальные настройки редактора
#
# http://editorconfig.org
# https://habrahabr.ru/post/220131/
# https://packagecontrol.io/packages/EditorConfig
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
src/js/head-script.js
{
"parser": "babel-eslint",
"extends": "eslint:recommended",
"globals": {
"require": true,
},
"rules": {
"strict": 0
}
}
* text=auto
design/
build/*
!build/readme.md
src/pug/mixins.pug
src/js/entry.js
src/scss/style.scss
src/blocks/sprite-svg/img/sprite.svg
src/blocks/sprite-png/sprite-png.scss
src/blocks/sprite-png/img/sprite-*.png
__MACOSX
.Saved/
_arc/
bower_components/
node_modules/
.vscode/
.publish/
.DS_Store
*.log
*.sublime-*
*.rar
*.zip
.idea
ex_files
Thumbs.db
{
"validateExtensions": true,
"validateDivTags": true,
"validateAttributeSeparator": { "separator": ", ", "multiLineSeparator": ",\n " },
"validateAttributeQuoteMarks": "'",
"requireSpecificAttributes": [ { "img": [ "src", "alt" ] } ],
"requireSpaceAfterCodeOperator": true,
"requireLowerCaseTags": true,
"requireClassLiteralsBeforeIdLiterals": true,
"requireClassLiteralsBeforeAttributes": true,
"disallowTrailingSpaces": true,
"disallowSpacesInsideAttributeBrackets": true,
"disallowLegacyMixinCall": true,
"disallowDuplicateAttributes": true
}
src/scss/style.scss
src/blocks/sprite-png/sprite-png.scss
{
"syntax": "scss",
"plugins": [
"stylelint-selector-bem-pattern",
"stylelint-order"
],
"rules": {
# Проверка совпадения селектора и имени файла, БЭМ-ошибок
# https://github.com/postcss/postcss-bem-linter#custom-patterns
"plugin/selector-bem-pattern": [{
"implicitComponents": true,
"componentName": "[-a-z]+",
"componentSelectors": {
"initial": "^(\\.{componentName}(__[a-z]+|--[-a-z0-9]+|__[-a-z]+--[-a-z0-9]+|.[-a-z]+|__[a-z]+.[-a-z]+)?)$",
"combined": "^.+$"
},
"ignoreSelectors": [
"^\\.(no-)?js(-modal-open)?$",
"a(\\[[a-z]+|:[a-z]+)?",
"h[1-6]",
"pre",
"blockquote",
"thead",
"tr",
"img",
"img",
"p",
"table",
"^&:empty",
"#{\\$[-a-z]+}__[-a-z]+",
"^\\*$"
],
},
{
"severity": "warning",
"message": "Селектор не из имени файла или БЭМ-ошибка"
}],
# Проверка очередности в контексте селектора
# https://github.com/hudochenkov/stylelint-order/blob/master/rules/order/README.md
"order/order": [
# Кастомные свойства
"custom-properties",
# Инклуды примесей
{
"type": "at-rule",
"name": "include",
},
# Свойства
"declarations",
# Медиаусловия
{
"type": "at-rule",
"name": "media",
},
# Псевдоселекторы и псевдоэлементы
{
"type": "rule",
"selector": "^&:\\w+$"
},
# Сторонние вложенные селекторы
{
"type": "rule",
"selector": "^\\.[-_a-zA-Z0-9]+"
},
# Элементы блока
{
"type": "rule",
"selector": "^&__[-a-z0-9]+"
},
# Модификаторы блока
{
"type": "rule",
"selector": "^&--[-a-z0-9]+"
},
],
# Проверка очередности правил
# https://github.com/hudochenkov/stylelint-order/blob/master/rules/properties-order/README.md
"order/properties-order": [[
{
"properties": [
"content",
"box-sizing",
"display",
"flex",
"flex-wrap",
"flex-basis",
"flex-direction",
"flex-flow",
"flex-grow",
"flex-shrink",
"align-content",
"align-items",
"align-self",
"justify-content",
"order",
"grid",
"grid-area",
"grid-auto-*",
"grid-column",
"grid-column-*",
"grid-gap",
"grid-row",
"grid-row-*",
"grid-template",
"grid-template-*",
"position",
"z-index",
"top",
"right",
"bottom",
"left",
"width",
"min-width",
"max-width",
"height",
"min-height",
"max-height",
"margin",
"margin-*",
"border",
"border-*",
"padding",
"padding-*",
"overflow",
"overflow-*",
]
},
{
# "emptyLineBefore": "always",
"properties": [
"transform",
"transform-*",
"backface-visibility",
"perspective",
"perspective-origin",
"transition",
"transition-*",
"animation",
"animation-*",
]
},
],
{
"severity": "warning",
}
],
# http://stylelint.io/user-guide/rules/
"color-hex-case": ["lower", {
"severity": "warning",
"message": "HEX цвета буквами строчными указывать следует"
}],
"color-named": ["never", {
"message": "Цвета ключевыми словами нельзя писать"
}],
"color-no-invalid-hex": [true, {
"message": "HEX цвета правильно написаны быть должны"
}],
"font-family-name-quotes": ["always-where-recommended", {
"message": "В кавычки шрифта название брать следует"
}],
"font-family-no-duplicate-names": [true, {
"message": "Шрифтов название повторять не следует"
}],
"font-family-no-missing-generic-family-keyword": [true, {
"message": "Указанием sans-serif/serif... это заканчиваться должно"
}],
"font-weight-notation": ["numeric", {
"message": "Плотность — цифрой: https://goo.gl/58eKi7",
ignore: ["relative"]
}],
# "function-calc-no-invalid": [true, {
# "message": "Для calc() некорректен синтаксис этот"
# }],
"function-calc-no-unspaced-operator": [true, {
"message": "Для calc() пробелы вокруг операторов нужны"
}],
"function-linear-gradient-no-nonstandard-direction": [true, {
"message": "Синтаксис градиентов стандартен: https://goo.gl/ba4HEc"
}],
"function-comma-space-after": ["always", {
"message": "После запятой пробел необходим"
}],
"function-comma-space-before": ["never", {
"message": "Перед запятой пробела быть не должно"
}],
"function-comma-newline-before": ["never-multi-line", {
"message": "Переноса перед запятой быть не должно"
}],
"function-comma-newline-after": ["never-multi-line", {
"message": "Переноса после запятой быть не должно"
}],
"function-parentheses-newline-inside": ["never-multi-line", {
"message": "Переноса после скобки быть не должно"
}],
"function-max-empty-lines": [0, {
"message": "Пустые строки в функциях нужно убрать"
}],
"function-name-case": ["lower", {
"message": "Функций имена строчными буквами набирать следует"
}],
"function-whitespace-after": ["always", {
"message": "Всегда нужны пробелы между функциями"
}],
"function-url-scheme-allowed-list": [["data", "/^https/"], {
"message": "Картинки фоновые только data: URL и по https хочешь ты"
}],
"number-leading-zero": ["always", {
"message": "Нуль перед точкой явно указать следует"
}],
"number-max-precision": [6, {
"message": "Максимум 6 символов после точки в десятичной дроби"
}],
"number-no-trailing-zeros": [true, {
"message": "Лишний (необязательный) нуль в дроби"
}],
"string-no-newline": [true, {
"message": "В строчных данных не должно быть переносов"
}],
"string-quotes": ["single", {
"message": "Кавычки одиночные в стилях на проекте у нас"
}],
"length-zero-no-unit": [true, {
"message": "Нулю не нужны измерения единицы"
}],
"time-min-milliseconds": [100, {
"message": "Время менее 100мс — нет смысла использовать"
}],
"unit-case": ["lower", {
"message": "Измерения единицы буквами строчными надобно"
}],
"value-keyword-case": ["lower", {
"message": "Значения свойств буквами строчными надобно",
"ignoreProperties": ["/font-/", ]
}],
"unit-no-unknown": [true, {
"message": "Нет единиц измерения таких"
}],
"value-no-vendor-prefix": [true, {
"message": "Вендорные префиксы в значениях использовать не следует"
}],
"property-no-vendor-prefix": [true, {
"message": "Свойства с вендорными префиксами использовать не следует"
}],
"value-list-comma-newline-after": ["always-multi-line", {
"message": "Значения свойств переносов иметь не должны"
}],
"value-list-comma-newline-before": ["never-multi-line", {
"message": "Значения свойств переносов иметь не должны"
}],
"value-list-comma-space-after": ["always-single-line", {
"message": "В значениях после запятой пробел необходим"
}],
"value-list-comma-space-before": ["never", {
"message": "В значениях перед запятой пробел запрещен"
}],
"property-disallowed-list": [["text-rendering", ], {
"message": "Свойство из чёрного списка"
}],
"property-case": ["lower", {
"message": "Свойств значение буквами строчными указывать следует"
}],
"property-no-unknown": [true, {
"message": "Такого свойства нет"
}],
"declaration-bang-space-after": ["never", {
"message": "После знака восклицательного пробел убрать следует"
}],
"declaration-bang-space-before": ["always", {
"message": "Перед знаком восклицательным пробел оставить хочешь ты"
}],
"declaration-colon-newline-after": ["always-multi-line", {
"message": "Для свойств множественных каждое — на строке своей быть должно"
}],
"declaration-colon-space-after": ["always-single-line", {
"message": "Правило задавая, один пробел после двоеточия ставь"
}],
"declaration-colon-space-before": ["never", {
"message": "Правило задавая, пробел перед двоеточием не используй"
}],
"declaration-no-important": [true, {
# "severity": "warning",
"message": "!important с опаской применять следует"
}],
"declaration-property-unit-disallowed-list": [{
"font-size": ["pt"],
"/^animation/": ["ms"],
"/^padding/": ["%"]
}, {
"message": "Для этого свойства данные единицы измерения в чёрном списке"
}],
# "declaration-property-value-blacklist": [{
# "transform": ["/scale3d/", "/rotate3d/", "/translate3d/"],
# "position": ["fixed"],
# "color": ["/^green/"],
# }, {
# "message": "Для этого свойства значение это в чёрном списке"
# }],
"unit-disallowed-list": [["mm","cm","in","pc",], {
"message": "Запрещены единицы измерения эти"
}],
"declaration-block-no-duplicate-properties": [true, {
"message": "Правила повторять не следует"
}],
"declaration-block-no-shorthand-property-overrides": [true, {
"message": "Свойство это неочевидно перебивает другое для селектора этого"
}],
"declaration-block-semicolon-newline-after": ["always", {
"message": "После точки с запятой строки перенос необходим"
}],
"declaration-block-single-line-max-declarations": [1, {
"message": "На строке одной правилу одному место есть только"
}],
"declaration-block-trailing-semicolon": ["always", {
"message": "Свойство каждое точкой с запятой заканчивать хочешь ты"
}],
"block-closing-brace-empty-line-before": ["never", {
"message": "Перед «}» пустой строки быть не должно"
}],
"block-closing-brace-newline-after": ["always", {
"message": "После «}» переносу строки быть должно"
}],
"block-closing-brace-newline-before": ["always", {
"message": "Перед «}» переносу строки быть должно"
}],
# "block-no-empty": [true, {
# "severity": "warning",
# "message": "Пустые блоки писать не следует"
# }],
"block-opening-brace-newline-after": ["always", {
"message": "После «{» перенос строки быть должен"
}],
"block-opening-brace-space-before": ["always", {
"message": "Перед «{» один пробел нужен"
}],
"selector-attribute-brackets-space-inside": ["never", {
"message": "В селекторе атрибута пробел использовать не следует"
}],
"selector-attribute-operator-space-after": ["never", {
"message": "В селекторе атрибута пробел писать не должен ты"
}],
"selector-attribute-operator-space-before": ["never", {
"message": "В селекторе атрибута пробел писать не должен ты"
}],
"selector-attribute-quotes": ["always", {
"message": "В селекторе атрибута кавычки писать должен ты"
}],
"selector-combinator-space-after": ["always", {
"message": "После комбинатора в селекторе пробел нужен"
}],
"selector-combinator-space-before": ["always", {
"message": "Перед комбинатором в селекторе пробел нужен"
}],
"selector-attribute-operator-disallowed-list": [["id"], {
"message": "По ID джедай стилизовать не должен"
}],
"selector-max-compound-selectors": [3, {
"severity": "warning",
"message": "Селектор более чем 3-составной не хочешь ты"
}],
"selector-no-qualifying-type": [true, {
"severity": "warning",
"message": "Возможно, перенасыщенный селектор (использован селектор типа)",
ignore: ["attribute"]
}],
"selector-pseudo-class-case": ["lower", {
"message": "Псевдоклассы набирать буквами строчными надо"
}],
"selector-pseudo-class-no-unknown": [true, {
"message": "Псевдокласса такого существование сомнительно"
}],
"selector-pseudo-class-parentheses-space-inside": ["never", {
"message": "Внутри скобок в псевдоселекторах пробелы ставить не следует"
}],
"selector-pseudo-element-case": ["lower", {
"message": "Псевдоэлементы набирать буквами строчными надо"
}],
"selector-pseudo-element-no-unknown": [true, {
"message": "Псевдоэлемента такого на светлой строне силы нет"
}],
"selector-type-case": ["lower", {
"message": "Селекторы буквами строчными набирать следует"
}],
"selector-type-no-unknown": [true, {
"message": "Селектора такого на светлой стороне силы нет"
}],
"selector-max-empty-lines": [0, {
"message": "Пустые строки в селекторах недопустимы"
}],
"selector-list-comma-newline-after": ["always", {
"message": "После запятой перенос строки ставить нужно"
}],
"selector-list-comma-newline-before": ["never-multi-line", {
"message": "Перед запятой переноса строки быть не должно"
}],
"selector-list-comma-space-after": ["always-single-line", {
"message": "После запятой пробел быть должен"
}],
"selector-list-comma-space-before": ["never", {
"message": "Перед запятой пробела быть не должно"
}],
"rule-empty-line-before": ["always", {
"message": "Перед селектором вложенным строку пустую оставь",
ignore: ["after-comment"]
}],
"media-feature-colon-space-after": ["always", {
"message": "В @media после «:» пробелы необходимы"
}],
"media-feature-colon-space-before": ["never", {
"message": "В @media перед «:» пробелов быть не должно"
}],
"media-feature-name-case": ["lower", {
"message": "Строчные буквы использовать следует"
}],
"media-feature-name-no-vendor-prefix": [true, {
"severity": "warning",
"message": "Использовать вендорные префиксы не следует"
}],
"media-feature-parentheses-space-inside": ["never", {
"message": "Пробелы после «(» и перед «)» нельзя использовать"
}],
"media-feature-range-operator-space-after": ["always", {
"message": "Пробел после оператора диапазона ставить следует"
}],
"media-feature-range-operator-space-before": ["always", {
"message": "Пробел перед оператором диапазона ставить следует"
}],
"at-rule-disallowed-list": [["extend", ], {
"message": "В чёрном списке @-правило это"
}],
"at-rule-empty-line-before": ["always", {
"message": "Перед @-правилами строку пустую оставь (кроме @import и @include)",
ignoreAtRules: ["import", "include", "function", "return", "if", "else"],
ignore: ["after-comment"]
}],
"at-rule-name-case": ["lower", {
"message": "@-правила буквами строчными набирать следует"
}],
"at-rule-name-space-after": ["always", {
"message": "После @-правила пробел ставить следует"
}],
"at-rule-no-unknown": [true, {
"message": "Неизвестное @-правило",
ignoreAtRules: [
"/^at-/",
"/^mixin/",
"/^include/",
"/^if/",
"/^else/",
"/^function/",
"/^return/",
"/^each/",
"/^while/",
]
}],
"at-rule-no-vendor-prefix": [true, {
"message": "Для @-правил вендорные префиксы использовать не следует"
}],
"at-rule-semicolon-newline-after": ["always", {
"message": "В @-правилах после ; новая строка быть должна"
}],
"at-rule-semicolon-space-before": ["never", {
"message": "В @-правилах перед ; пробелов быть не должно"
}],
"comment-empty-line-before": ["always", {
"message": "Перед комментом пустая строка требуется",
except: ["first-nested"],
ignore: ["stylelint-commands"]
}],
"comment-no-empty": [true, {
"message": "Пустой коммент убрать нужно"
}],
"comment-whitespace-inside": ["always", {
"message": "В комменте пробелы в начале о конце нужны"
}],
"comment-word-disallowed-list": [ [/((Х|х)+уй|(Х|х)уе|(Х|х)уё|(Б|б)ляд|(М|м)уда|(П|п)идо|(П|п)еди|(П|п)еде|(^|\s)+(Б|б)ля)/], {
"message": "Комментарий херовый, при себе оставь его"
}],
"indentation": [2, {
"message": "Использовать пробела два для отступов должен ты"
}],
"max-empty-lines": [3, {
"message": "Максимум линий пустых допустимо: 3"
}],
"max-nesting-depth": [3, {
"message": "Вложения боле 3 уровней — на тёмную сторону путь",
"severity": "warning",
"ignore": ["blockless-at-rules"]
}],
"no-duplicate-selectors": [true, {
"message": "Селекторы дублировать не хочешь ты"
}],
"no-eol-whitespace": [true, {
"message": "Пустые символы в конце строки. Используй https://goo.gl/Co2tZ8"
}],
"no-extra-semicolons": [true, {
"message": "Две точки с запятой подряд не хочешь ты"
}],
"no-missing-end-of-source-newline": [true, {
"message": "Файл пустой строкой кончаться должен: https://goo.gl/Co2tZ8"
}],
"media-feature-name-no-unknown": [true, {
"message": "Медиаусловия такого на светлой стороне силы нет"
}],
"keyframe-declaration-no-important": [true, {
"message": "!important в keyframe некоторыми браузерами игнорируется"
}],
# "no-descending-specificity": [true, {
# "message": "Менее специфичный селектор, чем написанный выше"
# }],
"no-duplicate-at-import-rules": [true, {
"message": "Повторный импорт файла не хочешь ты"
}],
"no-empty-source": [true, {
"message": "Пустые файлы не хочешь ты"
}],
"shorthand-property-no-redundant-values": [true, {
"message": "В значении свойства избыточность лишняя, короче можно написать"
}],
"selector-combinator-disallowed-list": [[">", ], {
"severity": "warning",
"message": "Комбинатор > опасно связывает CSS с DOM"
}],
"selector-max-attribute": [1, {
# "severity": "warning",
"message": "Атрибутов в селекторе более одного использовать не стоит"
}],
"selector-max-type": [2, {
# "severity": "warning",
"message": "Селекторов по тегу более двух использовать не стоит"
}],
"selector-max-class": [2, {
# "severity": "warning",
"message": "Классов в селекторе более двух использовать не стоит"
}],
"selector-max-combinators": [2, {
# "severity": "warning",
"message": "Комбинаторов в селекторе более двух использовать не стоит"
}],
# "selector-nested-pattern": ["^(&|\\.\\w+\\s+&)(__\\w|--\\w)?", {
# "message": "Вкладывать такой селектор нельзя"
# }],
"selector-pseudo-class-disallowed-list": [["/^nth-/", ], {
# "severity": "warning",
"message": "Псевдокласс такой использовать не стоит"
}],
"value-list-max-empty-lines": [0, {
"message": "Пустые строки в значениях свойств недопустимы"
}],
"declaration-block-semicolon-newline-before": ["never-multi-line", {
"message": "Перенос перед символом ; недопустим"
}],
"declaration-block-semicolon-space-before": ["never", {
"message": "Пробел перед символом ; недопустим"
}],
"selector-descendant-combinator-no-non-space": [true, {
"message": "Комбинированные селекторы одним пробелом разделять следует"
}],
"linebreaks": ["unix", {
"severity": "warning",
"message": "Окончания строк должны быть LF. Настроить редактор следует"
}],
}
}
language: node_js
node_js:
- "14.15"
notifications:
email: true
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
# Стартовый проект с gulp ![Test Status](https://travis-ci.org/nicothin/NTH-start-project.svg?branch=master) [![devDependencies Status](https://david-dm.org/nicothin/NTH-start-project/dev-status.svg)](https://david-dm.org/nicothin/NTH-start-project?type=dev) [![dependencies Status](https://david-dm.org/nicothin/NTH-start-project/status.svg)](https://david-dm.org/nicothin/NTH-start-project)
## Парадигма
- **Именование классов по БЭМ**, разметка в [pug](https://pugjs.org/), стилизация [Sass](http://sass-lang.com/). См. [как работать с CSS-препроцессорами и БЭМ](http://nicothin.github.io/idiomatic-pre-CSS/).
- **Каждый БЭМ-блок в своей папке** внутри `src/blocks/`. Имена файлов в такой папке должны совпадать с именем блока. См. ниже про Блоки.
- **Есть файл конфигурации**, определяющий попадание в сборку дополнительных файлов и неиспользованных в разметке блоков. Смотри `config.js`.
- **Можно использовать глобальные файлы**:
- `src/js/script.js` — общий js, берётся в сборку по умолчанию,
- `src/scss/print.scss` — стили печати, не берутся в сборку по умолчанию (см. `config.js`).
- **Список pug-примесей `src/pug/mixins.pug` генерируется автоматически** и содержит `include` существующих pug-файлов всех блоков.
- **Диспетчер подключения стилей `src/scss/style.scss` генерируется автоматически** и содержит импорты стилевых файлов использованных в разметке блоков и импорты доп. файлов, указанных в `config.js`.
- **Входная точка обработки js (`src/js/entry.js`) генерируется автоматически** и содержит `require` js-файлов использованных в разметке блоков и доп. файлов, указанных в `config.js`.
- **Используется кодгайд** (относительно жёсткий), его проверка делается перед коммитом или вручную (`npm run test`), ошибки выводятся в терминал.
- **Есть механизм быстрого создания нового блока**: `node createBlock.js new-block` (создаёт папки и scss-файл). После имени нового блока можно дописать нужные расширения.
## Установка
Требуются установленный [git](https://git-scm.com/) и [Node.js (LTS)](https://nodejs.org/en/).
1. Открыть терминал, попасть в папку проектов, клонировать этот репозиторий: `git clone https://github.com/nicothin/NTH-start-project.git my-new-project` (где `my-new-project` — папка для нового проекта).
2. Перейти в папку нового проекта (в этом примере — `cd my-new-project`).
3. Удалить историю разработки: `rm -rf .git`
4. Установить зависимости проекта: `npm i` (может быть долго, особенно на Windows).
## Команды
```bash
npm start # запуск сервера разработки (сборка БЕЗ библиотеки блоков)
npm start deploy # отправка содержимого папки сборки на gh-pages (нужен репозиторий на github.com)
npm run build # сборка БЕЗ библиотеки блоков и без запуска сервера разработки
npm run wlib # запуск сервера разработки (сборка проекта с библиотекой блоков)
npm run test # проверка всех pug-, scss- и js-файлов на соответствие правилам (см. .pug-lintrc, .stylelintrc и eslintrc соответственно)
npm run test:pug # проверить только pug-файлы
npm run test:style # проверить только scss-файлы
npm run test:js # проверить только js-файлы
```
## Структура
```bash
build/ # Папка сборки (результат работы над проектом)
src/ # Исходники
blocks/ # Блоки (подпапки с блоками)
favicon/ # Фавиконки (копирование прописать в config.js)
fonts/ # Шрифты (копирование прописать в config.js, подключение в src/blocks/page/page.scss)
img/ # Общие для всех блоков изображения (копирование прописать в config.js)
js/ # Общие js-файлы, в т.ч. точка сборки для webpack и общие модули
pages/ # Страницы проекта (при компиляции: src/pages/index.pug → build/index.html)
pug/ # Служебные pug-файлы (шаблоны страниц, примеси)
scss/ # Служебные стилевые файлы (диспетчер подключений, переменные, примеси)
```
## Как это работает
При `npm start` запускается дефолтная задача gulp:
1. Очищается папка сборки (`build/`).
5. Записывается файл `src/pug/mixins.pug` с includ-ами pug-файлов всех блоков.
5. Компилируются файлы страниц (из `src/pages/**/*.pug` в `build/*.html`).
5. Из скомпилированных html-файлов извлекаются все классы уровня БЭМ-блока. На основании этого списка будут построены диспетчер подключения стилей и список всех js-файлов проекта.
5. Генерируются спрайты (если блоки спрайтов используются), в папку сборки копируются картинки блоков и доп. файлы из секции `addAssets` файла `config.js`.
5. Записывается диспетчер подключения стилей `src/scss/style.scss`, в котором:
- Импорты файлов из секции `addStyleBefore` файла `config.js`. По умолчанию — SCSS-переменные и примеси.
- Импорты файлов БЭМ-блоков, упомянутых в секции `alwaysAddBlocks` файла `config.js`. Таким образом, можно взять в сборку любой блок, даже если его css-класс не упоминается в разметке страниц.
- Импорты файлов БЭМ-блоков, использующихся в разметке.
- Импорты файлов из секции `addStyleAfter` файла `config.js`.
5. Записывается входная точка обработки скриптов `src/js/entry.js`, в которой:
- `require` файлов из секции `addJsBefore` файла `config.js`.
- `require` файлов БЭМ-блоков, использующихся в разметке.
- `require` файлов БЭМ-блоков, упомянутых в секции `alwaysAddBlocks` файла `config.js`.
- `require` файлов из секции `addJsAfter` файла `config.js`.
5. Компилируется диспетчер подключения стилей (`src/scss/style.scss`). Результат обрабатываются плагинами PostCSS.
6. Обрабатывается входная точка Javascript (`src/js/entry.js`). Используется Webpack.
5. Запускается локальный сервер и слежение за файлами для пересборки.
## Блоки
Каждый блок лежит в `src/blocks/` в своей папке.
Возможное содержимое блока:
```bash
demo-block/ # Папка блока.
img/ # Изображения, используемые этим блоком (копируются в папку сборки).
demo-block.pug # Разметка (pug-примесь, отдающая разметку этого блока, описание API примеси).
demo-block.scss # Стилевой файл этого блока (без стилей других блоков).
demo-block.js # js-файл блока (без скриптов других блоков).
readme.md # Описание для документации, подсказки.
```
### Особенный блок `page`
На теге `<html>` по умолчанию прописан класс `page`, что делает одноимённый блок «глобальным». В стилях этого блока прописываются:
- подключение сторонних шрифтов,
- смена боксовой модели,
- cтили шаблона (см. `src/pug/layout.pug`),
- стилизация тегов по умолчанию (аналог Normalize.CSS).
### Удобное создание нового блока
```bash
# формат: node createBlock.js ИМЯБЛОКА [доп. расширения через пробел]
node createBlock.js demo-block # создаст папку блока, demo-block.scss, подпапку img/ для этого блока
node createBlock.js demo-block pug js # создаст папку блока, demo-block.scss, demo-block.pug, demo-block.js, подпапку img/ для этого блока
```
Если блок уже существует, файлы не будут затёрты, но создадутся те файлы, которые ещё не существуют.
## Сторонние npm-модули
Если нужно взять в сборку сторонний модуль, то после его установки (к примеру — `npm i package-name`) нужно:
1. Прописать `require` в js-файле проектного блока (там же писать всё, что касается работы с этим модулем). Если сторонний модуль нужен без привязки к какому-либо проектому блоку, прописать `require` в `src/js/script.js` (см. пример в файле).
2. Если нужно брать в сборку стилевые файлы модуля, прописать их в секции `addStyleBefore` файла `config.js` (пример в файле).
3. Если нужно брать в сборку дополнительные файлы модуля, прописать их в `addAssets` файла `config.js` с указанием в какую папку их копировать (пример в файле).
## Разметка
Используется [pug](https://pugjs.org/api/getting-started.html).
Все страницы (см. `src/pages/index.pug`) являются расширениями шаблонов из `src/pug` (см. [наследование шаблонов](https://pugjs.org/language/inheritance.html)), в страницах описывается только содержимое «шапки», «подвала» и контентной области (см. [блоки](https://pugjs.org/language/inheritance.html#block-append-prepend)).
Каждый блок (в `src/blocks/`) может (не обязан) содержать одноимённый pug-файл с одноименной примесью, который при старте сервера разработки будет взят includ-ом в файл `src/pug/mixins.pug`.
## Стили
Диспетчер подключений (`src/scss/style.scss`) формируется автоматически при старте сервера разработки.
Каждый блок (в `src/blocks/`) может содержать одноимённый scss-файл со стилями этого блока. Если блок используется в разметке (или упомянут в `config.js#alwaysAddBlocks`), его scss-файл будет взят в сборку стилей.
Используемый постпроцессинг:
1. [autoprefixer](https://github.com/postcss/autoprefixer)
2. [css-mqpacker](https://github.com/hail2u/node-css-mqpacker)
3. [postcss-import](https://github.com/postcss/postcss-import)
4. [postcss-inline-svg](https://github.com/TrySound/postcss-inline-svg)
### Стилевой код-гайд
Автопроверка с [stylelint](https://stylelint.io/) и плагинами. См. `.stylelintrc`.
1. БЭМ-именование: `__` — разделитель элемента, `--` — разделитель модификатора.
2. Один Блок = один стилевой файл.
3. Очередность селекторов:
- Инклуды примесей
- Стилевые правила сущности
- Медиаусловия
- Псевдоселекторы и псевдоэлементы
- Сторонние вложенные селекторы
- Элементы блока
- Модификаторы блока
## Скрипты
Точка входа (`src/js/entry.js`) формируется автоматически при старте сервера разработки. Точка входа обрабатывается webpack-ом (с babel-loader).
Для глобальных действий предусмотрен `src/js/script.js` (см. `config.js#addJsAfter` и `config.js#addJsBefore`).
Каждый блок (в `src/blocks/`) может (не обязан) содержать одноимённый js-файл. Если блок используется в разметке (или упомянут в `config.js#alwaysAddBlocks`), его js-файл будет взят в сборку стилей.
### Инлайн-скрипт
По умолчанию в шаблоне (`src/pug/layout.pug`) прописана вставка в `<head>` скрипта `src/js/head-script.js`, в котором для узла `<html>` указано:
- Убрать класс `no-js` и добавить класс `js`.
- Добавить класс с указанием названия браузера и ОС.
- Добавить класс `touch` или `no-touch`, в зависимости от типа интерфейса.
- Добавить кастомное свойство `--vh` со значением в 1% высоты вьюпорта (значение пересчитывается при изменении размеров).
## Нравится проект?
Ставьте звезду репозиторию и/или [угостите меня кофе](https://money.yandex.ru/to/41001252765094), переведя сколь угодно символическую сумму.
**Не редактируйте файлы в этой папке**, все такие изменения будут затёрты при следующей сборке проекта.
Для любых изменений создавайте новые файлы (стили, скрипты etc.) или обратитесь к автору.
Проект собирается автоматикой из исходников, хранящихся в репозитории. Адрес можно узнать у автора.
/* global module */
let config = {
'notGetBlocks': [
'blocks-demo.html',
],
'ignoredBlocks': [
'no-js',
],
'alwaysAddBlocks': [
// 'sprite-svg',
// 'sprite-png',
],
'addStyleBefore': [
'src/scss/variables.scss',
'src/scss/mixins.scss',
// 'somePackage/dist/somePackage.css', // для 'node_modules/somePackage/dist/somePackage.css',
],
'addStyleAfter': [
// 'src/scss/print.scss',
],
'addJsBefore': [
// 'somePackage/dist/somePackage.js', // для 'node_modules/somePackage/dist/somePackage.js',
],
'addJsAfter': [
'./script.js',
],
'addAssets': {
'src/img/demo-*.{png,svg,jpg,jpeg}': 'img/',
// 'src/fonts/demo-empty-open-sans.woff2': 'fonts/',
// 'src/favicon/*.{png,ico,svg,xml,webmanifest}': 'img/favicon',
// 'node_modules/somePackage/images/*.{png,svg,jpg,jpeg}': 'img/',
},
'dir': {
'src': 'src/',
'build': 'build/',
'blocks': 'src/blocks/'
}
};
module.exports = config;
/* eslint-disable */
'use strict';
// Генератор файлов блока
// Использование: node createBlock.js [имя блока] [доп. расширения через пробел]
const fs = require('fs');
const projectConfig = require('./config.js');
const dir = projectConfig.dir;
const mkdirp = require('mkdirp');
const blockName = process.argv[2];
const defaultExtensions = ['scss', 'img']; // расширения по умолчанию
const extensions = uniqueArray(defaultExtensions.concat(process.argv.slice(3)));
// Если есть имя блока
if (blockName) {
const dirPath = `${dir.blocks}${blockName}/`; // полный путь к создаваемой папке блока
const made = mkdirp.sync(dirPath);
console.log(`[NTH] Создание папки: ${made}`);
// Обходим массив расширений и создаем файлы, если они еще не созданы
extensions.forEach((extension) => {
const filePath = `${dirPath + blockName}.${extension}`; // полный путь к создаваемому файлу
let fileContent = ''; // будущий контент файла
let fileCreateMsg = ''; // будущее сообщение в консоли при создании файла
if (extension === 'scss') {
fileContent = `// В этом файле должны быть стили для БЭМ-блока ${blockName}, его элементов,\n// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...\n// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority\n\n.${blockName} {\n\n $block-name: &; // #{$block-name}__element\n}\n`;
// fileCreateMsg = '';
}
else if (extension === 'js') {
fileContent = `/* global document */\n\n// import ready from 'Utils/documentReady.js';\n\n// ready(function() {\n// \n// });\n`;
}
else if (extension === 'md') {
fileContent = '';
}
else if (extension === 'pug') {
fileContent = `//- Все примеси в этом файле должны начинаться c имени блока (${blockName})\n\nmixin ${blockName}(text, mods)\n\n //- Принимает:\n //- text {string} - текст\n //- mods {string} - список модификаторов\n //- Вызов:\n +${blockName}('Текст', 'some-mod')\n\n -\n // список модификаторов\n var allMods = '';\n if(typeof(mods) !== 'undefined' && mods) {\n var modsList = mods.split(',');\n for (var i = 0; i < modsList.length; i++) {\n allMods = allMods + ' ${blockName}--' + modsList[i].trim();\n }\n }\n\n .${blockName}(class=allMods)&attributes(attributes)\n .${blockName}__inner\n block\n`;
}
else if (extension === 'img') {
const imgFolder = `${dirPath}img/`;
if (fileExist(imgFolder) === false) {
const made = mkdirp.sync(imgFolder);
console.log(`[NTH] Создание папки: ${made}`);
} else {
console.log(`[NTH] Папка ${imgFolder} НЕ создана (уже существует) `);
}
}
if (fileExist(filePath) === false && extension !== 'img' && extension !== 'md') {
fs.writeFile(filePath, fileContent, (err) => {
if (err) {
return console.log(`[NTH] Файл НЕ создан: ${err}`);
}
console.log(`[NTH] Файл создан: ${filePath}`);
if (fileCreateMsg) {
console.warn(fileCreateMsg);
}
});
}
else if (extension !== 'img' && extension !== 'md') {
console.log(`[NTH] Файл НЕ создан: ${filePath} (уже существует)`);
}
else if (extension === 'md') {
fs.writeFile(`${dirPath}readme.md`, fileContent, (err) => {
if (err) {
return console.log(`[NTH] Файл НЕ создан: ${err}`);
}
console.log(`[NTH] Файл создан: ${dirPath}readme.md`);
if (fileCreateMsg) {
console.warn(fileCreateMsg);
}
});
}
});
} else {
console.log('[NTH] Отмена операции: не указан блок');
}
function uniqueArray(arr) {
const objectTemp = {};
for (let i = 0; i < arr.length; i++) {
const str = arr[i];
objectTemp[str] = true;
}
return Object.keys(objectTemp);
}
function fileExist(path) {
const fs = require('fs');
try {
fs.statSync(path);
} catch (err) {
return !(err && err.code === 'ENOENT');
}
}
/* global exports process console __dirname Buffer */
/* eslint-disable no-console */
'use strict';
// Пакеты, использующиеся при обработке
const { series, parallel, src, dest, watch, lastRun } = require('gulp');
const fs = require('fs');
const plumber = require('gulp-plumber');
const del = require('del');
const pug = require('gulp-pug');
const through2 = require('through2');
const rename = require('gulp-rename');
const getClassesFromHtml = require('get-classes-from-html');
const browserSync = require('browser-sync').create();
const debug = require('gulp-debug');
const sass = require('gulp-sass');
const webpackStream = require('webpack-stream');
const buffer = require('vinyl-buffer');
const postcss = require('gulp-postcss');
const autoprefixer = require("autoprefixer");
const mqpacker = require("css-mqpacker");
const atImport = require("postcss-import");
const csso = require('gulp-csso');
const inlineSVG = require('postcss-inline-svg');
const cpy = require('cpy');
const svgstore = require('gulp-svgstore');
const svgmin = require('gulp-svgmin');
const spritesmith = require('gulp.spritesmith');
const merge = require('merge-stream');
const imagemin = require('gulp-imagemin');
const prettyHtml = require('gulp-pretty-html');
const replace = require('gulp-replace');
const ghpages = require('gh-pages');
const path = require('path');
// Глобальные настройки этого запуска
const buildLibrary = process.env.BUILD_LIBRARY || false;
const mode = process.env.MODE || 'development';
const nth = {};
nth.config = require('./config.js');
nth.blocksFromHtml = Object.create(nth.config.alwaysAddBlocks); // блоки из конфига сразу добавим в список блоков
nth.scssImportsList = []; // список импортов стилей
const dir = nth.config.dir;
// Сообщение для компилируемых файлов
let doNotEditMsg = '\n ВНИМАНИЕ! Этот файл генерируется автоматически.\n Любые изменения этого файла будут потеряны при следующей компиляции.\n Любое изменение проекта без возможности компиляции ДОЛЬШЕ И ДОРОЖЕ в 2-5 раз.\n\n';
// Настройки pug-компилятора
let pugOption = {
data: { repoUrl: 'https://github.com/nicothin/NTH-start-project', },
filters: { 'show-code': filterShowCode, },
};
// Настройки бьютификатора
let prettyOption = {
indent_size: 2,
indent_char: ' ',
unformatted: ['code', 'em', 'strong', 'span', 'i', 'b', 'br', 'script'],
content_unformatted: [],
};
// Список и настройки плагинов postCSS
let postCssPlugins = [
autoprefixer({grid: true}),
mqpacker({
sort: true
}),
atImport(),
inlineSVG(),
];
function writePugMixinsFile(cb) {
let allBlocksWithPugFiles = getDirectories('pug');
let pugMixins = '//-' + doNotEditMsg.replace(/\n /gm,'\n ');
allBlocksWithPugFiles.forEach(function(blockName) {
pugMixins += `include ${dir.blocks.replace(dir.src,'../')}${blockName}/${blockName}.pug\n`;
});
fs.writeFileSync(`${dir.src}pug/mixins.pug`, pugMixins);
cb();
}
exports.writePugMixinsFile = writePugMixinsFile;
function compilePug() {
const fileList = [
`${dir.src}pages/**/*.pug`
];
if(!buildLibrary) fileList.push(`!${dir.src}pages/blocks-demo.pug`);
return src(fileList)
.pipe(plumber({
errorHandler: function (err) {
console.log(err.message);
this.emit('end');
}
}))
.pipe(debug({title: 'Compiles '}))
.pipe(pug(pugOption))
.pipe(prettyHtml(prettyOption))
.pipe(replace(/^(\s*)(<button.+?>)(.*)(<\/button>)/gm, '$1$2\n$1 $3\n$1$4'))
.pipe(replace(/^( *)(<.+?>)(<script>)([\s\S]*)(<\/script>)/gm, '$1$2\n$1$3\n$4\n$1$5\n'))
.pipe(through2.obj(getClassesToBlocksList))
.pipe(dest(dir.build));
}
exports.compilePug = compilePug;
function compilePugFast() {
const fileList = [
`${dir.src}pages/**/*.pug`
];
if(!buildLibrary) fileList.push(`!${dir.src}pages/blocks-demo.pug`);
return src(fileList, { since: lastRun(compilePugFast) })
.pipe(plumber({
errorHandler: function (err) {
console.log(err.message);
this.emit('end');
}
}))
.pipe(debug({title: 'Compiles '}))
.pipe(pug(pugOption))
.pipe(prettyHtml(prettyOption))
.pipe(replace(/^(\s*)(<button.+?>)(.*)(<\/button>)/gm, '$1$2\n$1 $3\n$1$4'))
.pipe(replace(/^( *)(<.+?>)(<script>)([\s\S]*)(<\/script>)/gm, '$1$2\n$1$3\n$4\n$1$5\n'))
.pipe(through2.obj(getClassesToBlocksList))
.pipe(dest(dir.build));
}
exports.compilePugFast = compilePugFast;
function copyAssets(cb) {
for (let item in nth.config.addAssets) {
let dest = `${dir.build}${nth.config.addAssets[item]}`;
cpy(item, dest);
}
cb();
}
exports.copyAssets = copyAssets;
function copyImg(cb) {
let copiedImages = [];
nth.blocksFromHtml.forEach(function(block) {
let src = `${dir.blocks}${block}/img`;
if(fileExist(src)) copiedImages.push(`${src}/*.{png,jpg,jpeg,svg}`);
});
nth.config.alwaysAddBlocks.forEach(function(block) {
let src = `${dir.blocks}${block}/img`;
if(fileExist(src)) copiedImages.push(`${src}/*.{png,jpg,jpeg,svg}`);
});
if(copiedImages.length) {
(async () => {
await cpy(copiedImages, `${dir.build}img`);
cb();
})();
}
else {
cb();
}
}
exports.copyImg = copyImg;
function generateSvgSprite(cb) {
let spriteSvgPath = `${dir.blocks}sprite-svg/svg/`;
if(nth.config.alwaysAddBlocks.indexOf('sprite-svg') > -1 && fileExist(spriteSvgPath)) {
return src(spriteSvgPath + '*.svg')
.pipe(svgmin(function () {
return { plugins: [{ cleanupIDs: { minify: true } }] }
}))
.pipe(svgstore({ inlineSvg: true }))
.pipe(rename('sprite.svg'))
.pipe(dest(`${dir.blocks}sprite-svg/img/`));
}
else {
cb();
}
}
exports.generateSvgSprite = generateSvgSprite;
function generatePngSprite(cb) {
let spritePngPath = `${dir.blocks}sprite-png/png/`;
if(nth.config.alwaysAddBlocks.indexOf('sprite-png') > -1 && fileExist(spritePngPath)) {
del(`${dir.blocks}sprite-png/img/*.png`);
let fileName = 'sprite-' + Math.random().toString().replace(/[^0-9]/g, '') + '.png';
let spriteData = src(spritePngPath + '*.png')
.pipe(spritesmith({
imgName: fileName,
cssName: 'sprite-png.scss',
padding: 4,
imgPath: '../img/' + fileName
}));
let imgStream = spriteData.img
.pipe(buffer())
.pipe(imagemin([ imagemin.optipng({ optimizationLevel: 5 }) ]))
.pipe(dest(`${dir.blocks}sprite-png/img/`));
let cssStream = spriteData.css
.pipe(dest(`${dir.blocks}sprite-png/`));
return merge(imgStream, cssStream);
}
else {
cb();
}
}
exports.generatePngSprite = generatePngSprite;
function writeSassImportsFile(cb) {
const newScssImportsList = [];
nth.config.addStyleBefore.forEach(function(src) {
newScssImportsList.push(src);
});
nth.config.alwaysAddBlocks.forEach(function(blockName) {
if (fileExist(`${dir.blocks}${blockName}/${blockName}.scss`)) newScssImportsList.push(`${dir.blocks}${blockName}/${blockName}.scss`);
});
let allBlocksWithScssFiles = getDirectories('scss');
allBlocksWithScssFiles.forEach(function(blockWithScssFile){
let url = `${dir.blocks}${blockWithScssFile}/${blockWithScssFile}.scss`;
if (nth.blocksFromHtml.indexOf(blockWithScssFile) == -1) return;
if (newScssImportsList.indexOf(url) > -1) return;
newScssImportsList.push(url);
});
nth.config.addStyleAfter.forEach(function(src) {
newScssImportsList.push(src);
});
let diff = getArraysDiff(newScssImportsList, nth.scssImportsList);
if (diff.length) {
let msg = `\n/*!*${doNotEditMsg.replace(/\n /gm,'\n * ').replace(/\n\n$/,'\n */\n\n')}`;
let styleImports = msg;
newScssImportsList.forEach(function(src) {
styleImports += `@import "${src}";\n`;
});
styleImports += msg;
fs.writeFileSync(`${dir.src}scss/style.scss`, styleImports);
console.log('---------- Write new style.scss');
nth.scssImportsList = newScssImportsList;
}
cb();
}
exports.writeSassImportsFile = writeSassImportsFile;
function compileSass() {
const fileList = [
`${dir.src}scss/style.scss`,
];
if(buildLibrary) fileList.push(`${dir.blocks}blocks-library/blocks-library.scss`);
return src(fileList, { sourcemaps: true })
.pipe(plumber({
errorHandler: function (err) {
console.log(err.message);
this.emit('end');
}
}))
.pipe(debug({title: 'Compiles:'}))
.pipe(sass({includePaths: [__dirname+'/','node_modules']}))
.pipe(postcss(postCssPlugins))
.pipe(csso({
restructure: false,
}))
.pipe(dest(`${dir.build}/css`, { sourcemaps: mode === 'development' ? '.' : false }))
.pipe(browserSync.stream());
}
exports.compileSass = compileSass;
function writeJsRequiresFile(cb) {
const jsRequiresList = [];
nth.config.addJsBefore.forEach(function(src) {
jsRequiresList.push(src);
});
const allBlocksWithJsFiles = getDirectories('js');
allBlocksWithJsFiles.forEach(function(blockName){
if (nth.config.alwaysAddBlocks.indexOf(blockName) == -1) return;
jsRequiresList.push(`../blocks/${blockName}/${blockName}.js`)
});
allBlocksWithJsFiles.forEach(function(blockName){
let src = `../blocks/${blockName}/${blockName}.js`
if (nth.blocksFromHtml.indexOf(blockName) == -1) return;
if (jsRequiresList.indexOf(src) > -1) return;
jsRequiresList.push(src);
});
nth.config.addJsAfter.forEach(function(src) {
jsRequiresList.push(src);
});
let msg = `\n/*!*${doNotEditMsg.replace(/\n /gm,'\n * ').replace(/\n\n$/,'\n */\n\n')}`;
let jsRequires = msg + '/* global require */\n\n';
jsRequiresList.forEach(function(src) {
jsRequires += `require('${src}');\n`;
});
jsRequires += msg;
fs.writeFileSync(`${dir.src}js/entry.js`, jsRequires);
console.log('---------- Write new entry.js');
cb();
}
exports.writeJsRequiresFile = writeJsRequiresFile;
function buildJs() {
const entryList = {
'bundle': `./${dir.src}js/entry.js`,
};
if(buildLibrary) entryList['blocks-library'] = `./${dir.blocks}blocks-library/blocks-library.js`;
return src(`${dir.src}js/entry.js`)
.pipe(plumber())
.pipe(webpackStream({
mode: mode,
entry: entryList,
devtool: mode === 'development' ? 'inline-source-map' : false,
output: {
filename: '[name].js',
},
resolve: {
alias: {
Utils: path.resolve(__dirname, 'src/js/utils/'),
},
},
module: {
rules: [
{
test: /\.(js)$/,
exclude: /(node_modules)/,
loader: 'babel-loader',
query: {
presets: ['@babel/preset-env']
}
}
]
},
// externals: {
// jquery: 'jQuery'
// }
}))
.pipe(dest(`${dir.build}js`));
}
exports.buildJs = buildJs;
function clearBuildDir() {
return del([
`${dir.build}**/*`,
`!${dir.build}readme.md`,
]);
}
exports.clearBuildDir = clearBuildDir;
function reload(done) {
browserSync.reload();
done();
}
function deploy(cb) {
ghpages.publish(path.join(process.cwd(), dir.build), cb);
}
exports.deploy = deploy;
function serve() {
browserSync.init({
server: dir.build,
port: 8080,
startPath: 'index.html',
open: false,
notify: false,
});
// Страницы: изменение, добавление
watch([`${dir.src}pages/**/*.pug`], { events: ['change', 'add'], delay: 100 }, series(
compilePugFast,
parallel(writeSassImportsFile, writeJsRequiresFile),
parallel(compileSass, buildJs),
reload
));
// Страницы: удаление
watch([`${dir.src}pages/**/*.pug`], { delay: 100 })
// TODO попробовать с events: ['unlink']
.on('unlink', function(path) {
let filePathInBuildDir = path.replace(`${dir.src}pages/`, dir.build).replace('.pug', '.html');
fs.unlink(filePathInBuildDir, (err) => {
if (err) throw err;
console.log(`---------- Delete: ${filePathInBuildDir}`);
});
});
// Разметка Блоков: изменение
watch([`${dir.blocks}**/*.pug`], { events: ['change'], delay: 100 }, series(
compilePug,
reload
));
// Разметка Блоков: добавление
watch([`${dir.blocks}**/*.pug`], { events: ['add'], delay: 100 }, series(
writePugMixinsFile,
compilePug,
reload
));
// Разметка Блоков: удаление
watch([`${dir.blocks}**/*.pug`], { events: ['unlink'], delay: 100 }, writePugMixinsFile);
// Шаблоны pug: все события
watch([`${dir.src}pug/**/*.pug`, `!${dir.src}pug/mixins.pug`], { delay: 100 }, series(
compilePug,
parallel(writeSassImportsFile, writeJsRequiresFile),
parallel(compileSass, buildJs),
reload,
));
// Стили Блоков: изменение
watch([`${dir.blocks}**/*.scss`], { events: ['change'], delay: 100 }, series(
compileSass,
));
// Стили Блоков: добавление
watch([`${dir.blocks}**/*.scss`], { events: ['add'], delay: 100 }, series(
writeSassImportsFile,
compileSass,
));
// Стилевые глобальные файлы: все события
watch([`${dir.src}scss/**/*.scss`, `!${dir.src}scss/style.scss`], { events: ['all'], delay: 100 }, series(
compileSass,
));
// Скриптовые глобальные файлы: все события
watch([`${dir.src}js/**/*.js`, `!${dir.src}js/entry.js`, `${dir.blocks}**/*.js`], { events: ['all'], delay: 100 }, series(
writeJsRequiresFile,
buildJs,
reload
));
// Картинки: все события
watch([`${dir.blocks}**/img/*.{jpg,jpeg,png,gif,svg,webp}`], { events: ['all'], delay: 100 }, series(copyImg, reload));
// Спрайт SVG
watch([`${dir.blocks}sprite-svg/svg/*.svg`], { events: ['all'], delay: 100 }, series(
generateSvgSprite,
copyImg,
reload,
));
// Спрайт PNG
watch([`${dir.blocks}sprite-png/png/*.png`], { events: ['all'], delay: 100 }, series(
generatePngSprite,
copyImg,
compileSass,
reload,
));
}
exports.build = series(
parallel(clearBuildDir, writePugMixinsFile),
parallel(compilePugFast, copyAssets, generateSvgSprite, generatePngSprite),
parallel(copyImg, writeSassImportsFile, writeJsRequiresFile),
parallel(compileSass, buildJs),
);
exports.default = series(
parallel(clearBuildDir, writePugMixinsFile),
parallel(compilePugFast, copyAssets, generateSvgSprite, generatePngSprite),
parallel(copyImg, writeSassImportsFile, writeJsRequiresFile),
parallel(compileSass, buildJs),
serve,
);
// Функции, не являющиеся задачами Gulp ----------------------------------------
/**
* Получение списка классов из HTML и запись его в глоб. переменную nth.blocksFromHtml.
* @param {object} file Обрабатываемый файл
* @param {string} enc Кодировка
* @param {Function} cb Коллбэк
*/
function getClassesToBlocksList(file, enc, cb) {
// Передана херь — выходим
if (file.isNull()) {
cb(null, file);
return;
}
// Проверяем, не является ли обрабатываемый файл исключением
let processThisFile = true;
nth.config.notGetBlocks.forEach(function(item) {
if (file.relative.trim() == item.trim()) processThisFile = false;
});
// Файл не исключён из обработки, погнали
if (processThisFile) {
const fileContent = file.contents.toString();
let classesInFile = getClassesFromHtml(fileContent);
// nth.blocksFromHtml = [];
// Обойдём найденные классы
for (let item of classesInFile) {
// Не Блок или этот Блок уже присутствует?
if ((item.indexOf('__') > -1) || (item.indexOf('--') > -1) || (nth.blocksFromHtml.indexOf(item) + 1)) continue;
// Класс совпадает с классом-исключением из настроек?
if (nth.config.ignoredBlocks.indexOf(item) + 1) continue;
// У этого блока отсутствует папка?
// if (!fileExist(dir.blocks + item)) continue;
// Добавляем класс в список
nth.blocksFromHtml.push(item);
}
console.log('---------- Used HTML blocks: ' + nth.blocksFromHtml.join(', '));
file.contents = new Buffer.from(fileContent);
}
this.push(file);
cb();
}
//
/**
* Pug-фильтр, выводящий содержимое pug-файла в виде форматированного текста
*/
function filterShowCode(text, options) {
var lines = text.split('\n');
var result = '<pre class="code">\n';
if (typeof(options['first-line']) !== 'undefined') result = result + '<code>' + options['first-line'] + '</code>\n';
for (var i = 0; i < (lines.length - 1); i++) { // (lines.length - 1) для срезания последней строки (пустая)
result = result + '<code>' + lines[i].replace(/</gm, '&lt;') + '</code>\n';
}
result = result + '</pre>\n';
result = result.replace(/<code><\/code>/g, '<code>&nbsp;</code>');
return result;
}
/**
* Проверка существования файла или папки
* @param {string} path Путь до файла или папки
* @return {boolean}
*/
function fileExist(filepath){
let flag = true;
try{
fs.accessSync(filepath, fs.F_OK);
}catch(e){
flag = false;
}
return flag;
}
/**
* Получение всех названий поддиректорий, содержащих файл указанного расширения, совпадающий по имени с поддиректорией
* @param {string} ext Расширение файлов, которое проверяется
* @return {array} Массив из имён блоков
*/
function getDirectories(ext) {
let source = dir.blocks;
let res = fs.readdirSync(source)
.filter(item => fs.lstatSync(source + item).isDirectory())
.filter(item => fileExist(source + item + '/' + item + '.' + ext));
return res;
}
/**
* Получение разницы между двумя массивами.
* @param {array} a1 Первый массив
* @param {array} a2 Второй массив
* @return {array} Элементы, которые отличаются
*/
function getArraysDiff(a1, a2) {
return a1.filter(i=>!a2.includes(i)).concat(a2.filter(i=>!a1.includes(i)))
}
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "NTH-start-project",
"version": "4.1.0",
"description": "Startkit for HTML/CSS/JS pages layout.",
"author": "Nikolay Gromov",
"license": "WTFPL",
"repository": {
"type": "git",
"url": "https://github.com/nicothin/NTH-start-project.git"
},
"scripts": {
"test": "npm run test:pug && npm run test:style && npm run test:js",
"test:pug": "glob-exec \"src/**/*.pug\" -- \"pug-lint {{files.join(' ')}}\"",
"test:style": "stylelint \"src/**/*.scss\" --syntax=scss",
"test:js": "eslint src/blocks/**/*.js src/js/**/*.js",
"start": "gulp",
"wlib": "cross-env BUILD_LIBRARY=true gulp",
"build": "cross-env MODE=production gulp build",
"lint-staged": "lint-staged"
},
"browserslist": [
"> 1%",
"last 2 versions"
],
"lint-staged": {
"*.{js,pug,md}": "editorconfig-cli",
"*.pug": "pug-lint",
"*.scss": "stylelint --syntax=scss",
"*.js": "eslint"
},
"devDependencies": {
"@babel/core": "^7.6.2",
"@babel/preset-env": "^7.6.2",
"@htmlacademy/editorconfig-cli": "^1.0.0",
"autoprefixer": "^10.2.4",
"babel-eslint": "^10.0.3",
"babel-loader": "^8.0.6",
"browser-sync": "^2.26.7",
"cpy": "^8.1.0",
"cross-env": "^7.0.2",
"css-mqpacker": "^7.0.0",
"del": "^6.0.0",
"eslint": "^7.19.0",
"get-classes-from-html": "^1.0.1",
"gh-pages": "^3.1.0",
"glob-exec": "^0.1.1",
"gulp": "^4.0.2",
"gulp-concat": "^2.6.1",
"gulp-csso": "^4.0.1",
"gulp-debug": "^4.0.0",
"gulp-imagemin": "^7.1.0",
"gulp-plumber": "^1.2.1",
"gulp-postcss": "^9.0.0",
"gulp-pretty-html": "^2.0.10",
"gulp-pug": "^4.0.1",
"gulp-rename": "^2.0.0",
"gulp-replace": "^1.0.0",
"gulp-sass": "^4.0.1",
"gulp-sourcemaps": "^3.0.0",
"gulp-svgmin": "^3.0.0",
"gulp-svgstore": "^7.0.1",
"gulp.spritesmith": "^6.11.0",
"husky": "^4.2.3",
"jstransformer-markdown-it": "^2.0.0",
"lint-staged": "^10.0.9",
"merge-stream": "^2.0.0",
"mkdirp": "^1.0.3",
"path": "^0.12.7",
"postcss": "^8.2.5",
"postcss-import": "^14.0.0",
"postcss-inline-svg": "^5.0.0",
"pug-lint": "^2.6.0",
"stylelint": "^13.2.1",
"stylelint-order": "^4.0.0",
"stylelint-selector-bem-pattern": "^2.1.0",
"through2": "^4.0.2",
"vinyl-buffer": "^1.0.1",
"webpack": "^5.21.1",
"webpack-stream": "^6.1.2"
},
"dependencies": {
"autosize": "^4.0.2",
"baron": "^3.0.3",
"choices.js": "^9.0.1",
"closest": "0.0.1",
"svg4everybody": "^2.1.8"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}
}
//- Все примеси в этом файле должны начинаться c имени блока (alert)
mixin alert(title, mods)
//- Принимает:
//- title {string} - заголовок
//- mods {string} - стилевые модификаторы
//- Вызов:
+alert()
p Текст
+alert('Внимание', 'warning, some-mod-name')
p Предупреждение
+close('Закрыть')(class='alert__close')
+alert('Внимание', 'danger')
p Проблема
+close('Закрыть')(class='alert__close')
+alert('Внимание', 'success')
p Успех
+close('Закрыть')(class='alert__close')
-
//- список модификаторов
var allMods = '';
if(typeof(mods) === 'string') {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' alert--' + modsList[i].trim();
}
}
.alert(class=allMods, role='alert')&attributes(attributes)
if (typeof(title) !== 'undefined' && title !== '')
h4.alert__header!= title
block
// В этом файле должны быть стили для БЭМ-блока alert, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.alert {
position: relative;
margin-top: $typo-margin-vertical;
margin-bottom: $typo-margin-vertical;
padding: 0.7em 1.5em 0.7em 1.4em;
border-radius: $border-radius;
color: $text-color;
background-color: lighten($color-warning, 30%);
& > * { // stylelint-disable-line selector-combinator-blacklist
margin-top: 0;
margin-bottom: 0;
}
& > * + * { // stylelint-disable-line selector-combinator-blacklist
margin-top: $typo-margin-vertical;
}
&__header {
display: block;
margin-top: 0;
margin-bottom: 0.5rem;
font-size: $font-size-h3;
font-weight: 400;
line-height: 1.2;
color: inherit;
& + * {
margin-top: 0;
}
}
&__close.close {
position: absolute;
top: 0;
right: 0;
margin: 0;
}
// &--warning {
// background-color: lighten($color-warning, 30%);
// color: $text-color;
// }
// &--danger {
// background-color: $color-danger;
// color: #fff;
// a {
// color: inherit;
// text-decoration: underline;
// &:hover,
// &:focus {
// text-decoration: none;
// }
// }
// }
// &--success {
// background-color: $color-success;
// color: #fff;
// a {
// color: inherit;
// text-decoration: underline;
// &:hover,
// &:focus {
// text-decoration: none;
// }
// }
// }
}
import ready from 'Utils/documentReady.js';
import baron from 'baron';
ready(function(){
baron({
root: '#baron-demo',
scroller: '.baron__scroller',
bar: '.baron__bar',
scrollingCls: 'baron--scrolling',
draggingCls: 'baron--dragging',
barOnCls: 'baron--scrollbar',
});
});
//- Блок для кастомного скролла baron: https://github.com/Diokuz/baron
//- Нуждается в соотв. зависимости: https://www.npmjs.com/package/baron
//- (должна быть указана в ./projectConfig.json, есть по умолчанию)
//- ВНИМАНИЕ! Помимо вызова примеси, нужно включить baron на блоке! см. blocks/baron/baron.js
mixin baron(id, mods, tag)
//- Принимает:
//- id {string} - ID этого конкретного блока с кастомным скроллом
//- mods {string} - список модификаторов
//- tag {string} - тег
//- Вызов:
+baron('some-mod')(style='width:100px; height: 100px')
p Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt dicta aliquid nemo, sit possimus, eveniet tempore minus, doloremque incidunt, nihil quos aperiam ab iure quia. Ipsa sit porro incidunt, sed!
.some-block
+baron('', 'section')(class='some-block__demo')
p Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt dicta aliquid nemo, sit possimus, eveniet tempore minus, doloremque incidunt, nihil quos aperiam ab iure quia. Ipsa sit porro incidunt, sed!
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' baron--' + modsList[i].trim();
}
}
var parentTag = 'div';
if(typeof(tag) !== 'undefined' && tag) {
parentTag = tag;
}
#{parentTag}.baron(id=id, class=allMods)&attributes(attributes)
.baron__scroller
block
.baron__track
//- .baron__control.baron__up ▲
.baron__free
.baron__bar
//- .baron__control.baron__down ▼
// В этом файле должны быть стили для БЭМ-блока baron, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Оригинальные темы оформления: https://github.com/Diokuz/baron/blob/master/skins/styles.css
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.baron {
$block-name: &; // #{$block-name}__element
$scrollbar-width: 14px;
position: relative;
overflow: hidden;
&__scroller {
box-sizing: border-box;
width: 100%;
height: 100%;
overflow-y: scroll;
margin: 0;
border: 0;
padding: 0;
padding-right: $scrollbar-width;
-webkit-overflow-scrolling: touch; // remove line to customize scrollbar in iOs
&::-webkit-scrollbar {
width: 0;
height: 0;
}
}
&__track {
position: absolute;
z-index: 3;
top: 0;
right: 0;
bottom: 0;
width: $scrollbar-width;
opacity: 0;
background-color: $gray-lightest;
#{$block-name}--scrollbar & {
opacity: 1;
}
}
&__free {
position: absolute;
top: 0;
right: 0;
bottom: 0;
}
&__bar {
box-sizing: border-box;
display: none;
position: absolute;
z-index: 1;
left: ($scrollbar-width * -1 + 1);
width: ($scrollbar-width - 2);
opacity: 1;
pointer-events: auto;
background: $gray-lighten;
#{$block-name}--scrollbar & {
display: block;
}
#{$block-name}--dragging &,
&:hover {
background-color: $gray-light;
}
}
&__control {
display: none;
}
}
Кастомный скролл c [baron](https://www.npmjs.com/package/baron). [Демо кастомного скролла](http://diokuz.github.io/baron/).
Для каждого блока нужно включать baron в `blocks/baron/baron.js`.
Оставлена одна тема оформления (используется по умолчанию). Стили берутся из `blocks/baron/baron.scss`. См. [стилизацию оригинальных тем](https://github.com/Diokuz/baron/blob/master/skins/styles.css).
require('../main-nav/main-nav.js');
require('../burger/burger.js');
require('../field-text/field-text.js');
require('../field-file/field-file.js');
require('../field-num/field-num.js');
require('../field-select/field-select.js');
require('../scroll-link/scroll-link.js');
require('../to-top/to-top.js');
require('../off-canvas/off-canvas.js');
require('../modal/modal.js');
require('../tabs/tabs.js');
require('../baron/baron.js');
require('../form-validation/form-validation.js');
require('../../js/script.js');
/* stylelint-disable */
@import "../../scss/variables.scss";
@import "../../scss/mixins.scss";
@import "../alert/alert.scss";
@import "../baron/baron.scss";
@import "../breadcrumbs/breadcrumbs.scss";
@import "../btn/btn.scss";
@import "../burger/burger.scss";
@import "../close/close.scss";
@import "../code/code.scss";
@import "../comment/comment.scss";
@import "../embed-responsive/embed-responsive.scss";
@import "../field-actions/field-actions.scss";
@import "../field-checkbox/field-checkbox.scss";
@import "../field-file/field-file.scss";
@import "../field-radio/field-radio.scss";
@import "../field-range/field-range.scss";
@import "../field-select/field-select.scss";
@import "../field-text/field-text.scss";
@import "../field-num/field-num.scss";
@import "../field-toggler/field-toggler.scss";
@import "../form/form.scss";
@import "../label/label.scss";
@import "../loader/loader.scss";
@import "../logo/logo.scss";
@import "../main-nav/main-nav.scss";
@import "../modal/modal.scss";
@import "../off-canvas/off-canvas.scss";
@import "../or/or.scss";
@import "../page/page.scss";
@import "../page-footer/page-footer.scss";
@import "../page-header/page-header.scss";
@import "../pagination/pagination.scss";
@import "../progress/progress.scss";
// @import "../sprite-png/sprite-png.scss";
@import "../table/table.scss";
@import "../table-responsive/table-responsive.scss";
@import "../tabs/tabs.scss";
@import "../social/social.scss";
@import "../to-top/to-top.scss";
@import "../card/card.scss";
@import "../../scss/print.scss";
.blocks-library {
$block-name: &; // #{$block-name}__element
&__title {
font-weight: 400;
}
&__item {
position: relative;
padding: 1rem;
border: 2px dashed $gray-lighter;
margin: 2.5rem 0;
@media (min-width: $screen-md) {
padding: 1.5rem 2rem 0.5rem 3rem;
}
&:before {
content: '.' attr(data-id);
position: absolute;
top: -1.2rem;
left: 0;
font-weight: 700;
text-transform: lowercase;
font-size: 1em;
line-height: 1;
height: 1em;
text-align: right;
white-space: nowrap;
color: $gray-lighter;
@media (min-width: $screen-md) {
left: 1.4rem;
top: 20.5rem;
width: 21rem;
transform: rotate(-90deg);
transform-origin: 0 100%;
}
}
}
&__item-title {
margin: 0 0 0.5rem;
}
&__item-title-link {
display: inline-block;
margin-right: 0.8rem;
svg {
display: block;
fill: $gray;
}
}
&__code-wrapper {
position: relative;
}
&__code-show-trigger {
display: block;
width: 30px;
height: 30px;
cursor: pointer;
margin-bottom: 0.3em;
@media (min-width: $screen-md) {
position: absolute;
top: -1.65rem;
left: -2.5rem;
}
}
&__code-icon {
display: block;
width: 30px;
height: 3px;
background-color: $gray-lighter;
&:before,
&:after {
position: absolute;
background-color: $gray-lighter;
width: 30px;
height: 3px;
left: 0;
content: '';
}
&:before {
top: 10px;
}
&:after {
top: 20px;
}
}
&__code {
overflow: hidden;
.js & {
max-height: 0;
transition-duration: $transition-time;
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
}
.js &--shown {
transition-duration: $transition-time;
transition-timing-function: ease-in;
max-height: 1000px;
}
pre {
max-height: 800px;
}
}
&__menu-toggler {
position: fixed !important;
backface-visibility: hidden;
bottom: 0;
left: 0;
background-color: #000 !important;
width: 40px !important;
height: 40px;
padding: 5px !important;
.off-canvas--open & {
display: none;
}
& > span,
& > span:before,
& > span:after {
background-color: #fff !important;
}
}
}
/* stylelint-enable */
Оформление одного блока при описании в библиотеке блоков.
//- Все примеси в этом файле должны начинаться c имени блока (breadcrumbs)
mixin breadcrumbs(mods)
//- Принимает:
//- mods {string} - список модификаторов
//- Вызов:
+breadcrumbs()
+breadcrumbs-item('Главная', '/home')
+breadcrumbs-item('Услуги', '/services')
+breadcrumbs-item('Стриптиз')
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' breadcrumbs--' + modsList[i].trim();
}
}
ul.breadcrumbs(class=allMods, aria-label='Breadcrumb', role='navigation')&attributes(attributes)
block
mixin breadcrumbs-item(text, href, mods)
//- Принимает:
//- text {string} - содержимое пункта
//- href {string} - ссылка этого пункта (если пустая, то это не ссылка, а span)
//- mods {string} - список модификаторов
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' breadcrumbs__item--' + modsList[i].trim();
}
}
li.breadcrumbs__item(class=allMods)&attributes(attributes)
if(typeof(href) !== 'undefined' && href)
a(href=href)!= text
else
span!= text
// В этом файле должны быть стили для БЭМ-блока breadcrumbs, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.breadcrumbs {
list-style: none;
margin-top: $typo-margin-vertical;
margin-bottom: $typo-margin-vertical;
padding: 0;
color: $text-color;
a {
color: $text-color;
text-decoration: none;
&:hover,
&:focus {
color: $text-color;
text-decoration: none;
}
}
&__item {
display: inline-block;
margin-right: 0.6em;
white-space: nowrap;
&:not(:last-child):after {
content: '>';
display: inline-block;
margin-left: 0.8em;
color: $gray;
}
}
}
//- Все примеси в этом файле должны начинаться c имени блока (btn)
mixin btn(text, mods, isInput)
//- Принимает:
//- text {string} - текст кнопки
//- mods {string} - список модификаторов
//- isInput {bool} - флаг «это тег input»
//- Вызов:
+btn('Кнопка-ссылка')(href='/') - есть href, это точно ссылка
+btn('Кнопка-input', '', true) - есть флаг isInput, это input
+btn('Кнопка-button', 'success') - нет href, нет isInput — это button
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' btn--' + modsList[i].trim();
}
}
//- передан href — это ссылка
if (attributes.href)
a.btn(class=allMods)&attributes(attributes)!= text
block
//- иначе, если передан isInput и он true, это input
else if (typeof(isInput) !== 'undefined' && isInput)
input.btn(class=allMods, value=text, type='button')&attributes(attributes)
//- иначе это button
else
button.btn(class=allMods)&attributes(attributes)!= text
block
// В этом файле должны быть стили для БЭМ-блока btn, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.btn {
box-sizing: border-box;
display: inline-block;
max-width: 100%;
margin: 0;
border-radius: $border-radius;
// border: 1px solid $gray-lighter;
border: 0;
padding: $field-padding-vertical $field-padding-horizontal;
vertical-align: middle;
white-space: nowrap;
text-overflow: ellipsis;
user-select: none;
text-align: center;
font-size: 1em;
font-family: inherit;
line-height: $line-height;
text-decoration: none;
text-transform: none;
color: $text-color;
background-color: $gray-lightest;
background-image: none;
cursor: pointer;
overflow: hidden;
&:hover,
&:focus {
text-decoration: none;
color: $text-color;
background-color: darken($gray-lightest, 10%);
// border-color: darken($gray-lighter, 12%);
}
// &:visited {
// color: $text-color;
// }
&:active,
&:focus {
@include field-focus;
}
&--disabled,
&:disabled {
opacity: $opacity;
cursor: not-allowed;
color: $text-color;
background-color: $gray-lightest;
// border-color: $gray-lighter;
@at-root a.btn--disabled {
pointer-events: none;
}
}
// &__icon {
// display: inline-block;
// vertical-align: middle;
// }
// &--main {
// color: #fff;
// background-color: $color-main;
// // border: 1px solid $color-main;
// &:hover,
// &:focus {
// color: #fff;
// background-color: darken($color-main, 10%);
// // border-color: darken($color-main, 12%);
// }
// &--disabled,
// &:disabled {
// color: #fff;
// background-color: $color-main;
// // border-color: $color-main;
// }
// }
}
/* global document */
import ready from 'Utils/documentReady.js';
ready(function(){
var burgers = document.querySelectorAll('.burger');
for (var i = 0; i < burgers.length; i++) {
var burger = burgers[i];
burger.addEventListener('click', showBurgerTarget);
}
function showBurgerTarget() {
var targetId = this.getAttribute('data-target-id');
var targetClassToggle = this.getAttribute('data-target-class-toggle');
if (targetId && targetClassToggle) {
this.classList.toggle('burger--close');
document.getElementById(targetId).classList.toggle(targetClassToggle);
}
}
});
//- Все примеси в этом файле должны начинаться c имени блока (burger)
mixin burger(label, targetId, switchableClass)
//- Принимает:
//- label {string} - описание, значение атрибута aria-label
//- targetId {string} - атрибут id целевого элемента (без символа #), на котором по клику будет меняться класс
//- switchableClass {string} - класс, добавляемый/убираемый с целевого элемента
//- Вызов:
+burger('Показать пример кода', 'burger-code', 'blocks-library__code--shown')
+burger('Показать ничто')(data-some="SOME")
-
label = label || 'Toggle block ' + targetId
if (typeof(targetId) !== 'undefined' && targetId !== '') attributes['data-target-id'] = targetId
if (typeof(switchableClass) !== 'undefined' && switchableClass !== '') attributes['data-target-class-toggle'] = switchableClass
button.burger(aria-label=label)&attributes(attributes)
span= label
// В этом файле должны быть стили для БЭМ-блока burger, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.burger {
position: relative;
z-index: 1;
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: none;
padding: 0;
background: transparent;
user-select: none;
cursor: pointer;
> span { // stylelint-disable-line selector-combinator-blacklist
display: block;
position: relative;
width: 90%;
height: $toggler-part-height;
font-size: 0;
color: transparent;
background: $gray-dark;
transition: background $transition-time $transition-time;
&:before,
&:after {
content: '';
position: absolute;
left: 0;
width: 100%;
height: $toggler-part-height;
background: $gray-dark;
transform-origin: 50% 50%;
transition: top $transition-time $transition-time, transform $transition-time;
}
&:before {
top: -10px;
}
&:after {
top: 10px;
}
}
&--close {
> span { // stylelint-disable-line selector-combinator-blacklist
transition: background $transition-time 0s;
background: transparent;
&:before,
&:after {
top: 0;
transition: top $transition-time, transform $transition-time $transition-time;
}
&:before {
transform: rotate3d(0, 0, 1, 45deg);
}
&:after {
transform: rotate3d(0, 0, 1, -45deg);
}
}
}
}
Анимированное превращение в крестик по добавлению модификатора. <br />
JS блока берет переданный в data-атрибуте идентификатор и, по клику на бургере, добавляет переданный в data-атрибуте класс на элемент с этим модификатором.
//- Все примеси в этом файле должны начинаться c имени блока (card)
mixin card(data, mods)
//- Принимает:
//- data {
//- url: {string}, - адрес для ссылки
//- img: {string}, - адрес картинки товара
//- name: {string}, - название
//- descr: {string}, - описание
//- price: {number}, - цена
//- new: {boolean}, - показать метку «новинка»
//- }
//- mods {string} - модификаторы
//- Вызов:
+card({
url: 'good01',
img: 'img/demo-product.jpg',
name: 'Товар1',
descr: 'Описание1',
price: 7777,
new: true,
}, 'mod')
+card({
img: 'img/demo-product.jpg',
name: 'Товар2',
price: 12000,
})
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' card--' + modsList[i].trim();
}
}
var newLabel = (data.new !== 'undefined' && data.new) ? '<div class="card__new">Новинка!</div>' : '';
var price = '' + data.price;
var price = price.replace(/(\d)(?=(\d\d\d)+([^\d]|$))/g, '$1&thinsp;') + ' ₽';
article.card(class=allMods)&attributes(attributes)
!= newLabel
if (data.url !== 'undefined' && data.url)
.card__inner
a.card__img-wrap(href=data.url)
img.card__img(src=data.img, alt=data.name)
h3.card__title
a(href=data.url)!= data.name
if (data.descr !== 'undefined' && data.descr)
p.card__descr!= data.descr
p.card__price!= price
else
.card__inner
span.card__img-wrap
img.card__img(src=data.img, alt=data.name)
h3.card__title!= data.name
if (data.descr !== 'undefined' && data.descr)
p.card__descr!= data.descr
p.card__price!= price
+btn('Купить', '')(class='card__buy')
// В этом файле должны быть стили для БЭМ-блока card, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.card {
$block-name: &; // #{$block-name}__element
display: flex;
flex-direction: column;
align-items: center;
position: relative;
z-index: 0;
min-width: 200px;
padding: 0 15px;
margin-bottom: 30px;
&__inner {
display: flex;
flex-direction: column;
flex-grow: 1;
align-items: center;
position: relative;
width: 100%;
margin-bottom: 20px;
}
&__img-wrap {
display: block;
flex-shrink: 0;
margin-bottom: 20px;
}
&__img {
display: block;
}
&__title {
margin: 0 0 20px;
}
&__new {
position: absolute;
z-index: 1;
top: 0;
left: 50%;
padding: 5px 10px;
line-height: 1;
font-weight: 700;
color: #fff;
background: $color-success;
transform: translateX(-50%) rotate(-10deg);
}
&__price {
flex-shrink: 0;
margin: auto 0 0;
font-size: $font-size-h3;
font-weight: 700;
line-height: 1;
}
&__buy {
flex-shrink: 0;
}
}
Карточка товара (миниатюра-ссылка на страницу товара).
//- Все примеси в этом файле должны начинаться c имени блока (close)
mixin close(label, mods)
//- Принимает:
//- label {string} - описание, значение атрибута aria-label
//- mods {string} - стилевые модификаторы
//- Вызов:
+close('Закрыть')
+close('Закрыть', 'sm')
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' close--' + modsList[i].trim();
}
}
button.close(class=allMods, type='button', aria-label=label, title=label)&attributes(attributes)
span= label
// В этом файле должны быть стили для БЭМ-блока close, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.close {
position: relative;
z-index: 1;
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: none;
padding: 0;
background: transparent;
user-select: none;
cursor: pointer;
span {
position: relative;
display: block;
width: 60%;
height: $toggler-part-height;
font-size: 0;
color: transparent;
background: transparent;
&:before,
&:after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: $toggler-part-height;
background: $gray-dark;
transform-origin: 50% 50%;
}
&:before {
transform: rotate3d(0, 0, 1, 45deg);
}
&:after {
transform: rotate3d(0, 0, 1, -45deg);
}
}
}
Иконка закрытия.
//- Все примеси в этом файле должны начинаться c имени блока (code)
mixin code()
//- Вызов:
+code()
code &lt;div class="some">...&lt;/div>
code &lt;div class="some">...&lt;/div>
pre.code&attributes(attributes)
block
// В этом файле должны быть стили для БЭМ-блока code, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.code {
display: block;
width: 100%;
max-height: 500px;
margin-top: $typo-margin-vertical;
margin-bottom: $typo-margin-vertical;
border-radius: $border-radius;
border: 1px solid $gray-lighter;
padding: 0.5em 1em;
overflow-x: auto;
background-color: $gray-lightest;
white-space: normal;
tab-size: 2;
code {
display: block;
min-height: 1em;
padding: 0;
white-space: pre;
background-color: transparent;
p,
div {
display: inline-block;
margin: 0;
}
}
}
Оформление блочных вставок кода.
//- Все примеси в этом файле должны начинаться c имени блока (comment)
mixin comment(props)
//- Принимает:
//- props {object}
//- username {string} - имя пользователя
//- avatarURL {string} - адрес изображения с аватаром
//- authorURL {string} - адрес изображения с аватаром
//- content {string} - html текста коммента
//- mods {string} - список модификаторов
//- Вызов:
+comment({
username: 'Джокер',
avatarURL: 'img/joker.jpg',
authorURL: '/users/joker',
content: '<p>Далеко-далеко за словесными горами в стране, гласных и согласных живут рыбные тексты.</p>',
mods: 'admin',
})
+comment({
username: 'Докер',
content: '<p>Далеко-далеко за словесными горами в стране, гласных и согласных живут рыбные тексты.</p>',
})
-
// список модификаторов
var allMods = '';
if(typeof(props.mods) !== 'undefined' && props.mods) {
var modsList = props.mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' comment--' + modsList[i].trim();
}
}
if(typeof(props.username) === 'undefined') props.username = 'anonymous';
if(typeof(props.avatarURL) === 'undefined') props.avatarURL = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60"%3E%3Ccircle r="50%25" cx="50%25" cy="50%25" fill="#ccc"/%3E%3C/svg%3E';
article.comment(class=allMods)&attributes(attributes)
.comment__inner
if(typeof(props.authorURL) !== 'undefined' && props.authorURL)
a.comment__avatar-wrap(href=props.authorURL)
img(src=props.avatarURL, alt=props.username)
else
span.comment__avatar-wrap
img(src=props.avatarURL, alt=props.username)
.comment__text
header.comment__header #[b.comment__author= props.username] #[time.comment__date(datetime='2017-04-25T05:45') 25.04.2017 в 05:45]
.comment__body!= props.content
footer.comment__footer.
#[a.comment__reply(href='') Reply] #[a.comment__reply(href='') Admin]
block
// В этом файле должны быть стили для БЭМ-блока comment, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.comment {
margin-top: $typo-margin-vertical;
margin-bottom: $typo-margin-vertical;
@media (min-width: $screen-md) {
& & {
padding-left: calc(60px + 1em);
}
& & & & { // stylelint-disable-line selector-max-compound-selectors, selector-max-class, selector-max-combinators
padding-left: 0;
}
}
&__inner {
display: flex;
}
&__avatar-wrap {
flex-shrink: 0;
margin-right: 1em;
img {
display: block;
width: 60px;
height: 60px;
object-fit: cover;
}
}
&__text {
flex-grow: 1;
*:first-child {
margin-top: 0;
}
}
&__header {}
&__body {
& > *:last-child { // stylelint-disable-line selector-combinator-blacklist
margin-bottom: 0;
}
}
&__footer {}
}
//- Все примеси в этом файле должны начинаться c имени блока (embed-responsive)
mixin embed-responsive(mods)
//- Принимает:
//- mods {string} - список модификаторов
//- Вызов:
+embed-responsive()
iframe(src='https://www.youtube.com/embed/B0Q1rKpDNE4', frameborder='0', allowfullscreen='')
+embed-responsive('4-3')
iframe(src='https://www.youtube.com/embed/7pOr3dBFAeY', frameborder='0', allowfullscreen='')
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' embed-responsive--' + modsList[i].trim();
}
}
.embed-responsive(class=allMods)&attributes(attributes)
block
// В этом файле должны быть стили только для БЭМ-блока embed-responsive, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Не пишите здесь другие селекторы.
.embed-responsive {
display: block;
position: relative;
width: 100%;
margin-top: $typo-margin-vertical;
margin-bottom: $typo-margin-vertical;
padding: 0;
overflow: hidden;
&::before {
content: '';
display: block;
padding-top: percentage(9 / 16); // по умолчанию ожидается вставка в формате 16/9
}
// embed,
// object,
iframe,
video {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
}
&--4-3 { // модификатор на случай вставки 4/3
&::before {
padding-top: percentage(3 / 4);
}
}
}
По умолчанию предназначен для встраивания роликов 16:9. C модификатором позволяет встраивать ролики 4:3.
//- Все примеси в этом файле должны начинаться c имени блока (field-actions)
mixin field-actions(text, mods)
//- Принимает:
//- text: '' {string} - текст пояснения
//- mods: '' {string} - модификаторы блока
//- Вызов:
+field-actions('* — обязательные поля', 'some-mod')
p КнопкиТут
-
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' field-actions--' + modsList[i].trim();
}
}
.field-actions(class=allMods)&attributes(attributes)
if(typeof(text) !== 'undefined' && text)
.field-actions__text!= text
block
// В этом файле должны быть стили для БЭМ-блока field-actions, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.field-actions {
display: block;
margin-top: $typo-margin-vertical;
margin-bottom: $typo-margin-vertical;
&__text {
display: block;
margin-top: $typo-margin-vertical;
margin-bottom: $typo-margin-vertical;
font-size: $font-size-sm;
color: $text-color-muted;
line-height: 1.2em;
}
}
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><path d="M7.984 15.017l9-9-1.406-1.454-7.594 7.595L4.42 8.594 3.018 10zm9-14.017q.844 0 1.43.61T19 3.014v13.97q0 .797-.586 1.406-.586.61-1.43.61H3.014q-.843 0-1.43-.61T1 16.984V3.014q0-.796.585-1.404Q2.17 1 3.015 1h13.97z" fill="#2d2d2d"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><path d="M16.984 1q.797 0 1.406.61.61.61.61 1.404v13.97q0 .797-.61 1.406-.61.61-1.406.61H3.014q-.796 0-1.405-.61Q1 17.78 1 16.984V3.014q0-.796.61-1.404Q2.22 1 3.014 1h13.97zm0 2.016H3.014v13.97h13.97V3.016z" fill="#2d2d2d"/></svg>
\ No newline at end of file
//- Все примеси в этом файле должны начинаться c имени блока (field-checkbox)
mixin field-checkbox(checkboxes, title)
//- Принимает:
//- checkboxes {array}
//- {object}
//- title: '' {string} - текст рядом с чекбоксом
//- helpText: '' {string} - пояснение под полем
//- mods: '' {string} - модификаторы обертки чекбокса
//- attrs: {object} - любые атрибуты для input
//- name: {string}
//- ...
//- Вызов:
+field-checkbox([
{
title: 'Чекбокс0',
helpText: 'Подсказка',
attrs: {
name: 'check0',
}
},
])
+field-checkbox([
{
title: 'Чекбокс1',
helpText: 'Подсказка',
mods: 'error',
attrs: {
name: 'check1',
checked: true,
}
},
{
title: 'Чекбокс2',
helpText: 'Подсказка',
mods: 'error',
attrs: {
name: 'check2',
}
},
], 'ОбщееНазваниеБлока')
.field-checkbox&attributes(attributes)
if(typeof(title) !== 'undefined' && title)
.field-checkbox__title!= title
each checkbox in checkboxes
-
var allMods = '';
if(typeof(checkbox.mods) !== 'undefined' && checkbox.mods) {
var modsList = checkbox.mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' field-checkbox__input-wrap--' + modsList[i].trim();
}
}
.field-checkbox__input-wrap(class=allMods)
label.field-checkbox__name
input.field-checkbox__input(type='checkbox')&attributes(checkbox.attrs)
span.field-checkbox__name-text!= checkbox.title
if(typeof(checkbox.helpText) !== 'undefined' && checkbox.helpText)
.field-checkbox__help-text-wrap
.field-checkbox__help-text!= checkbox.helpText
// В этом файле должны быть стили для БЭМ-блока field-checkbox, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.field-checkbox {
$block-name: &; // #{$block-name}__element
display: block;
margin-bottom: $typo-margin-vertical;
&__title {
@include field-name;
}
&__input-wrap {
& + & {
margin-top: $typo-margin-vertical;
}
&--error {
#{$block-name}__name-text,
#{$block-name}__help-text {
color: $color-danger;
}
}
}
&__name {
display: inline-block;
position: relative;
padding-left: 1.5em;
margin-right: 0.6em;
font-weight: 400;
line-height: $line-height;
}
&__name-text {
display: inline-block;
// свой чекбокс с картинкой
// &:before {
// content: '';
// position: absolute;
// top: 5px;
// left: 0;
// width: 14px;
// height: 14px;
// background-image: svg-load('../blocks/field-checkbox/bg-img/checkbox.svg');
// background-size: 100%;
// }
// @at-root input:checked ~ &:before {
// background-image: svg-load('../blocks/field-checkbox/bg-img/checkbox--checked.svg');
// }
// / свой чекбокс с картинкой
// свой чекбокс без картинок
@at-root input:disabled ~ & {
cursor: not-allowed;
}
&:before {
content: '';
position: absolute;
top: 5px;
left: 0;
width: 14px;
height: 14px;
border: 2px solid currentColor;
border-radius: $border-radius;
}
@at-root input:focus ~ &:before {
@include field-focus;
}
@at-root input:disabled ~ &:before {
border-color: $gray-light;
background: $gray-lighter;
}
&:after {
content: '';
position: absolute;
top: 6px;
left: 3px;
width: 14px;
height: 6px;
opacity: 0;
border-left: 2px solid currentColor;
border-bottom: 2px solid currentColor;
transform: rotate(-45deg);
transition: opacity $transition-time;
box-shadow: 1px 2px 0 #fff, inset 0 -2px 0 #fff;
@at-root input:checked ~ & {
opacity: 1;
}
@at-root input:checked:disabled ~ & {
border-color: $gray-light;
}
}
// / свой чекбокс без картинок
}
&__input {
position: absolute;
top: 0.7em;
left: 0;
margin: 0;
padding: 0;
transform: translateY(-50%);
&:focus,
&:active {
@include field-focus;
}
// сокрытие инпута в случае использования своего чекбокса
opacity: 0;
}
&__help-text-wrap {
padding-left: 1.5em;
}
&__help-text {
@include field-help-text;
}
}
В стилевом файле есть закомментированные фрагменты для замены нативного элемента своим (svg).
/* global document */
import closest from 'closest';
import ready from 'Utils/documentReady.js';
ready(function() {
/*
Форма: работа стилизованного input[type="file"]
Автор: Osvaldas Valutis, www.osvaldas.info (адаптировано под используемую разметку)
Available for use under the MIT License
*/
var inputs = document.querySelectorAll( '.field-file__input:not([disabled])' );
Array.prototype.forEach.call( inputs, function( input )
{
var label = closest(input, '.field-file').querySelector( '.field-file__name-text' ),
labelVal = label.innerHTML;
input.addEventListener( 'change', function( e ) {
var fileName = '';
if( this.files && this.files.length > 1 ) {
fileName = ( this.getAttribute( 'data-multiple-caption' ) || '' ).replace( '{count}', this.files.length );
}
else {
fileName = e.target.value.split( '\\' ).pop();
}
if( fileName ) {
label.innerHTML = fileName;
}
else {
label.innerHTML = labelVal;
}
});
});
});
//- Все примеси в этом файле должны начинаться c имени блока (field-file)
mixin field-file(props)
//- Принимает:
//- props {object}
//- title: '' {string} - текст с названием (выводится над полем)
//- helpText: '' {string} - пояснение под полем
//- mods: '' {string} - модификаторы блока
//- attrs: {object} - любые атрибуты для input
//- name: {string}
//- text: {object} - тексты
//- selectText: {string} - текст на кнопке «выберите файл(ы)»
//- nothingText: {string} - текст, показываемый, пока ничего не выбрано
//- flesSelectedText: {string} - текст, показываемый, если выбрано более одного файла («файлов выбрано: 2»)
//- Вызов:
+field-file({
title: 'Название',
helpText: 'Подсказка',
mods: '',
attrs: {
name: 'commentFile',
multiple: true,
},
text: {
selectText: 'Выберите файл(ы)',
nothingText: 'Ничего не выбрано',
flesSelectedText: 'Выбрано файлов:',
},
})
-
if(typeof(props) === 'undefined') {
var props = {};
}
var allMods = '';
if(typeof(props.mods) !== 'undefined' && props.mods) {
var modsList = props.mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' field-file--' + modsList[i].trim();
}
}
var selectText = 'Выберите файл(ы)'; // Select file(s)
var nothingText = ''; // Nothing selected
var flesSelectedText = 'Выбрано файлов: {count}'; // Selected files: {count}
if (typeof(props.text) !== 'undefined' && props.text !== '') {
if (typeof(props.text.selectText) !== 'undefined') selectText = props.text.selectText;
if (typeof(props.text.nothingText) !== 'undefined') nothingText = props.text.nothingText;
if (typeof(props.text.flesSelectedText) !== 'undefined') flesSelectedText = props.text.flesSelectedText + ' {count}';
}
.field-file(class=allMods)&attributes(attributes)
if(typeof(props.title) !== 'undefined' && props.title)
.field-file__name!= props.title
label.field-file__input-wrap
input.field-file__input(type='file', data-multiple-caption=flesSelectedText)&attributes(props.attrs)
.field-file__name-text(data-button-text=selectText)= nothingText
if(typeof(props.helpText) !== 'undefined' && props.helpText)
.field-file__help-text!= props.helpText
block
// В этом файле должны быть стили для БЭМ-блока field-file, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.field-file {
$block-name: &; // #{$block-name}__element
display: block;
margin-bottom: $typo-margin-vertical;
&__name {
@include field-name;
}
&__input-wrap {
display: block;
position: relative;
}
&__input {
position: absolute;
width: 0;
height: 0;
opacity: 0;
&:active ~ .field-file__name-text,
&:focus ~ .field-file__name-text {
@include field-focus;
}
}
&__name-text {
display: flex;
align-items: baseline;
position: relative;
width: 100%;
border: 1px solid $border-color;
border-radius: $border-radius;
background-color: #fff;
white-space: nowrap;
text-overflow: ellipsis;
cursor: pointer;
overflow: hidden;
@at-root input:disabled ~ & {
background: $gray-lighter;
cursor: not-allowed;
}
&:before {
content: attr(data-button-text);
display: block;
background-color: $gray-lightest;
border-right: 1px solid $border-color;
margin-right: $field-padding-horizontal;
line-height: $line-height;
padding: $field-padding-vertical $field-padding-horizontal;
border-top-left-radius: $border-radius;
border-bottom-left-radius: $border-radius;
}
&:hover,
&:focus {
&:before {
background-color: darken($gray-lightest, 10%); // как у обычных кнопок
}
}
}
&__help-text {
@include field-help-text;
}
&--error {
#{$block-name}__name,
#{$block-name}__name-text,
#{$block-name}__input,
#{$block-name}__help-text {
color: $color-danger;
}
#{$block-name}__name-text {
border-color: $color-danger;
background-color: lighten($color-danger, 35%);
}
}
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 357 357"><path d="M357 204H0v-51h357v51z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 357 357"><path d="M357 204H204v153h-51V204H0v-51h153V0h51v153h153v51z"/></svg>
/* global document */
import ready from 'Utils/documentReady.js';
ready(function(){
var fields = document.querySelectorAll( '.field-num' );
if(fields.length) {
Array.prototype.forEach.call( fields, function( field ) {
const input = field.querySelector('.field-num__input');
const valueMin = input.getAttribute('min') ? +input.getAttribute('min') : -Infinity;
const valueMax = input.getAttribute('max') ? +input.getAttribute('max') : Infinity;
const valueStep = input.getAttribute('step') ? +input.getAttribute('step') : 1;
field.addEventListener('click', function(event){
if(event.target.classList.contains('field-num__btn') && !input.getAttribute('disabled')) {
let num = parseInt(input.value);
if(isNaN(num)) num = 0;
if(event.target.classList.contains('field-num__btn--plus')) {
if (num < valueMax) input.value = num + valueStep;
}
if(event.target.classList.contains('field-num__btn--minus')) {
if (num > valueMin) input.value = num - valueStep;
}
}
});
});
}
});
//- Все примеси в этом файле должны начинаться c имени блока (field-num)
mixin field-num(props)
//- Принимает:
//- props {
//- title: '' {string} - текст с названием (выводится над полем)
//- helpText: '' {string} - пояснение под полем
//- mods: '' {string} - модификаторы блока
//- val: {number} - количество в поле
//- attrs: {object} - любые атрибуты для input
//- placeholder: {string}
//- Вызов:
+field-num({
title: 'Количество',
helpText: 'От 1 до 10, шаг 1',
mods: '',
val: '9',
attrs: {
name: 'quantity',
max: '10',
min: '1',
step: '1',
}
})
+field-num({
mods: 'error',
attrs: {
name: 'quantity',
}
})
-
if(typeof(props) === 'undefined') { var props = {}; }
var allMods = '';
if(typeof(props.mods) !== 'undefined' && props.mods) {
var modsList = props.mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' field-num--' + modsList[i].trim();
}
}
if(props.attrs.disabled) allMods = allMods + ' field-num--disabled';
var val = parseInt(props.val);
if(isNaN(val)) val = 0;
.field-num(class=allMods)&attributes(attributes)
if(typeof(props.title) !== 'undefined' && props.title)
span.field-num__name!= props.title
span.field-num__input-wrap
span.field-num__input-and-btns
button.field-num__btn.field-num__btn--minus(type='button') -
button.field-num__btn.field-num__btn--plus(type='button') +
input.field-num__input(type='number', value=val)&attributes(props.attrs)
if(typeof(props.helpText) !== 'undefined' && props.helpText)
span.field-num__help-text!= props.helpText
block
// В этом файле должны быть стили для БЭМ-блока field-num, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.field-num {
$block-name: &; // #{$block-name}__element
display: block;
margin-bottom: $typo-margin-vertical;
&__name {
@include field-name;
}
&__input-wrap {
display: block;
}
&__input-and-btns {
display: block;
position: relative;
width: 140px;
}
&__input {
@include field-text;
padding: $field-padding-vertical 45px;
text-align: center;
#{$block-name}:not(#{$block-name}--disabled) button:focus ~ & { // stylelint-disable-line
@include field-focus;
}
}
&__btn {
position: absolute;
top: 1px;
bottom: 1px;
width: 40px;
border: none;
padding: 0;
border-top-right-radius: $border-radius;
border-bottom-right-radius: $border-radius;
font: 0/0 a; // stylelint-disable-line
color: transparent;
background: transparent;
&:hover,
&:focus {
background-color: $gray-lightest;
cursor: pointer;
}
&:focus {
outline: none;
}
&--plus {
right: 1px;
background: svg-load('../field-num/bg-img/plus.svg') center no-repeat;
background-size: 14px 14px;
}
&--minus {
left: 1px;
background: svg-load('../field-num/bg-img/minus.svg') center no-repeat;
background-size: 14px 14px;
}
#{$block-name}--disabled & {
cursor: not-allowed;
&:hover,
&:focus {
background-color: transparent;
}
}
}
&__help-text {
@include field-help-text;
}
&--error {
#{$block-name}__name,
#{$block-name}__input,
#{$block-name}__help-text {
color: $color-danger;
}
#{$block-name}__input {
border-color: $color-danger;
background-color: lighten($color-danger, 35%);
}
}
}
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#2D2D2D" d="M12 20.016q3.28 0 5.648-2.367T20.015 12t-2.367-5.647T12 3.986 6.352 6.353 3.985 12t2.367 5.65T12 20.015zm0-18q4.125 0 7.055 2.93T21.985 12t-2.93 7.056T12 21.986t-7.055-2.93T2.015 12t2.93-7.054T12 2.016zm0 4.968q2.063 0 3.54 1.477T17.015 12t-1.477 3.54T12 17.015 8.46 15.54 6.985 12 8.46 8.46 12 6.985z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#2D2D2D" d="M12 20.016q3.28 0 5.648-2.367T20.015 12t-2.367-5.647T12 3.986 6.352 6.353 3.985 12t2.367 5.65T12 20.015zm0-18q4.125 0 7.055 2.93T21.985 12t-2.93 7.056T12 21.986t-7.055-2.93T2.015 12t2.93-7.054T12 2.016z"/></svg>
\ No newline at end of file
//- Все примеси в этом файле должны начинаться c имени блока (field-radio)
mixin field-radio(radiobuttons, title)
//- Принимает:
//- radiobuttons {array}
//- {object}
//- title: '' {string} - текст рядом с радиокнопкой
//- helpText: '' {string} - пояснение под полем
//- mods: '' {string} - модификаторы обертки радиокнопки
//- attrs: {object} - любые атрибуты для input
//- name: {string}
//- ...
//- Вызов:
+field-radio([
{
title: 'Радиокнопка1',
helpText: 'Подсказка',
mods: '',
attrs: {
name: 'radio',
checked: true,
}
},
{
title: 'Радиокнопка2',
helpText: 'Подсказка',
mods: '',
attrs: {
name: 'radio',
}
},
], 'ОбщееНазваниеБлока')
.field-radio&attributes(attributes)
if(typeof(title) !== 'undefined' && title)
.field-radio__title!= title
each checkbox in radiobuttons
-
var allMods = '';
if(typeof(checkbox.mods) !== 'undefined' && checkbox.mods) {
var modsList = checkbox.mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' field-radio__input-wrap--' + modsList[i].trim();
}
}
.field-radio__input-wrap(class=allMods)
label.field-radio__name
input.field-radio__input(type='radio')&attributes(checkbox.attrs)
span.field-radio__name-text!= checkbox.title
if(typeof(checkbox.helpText) !== 'undefined' && checkbox.helpText)
.field-radio__help-text-wrap
.field-radio__help-text!= checkbox.helpText
// В этом файле должны быть стили для БЭМ-блока field-radio, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.field-radio {
$block-name: &; // #{$block-name}__element
display: block;
margin-bottom: $typo-margin-vertical;
&__title {
@include field-name;
}
&__input-wrap {
& + & {
margin-top: $typo-margin-vertical;
}
&--error {
#{$block-name}__name-text,
#{$block-name}__help-text {
color: $color-danger;
}
}
}
&__name {
display: inline-block;
position: relative;
margin-right: 0.6em;
padding-left: 1.5em;
font-weight: 400;
line-height: $line-height;
}
&__name-text {
display: inline-block;
// своя радиокнопка с картинкой
// &:before {
// content: '';
// position: absolute;
// top: 5px;
// left: 0;
// width: 14px;
// height: 14px;
// background-image: svg-load('../blocks/field-radio/bg-img/radiobutton.svg');
// background-size: 100%;
// }
// @at-root input:checked ~ &:before {
// background-image: svg-load('../blocks/field-radio/bg-img/radiobutton--checked.svg');
// }
// / своя радиокнопка с картинкой
// своя радиокнопка без картинки
@at-root input:disabled ~ & {
cursor: not-allowed;
}
&:before {
content: '';
position: absolute;
top: 5px;
left: 0;
width: 14px;
height: 14px;
border: 2px solid currentColor;
border-radius: 50%;
}
@at-root input:focus ~ &:before {
@include field-focus;
}
@at-root input:disabled ~ &:before {
border-color: $gray-light;
background: $gray-lighter;
}
&:after {
content: '';
position: absolute;
top: 9px;
left: 4px;
width: 6px;
height: 6px;
opacity: 0;
border-radius: 50%;
background-color: currentColor;
transition: opacity $transition-time;
@at-root input:checked ~ & {
opacity: 1;
}
@at-root input:checked:disabled ~ & {
background-color: $gray-light;
}
}
// / своя радиокнопка без картинки
}
&__input {
position: absolute;
top: 0.8em;
left: 0;
margin: 0;
padding: 0;
transform: translateY(-50%);
&:focus,
&:active {
@include field-focus;
}
// сокрытие инпута в случае использования своей радиокнопки
opacity: 0;
}
&__help-text-wrap {
padding-left: 1.5em;
}
&__help-text {
@include field-help-text;
}
}
В стилевом файле есть закомментированные фрагменты для замены нативного элемента своим (svg).
//- Все примеси в этом файле должны начинаться c имени блока (field-range)
mixin field-range(props)
//- Принимает:
//- props {object}
//- title: '' {string} - текст с названием (выводится над полем)
//- helpText: '' {string} - пояснение под полем
//- mods: '' {string} - модификаторы блока
//- attrs: {object} - любые атрибуты для input
//- name: {string}
//- Вызов:
+field-range({
title: 'Название',
helpText: 'Подсказка',
mods: '',
attrs: {
name: 'counter',
min: '1',
max: '100',
step: '1',
value: '40',
}
})
-
if(typeof(props) === 'undefined') {
var props = {};
}
var allMods = '';
if(typeof(props.mods) !== 'undefined' && props.mods) {
var modsList = props.mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' field-range--' + modsList[i].trim();
}
}
label.field-range(class=allMods)&attributes(attributes)
if(typeof(props.title) !== 'undefined' && props.title)
span.field-range__name!= props.title
span.field-range__input-wrap
input.field-range__input(type='range')&attributes(props.attrs)
if(typeof(props.helpText) !== 'undefined' && props.helpText)
span.field-range__help-text!= props.helpText
block
// В этом файле должны быть стили для БЭМ-блока field-range, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.field-range {
display: block;
margin-bottom: $typo-margin-vertical;
&__name {
@include field-name;
}
&__input-wrap {
display: block;
position: relative;
}
&__input {
align-self: center;
width: 100%;
height: 2em;
border-radius: 0.25em;
border: 0;
padding: 0;
background: none;
font-size: inherit;
cursor: pointer;
appearance: none;
&::-webkit-slider-runnable-track,
&::-webkit-slider-thumb {
-webkit-appearance: none; // stylelint-disable-line property-no-vendor-prefix
}
&:focus {
@include field-focus;
}
// Фоновая линия ползунка
// Да, да, и это — локальная примесь.
@mixin field-range-runnable-track {
box-sizing: border-box;
width: 100%;
height: 0.75em;
border-radius: $border-radius;
background: #fff;
border: 1px solid $border-color;
}
// Увы, тут похожие наборы свойств приходится писать порознь.
&::-webkit-slider-runnable-track {
@include field-range-runnable-track; // локальная примесь!
}
&::-moz-range-track {
@include field-range-runnable-track; // локальная примесь!
}
&::-ms-track {
@include field-range-runnable-track; // локальная примесь!
color: transparent;
}
// Смещающийся элемент ползунка
// Да, да, и это — локальная примесь.
@mixin field-range-slider-thumb {
box-sizing: border-box;
width: 2em;
height: 2em;
border: 1px solid $border-color;
border-radius: 1em;
padding: 0;
box-shadow: none;
cursor: ew-resize;
background: $gray-lightest;
}
&::-webkit-slider-thumb {
@include field-range-slider-thumb; // локальная примесь!
margin-top: -0.68em;
}
&::-moz-range-thumb {
@include field-range-slider-thumb; // локальная примесь!
}
&::-ms-thumb {
@include field-range-slider-thumb; // локальная примесь!
}
&::-ms-fill-lower,
&::-ms-tooltip {
display: none;
}
}
&__help-text {
@include field-help-text;
padding-top: 0;
}
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17 11" width="17" height="11"><path fill="#B1B1B1" d="M.202 1.97L1.956.204A.61.61 0 0 1 2.43 0a.61.61 0 0 1 .47.204L8.5 5.84 14.1.204A.61.61 0 0 1 14.57 0a.61.61 0 0 1 .474.204l1.754 1.766a.618.618 0 0 1 .202.473.62.62 0 0 1-.202.476l-7.826 7.875A.61.61 0 0 1 8.5 11a.61.61 0 0 1-.472-.204L.202 2.92A.618.618 0 0 1 0 2.444c0-.203.067-.34.202-.475z"/></svg>
/* global document */
import ready from 'Utils/documentReady.js';
import Choices from 'choices.js';
ready(function(){
if (typeof Object.assign != 'function') {
Object.assign = function(target) {
'use strict';
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
target = Object(target);
for (var index = 1; index < arguments.length; index++) {
var source = arguments[index];
if (source != null) {
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
}
return target;
};
}
// Включим на каком-то конкретном отдельно
// const choices = new Choices('#some-if', {/* options */});
// Или тупо найдём все селекты и включим на них Choices
const selects = document.querySelectorAll('.field-select__select');
selects.forEach(function(item){
new Choices(item, {
searchEnabled: false,
placeholderValue: 'Выберите',
});
});
});
//- Все примеси в этом файле должны начинаться c имени блока (field-select)
mixin field-select(title, attrs, options, helpText, mods)
//- Принимает:
//- title: '' {string} - название селекта
//- attrs: {object} - атрибуты селекта
//- name: '' {string}
//- options {array}
//- {object}
//- title: '' {string} - текст пункта
//- attrs: {object} - любые атрибуты пункта
//- value: {string}
//- {object}
//- attrs: {object} - любые атрибуты пункта
//- label: {string}
//- child: {array} - потомки (если есть этот элемент, то его родитель — optgroup)
//- {object}
//- title: '' {string}
//- attrs: {object}
//- value: {string}
//- {object}
//- title: '' {string} - текст пункта
//- attrs: {object} - любые атрибуты пункта
//- value: {string}
//- helpText: '' {string} - текст подсказки
//- mods: '' {string} - модификаторы блока
//- Вызов:
+field-select(
'Название',
{
name: 'select',
id: 'select'
},
[
{
title: 'Опция1',
attrs: {
value: '',
}
},
{
title: 'Опция2',
attrs: {
value: '',
}
},
],
'Подсказка',
''
)
+field-select(
'Название',
{
name: '',
id: ''
},
[
{
attrs: {
label: 'Группа',
},
child: [
{
title: 'Опция1',
attrs: {
value: 'val01',
}
},
{
title: 'Опция2',
attrs: {
value: 'val02',
}
},
]
},
{
title: 'Опция3',
attrs: {
value: 'val03',
}
}
],
'Подсказка',
''
)
-
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' field-select--' + modsList[i].trim();
}
}
.field-select(class=allMods)&attributes(attributes)
if(typeof(title) !== 'undefined' && title)
.field-select__name!= title
.field-select__select-wrap
select.field-select__select&attributes(attrs)
each option in options
//- option= option
if(typeof(option.child) !== 'undefined' && option.child)
optgroup(label=option.attrs.label)
each subOption in option.child
option&attributes(subOption.attrs)= subOption.title
else if(typeof(option.title) !== 'undefined' && option.title)
option&attributes(option.attrs)= option.title
if(typeof(helpText) !== 'undefined' && helpText)
span.field-select__help-text!= helpText
// В этом файле должны быть стили для БЭМ-блока field-select, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.field-select {
$block-name: &; // #{$block-name}__element
display: block;
margin-bottom: $typo-margin-vertical;
&__name {
@include field-name;
}
&__select-wrap {}
// Оформление штатного селекта
// &__select {
// display: inline-block;
// vertical-align: middle;
// width: 100%;
// line-height: $line-height;
// padding: $field-padding-vertical $field-padding-horizontal;
// padding-right: 35px;
// font-size: inherit;
// font-family: inherit;
// border: 1px solid $border-color;
// border-radius: $border-radius;
// appearance: none;
// background-color: #fff;
// &[multiple] {
// background-image: none;
// min-height: 6.8em;
// }
// &::-ms-expand {
// display: none;
// }
// &:active,
// &:focus {
// @include field-focus;
// }
// &[disabled],
// &[readonly] {
// cursor: default;
// background-color: $gray-lightest;
// }
// }
&__help-text {
@include field-help-text;
}
&--error {
color: $color-danger;
#{$block-name}__name,
#{$block-name}__select,
#{$block-name}__help-text {
color: $color-danger;
}
#{$block-name}__select,
.choices__inner,
.choices:after {
border-color: $color-danger;
background-color: lighten($color-danger, 35%);
}
}
}
/** @define choices */
.choices {
$block-name: &; // #{$block-name}__element
position: relative;
&:focus {
outline: 0;
&:not(.is-disabled) {
#{$block-name}__inner {
@include field-focus;
}
}
}
&:after {
content: '';
position: absolute;
top: 7px;
right: 16px;
width: 14px;
height: 14px;
border-left: 1px solid $border-color;
border-bottom: 1px solid $border-color;
pointer-events: none;
transform: rotate(-45deg);
transition: all 0.3s;
}
&.is-open:after {
transform: translateY(7px) rotate(135deg);
}
.is-hidden {
display: none;
}
&__inner {
border: 1px solid $border-color;
border-radius: $border-radius;
padding: $field-padding-vertical 40px $field-padding-vertical $field-padding-horizontal;
background: #fff;
font-size: 1em;
line-height: 1.5;
cursor: pointer;
#{$block-name}__item--selectable {
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
&__list--dropdown {
display: none;
position: absolute;
z-index: 1;
top: 100%;
width: 100%;
margin-top: -1px;
border: 1px solid $border-color;
border-bottom-left-radius: $border-radius;
border-bottom-right-radius: $border-radius;
background-color: #fff;
word-break: break-all;
overflow: hidden;
#{$block-name}__item {
padding: $field-padding-vertical $field-padding-horizontal;
cursor: pointer;
&.is-highlighted {
background: $gray-lightest;
}
}
}
&__heading {
padding: $field-padding-vertical $field-padding-horizontal;
font-size: $font-size-sm;
font-weight: 700;
opacity: 0.5;
cursor: default;
}
&.is-open {
#{$block-name}__list--dropdown {
display: block;
}
}
&.is-disabled {
#{$block-name}__inner {
background-color: $gray-lighter;
cursor: default;
}
}
}
/* global document */
import ready from 'Utils/documentReady.js';
import autosize from 'autosize';
ready(function() {
autosize(document.querySelectorAll('textarea'));
});
//- Все примеси в этом файле должны начинаться c имени блока (field-text)
mixin field-text(props)
//- Принимает:
//- props {
//- title: '' {string} - текст с названием (выводится над полем)
//- isTextarea: false {bool} - флаг input/textarea
//- helpText: '' {string} - пояснение под полем
//- mods: '' {string} - модификаторы блока
//- val: '' {string} - текст в поле
//- attrs: {object} - любые атрибуты для input/textarea
//- type: {string}
//- placeholder: {string}
//- Вызов:
+field-text({
title: 'Название',
isTextarea: true,
helpText: 'Подсказка',
mods: '',
val: '',
attrs: {
name: 'comment',
}
})
-
if(typeof(props) === 'undefined') {
var props = {};
}
var allMods = '';
if(typeof(props.mods) !== 'undefined' && props.mods) {
var modsList = props.mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' field-text--' + modsList[i].trim();
}
}
label.field-text(class=allMods)&attributes(attributes)
if(typeof(props.title) !== 'undefined' && props.title)
span.field-text__name!= props.title
span.field-text__input-wrap
if(typeof(props.isTextarea) !== 'undefined' && props.isTextarea)
textarea.field-text__input&attributes(props.attrs)= props.val
else
input.field-text__input(type=(typeof(props.attrs) !== 'undefined' && props.attrs.type) ? props.attrs.type : 'text', value=props.val)&attributes(props.attrs)
if(typeof(props.helpText) !== 'undefined' && props.helpText)
span.field-text__help-text!= props.helpText
block
// В этом файле должны быть стили для БЭМ-блока field-text, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.field-text {
$block-name: &; // #{$block-name}__element
display: block;
margin-bottom: $typo-margin-vertical;
&__name {
@include field-name;
}
&__input-wrap {
display: block;
}
&__input {
@include field-text;
@at-root textarea#{&} {
$textarea-height: ($line-height * 2) + ($field-padding-vertical * 2);
height: auto;
min-height: calc(#{$textarea-height} + 2px);
resize: vertical;
}
}
&__help-text {
@include field-help-text;
}
&--error {
#{$block-name}__name,
#{$block-name}__input,
#{$block-name}__help-text {
color: $color-danger;
}
#{$block-name}__input {
border-color: $color-danger;
background-color: lighten($color-danger, 35%);
}
}
}
Текстовые поля любого типа. В том числе, с использованием не `input`, а `textarea`. Для `textarea` использован [autosize](https://github.com/jackmoore/autosize).
//- Все примеси в этом файле должны начинаться c имени блока (field-toggler)
mixin field-toggler(togglers, title, isRadio)
//- Принимает:
//- togglers {array}
//- {object}
//- title: '' {string} - текст рядом с переключателем
//- helpText: '' {string} - пояснение под полем
//- mods: '' {string} - модификаторы обёртки переключателя
//- attrs: {object} - любые атрибуты для input
//- name: {string}
//- ...
//- title: '' {string} - общее название группы переключателей
//- isRadio: false {bool} - флаг «это радиокнопки, а не чекбоксы»
//- Вызов:
+field-toggler([
{
title: 'Переключатель0',
helpText: 'Подсказка',
attrs: {
name: 'check0',
}
},
])
+field-toggler([
{
title: 'Переключатель1',
helpText: 'Подсказка',
attrs: {
name: 'check1',
checked: true,
}
},
{
title: 'Переключатель2',
helpText: 'Подсказка',
mods: 'some',
attrs: {
name: 'check2',
}
},
], 'ОбщееНазваниеБлока', true)
-
if(typeof(isRadio) !== 'undefined' && isRadio)
var type = 'radio';
else
var type = 'checkbox';
.field-toggler&attributes(attributes)
if(typeof(title) !== 'undefined' && title)
.field-toggler__title!= title
each toggler in togglers
-
var allMods = '';
if(typeof(toggler.mods) !== 'undefined' && toggler.mods) {
var modsList = toggler.mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' field-toggler__input-wrap--' + modsList[i].trim();
}
}
.field-toggler__input-wrap(class=allMods)
label.field-toggler__name
input.field-toggler__input(type=type)&attributes(toggler.attrs)
span.field-toggler__name-text!= toggler.title
if(typeof(toggler.helpText) !== 'undefined' && toggler.helpText)
.field-toggler__help-text-wrap
.field-toggler__help-text!= toggler.helpText
// В этом файле должны быть стили для БЭМ-блока field-toggler, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.field-toggler {
$block-name: &; // #{$block-name}__element
display: block;
margin-bottom: $typo-margin-vertical;
&__title {
@include field-name;
}
&__input-wrap {
& + & {
margin-top: $typo-margin-vertical;
}
&--error {
#{$block-name}__name-text,
#{$block-name}__help-text {
color: $color-danger;
}
#{$block-name}__name-text:after {
background: $color-danger;
}
}
}
&__name {
display: inline-block;
position: relative;
padding-left: 3.2em;
margin-right: 0.6em;
font-weight: 400;
line-height: $line-height;
}
&__name-text {
display: inline-block;
@at-root input:disabled ~ & {
cursor: not-allowed;
}
&:before {
content: '';
position: absolute;
top: 3px;
left: 0;
width: 35px;
height: 18px;
background-color: #fff;
border: 1px solid $border-color;
@at-root input:focus ~ & {
@include field-focus;
}
}
@at-root input:disabled ~ &:before {
border-color: $gray-light;
background: $gray-lighter;
}
&:after {
content: '';
position: absolute;
top: 5px;
left: 2px;
width: 14px;
height: 14px;
background-color: $gray-lightest;
border: 1px solid $border-color;
transition: all 0.3s;
}
@at-root input:checked ~ &:after {
transform: translate(17px, 0);
}
@at-root input:disabled ~ &:after {
background-color: $gray-light;
}
}
&__input {
position: absolute;
top: 0;
left: 0;
width: 0;
height: 0;
opacity: 0;
}
&__help-text-wrap {
padding-left: 3.2em;
}
&__help-text {
@include field-help-text;
}
}
/* global document */
const closest = require('closest');
import ready from 'Utils/documentReady.js';
ready(function(){
// Для всех форм страницы
const forms = Array.from(document.querySelectorAll('form[data-check-form]'));
forms.forEach(function(form){
// Подпишемся на событие отправки
form.addEventListener('submit', function(e){
let valid = true;
// Проверим все текстовые инпуты
const fieldsText = Array.from(form.querySelectorAll('input[data-check-pattern]'));
fieldsText.forEach(function(input){
if(!checkFieldText(input)) valid = false;
});
// Проверим все чекбоксы
const fieldsCheckbox = Array.from(form.querySelectorAll('input[data-check-state]'));
fieldsCheckbox.forEach(function(input){
if(!checkFieldCheckbox(input)) valid = false;
});
// Если были ошибки, не отправляем форму
if(!valid) e.preventDefault();
});
});
// Для всех проверяемых текстовых полей
const fieldsText = Array.from(document.querySelectorAll('input[data-check-pattern]'));
fieldsText.forEach(function(input){
let hasBeenAlreadyBlured = false;
input.addEventListener('blur', function(){
checkFieldText(input);
if(!hasBeenAlreadyBlured) hasBeenAlreadyBlured = true;
});
input.addEventListener('input', function(){ if(hasBeenAlreadyBlured) checkFieldText(input); });
});
// Для всех проверяемых чекбоксов
const fieldsCheckbox = Array.from(document.querySelectorAll('input[data-check-state]'));
fieldsCheckbox.forEach(function(input){
input.addEventListener('change', function(){ checkFieldCheckbox(input); });
});
function checkFieldText(input) {
const regExp = new RegExp(input.dataset.checkPattern, 'gi');
const result = regExp.test(input.value);
const errorClass = 'field-text--error';
const parent = closest(input, '.field-text');
result ? parent.classList.remove(errorClass) : parent.classList.add(errorClass);
return result;
}
function checkFieldCheckbox(input) {
const trueVal = input.dataset.checkState == 'on' ? true : false;
const result = trueVal === input.checked
const errorClass = 'field-checkbox__input-wrap--error';
const parent = closest(input, '.field-checkbox__input-wrap');
result ? parent.classList.remove(errorClass) : parent.classList.add(errorClass);
return result;
}
});
Включается при указании на теге `form` атрибута `data-check-form`.
Для текстовых полей: содержимое поля анализируется на соответствие регулярному выражению из атрибута `data-check-pattern` по событию `blur`, а также по событию `input` при последующем фокусе.
Для чекбоксов: по событию `change` проверяется соответствия состояния и `data-check-state="on"` (`off`).
<p class="alert alert--warning">Блок не имеет классов, упоминаемых в разметке. Чтобы взять его в сборку, упомяните <code>form-validation</code> в <code>config.js#alwaysAddBlocks</code>.</p>
<p class="alert alert--warning">Pug «умно» обрабатывает строки, убирая обратный слеш (<code>\</code>), этот символ в pug нужно экранировать: не <code>\d</code>, а <code>\\d</code>. <br>См. пример кода ниже.</p>
//- Все примеси в этом файле должны начинаться c имени блока (form)
//- ВНИМАНИЕ: в файле две примеси
mixin form(mods)
//- Принимает:
//- mods {string} - список модификаторов
//- Вызов:
+form()
+fieldset('Название1')
+field-text({
helpText: 'Подсказка',
attrs: {
placeholder: 'Пример'
}
})
+field-text({
helpText: 'Подсказка',
attrs: {
placeholder: 'Пример'
}
})
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' form--' + modsList[i].trim();
}
}
form.form(class=allMods)&attributes(attributes)
block
mixin fieldset(legend, mods)
//- Принимает:
//- legend {string} - название группы полей
//- mods {string} - список модификаторов
//- Вызов:
+fieldset('Название1')
+field-text({
helpText: 'Подсказка',
attrs: {
placeholder: 'Пример'
}
})
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' form--' + modsList[i].trim();
}
}
fieldset.form__fieldset(class=allMods)&attributes(attributes)
legend.form__legend!= legend
block
// В этом файле должны быть стили для БЭМ-блока form, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.form {
&__fieldset {
min-width: 0;
margin: 0;
border: 0;
padding: 0;
&:not(:last-child) {
margin-bottom: $typo-margin-vertical;
}
}
&__legend {
display: block;
width: 100%;
max-width: 100%; // Привет IE
margin-top: 0;
margin-bottom: $typo-margin-vertical;
font-size: $font-size-h3;
font-family: $font-family;
font-weight: 700;
line-height: $line-height;
white-space: normal; // Привет IE
color: currentColor;
}
}
//- Все примеси в этом файле должны начинаться c имени блока (label)
mixin label(text, mods, tag)
//- Принимает:
//- text {string} - текст кнопки
//- mods {string} - список модификаторов
//- tag {string} - тег (если не span)
//- Вызов:
+label('Лейбл')
+label('Лейбл', 'danger', 'div')
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' label--' + modsList[i].trim();
}
}
if(typeof(tag) !== 'undefined' && tag)
#{tag}.label(class=allMods)&attributes(attributes)!= text
block
else
span.label(class=allMods)&attributes(attributes)!= text
// В этом файле должны быть стили для БЭМ-блока label, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.label {
display: inline-block;
position: relative;
margin: 0 0.1em;
vertical-align: baseline;
line-height: 1;
padding: 0.25em 0.4em 0.2em;
background-color: $gray-lightest;
color: $text-color;
text-decoration: none;
text-transform: none;
font-weight: 700;
font-size: $font-size-sm;
white-space: nowrap;
border-radius: $border-radius;
&:hover,
&:focus {
color: $text-color;
text-decoration: none;
}
// &--main {
// background-color: $color-main;
// color: #fff;
// &:hover,
// &:focus {
// color: #fff;
// }
// }
}
//- Все примеси в этом файле должны начинаться c имени блока (loader)
mixin loader(mods)
//- Принимает:
//- mods {string} - список модификаторов
//- Вызов:
+loader()
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' loader--' + modsList[i].trim();
}
}
span.loader(class=allMods)&attributes(attributes)
// В этом файле должны быть стили для БЭМ-блока loader, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.loader {
display: inline-block;
&:before {
content: '';
display: block;
width: 3rem;
height: 3rem;
border: 3px solid $gray-lightest;
border-top: 3px solid $gray-lighter;
border-radius: 50%;
animation: loader-rotate 1s linear infinite;
}
}
@keyframes loader-rotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
//- Все примеси в этом файле должны начинаться c имени блока (logo)
mixin logo(href, mods)
//- Принимает:
//- href {string} - ссылка логотипа
//- mods {string} - список модификаторов
//- Вызов:
+logo('/')
img(src='img/demo-logo.svg', alt='Project_name', width='64', height='15')
+logo(false, 'mod')
img(src='img/demo-logo.svg', alt='Project_name', width='64', height='15')
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' logo--' + modsList[i].trim();
}
}
a.logo(class=allMods, href=href)&attributes(attributes)
block
// В этом файле должны быть стили для БЭМ-блока logo, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.logo {
$block-name: &; // #{$block-name}__element
display: inline-block;
white-space: nowrap;
img {
display: block;
}
}
-
var mainMenu = [
{
"id": "0",
"name": "Главная страница",
"url": "/"
},
{
"id": "10",
"name": "Страницы",
"url": "/pages",
"childs": [
{
"id": "10-0",
"name": "О компании",
"url": "pages/about"
},
{
"id": "10-10",
"name": "Услуги",
"url": "pages/services",
"childs": [
{
"id": "10-10-0",
"name": "Вёрстка",
"url": "pages/services/serve"
},
{
"id": "10-10-10",
"name": "Стриптиз",
"url": "pages/services/strip"
}
]
},
{
"id": "10-20",
"name": "Ещё одна страница",
"url": "pages/some"
},
]
},
{
"id": "20",
"name": "Репозиторий",
"url": "https://github.com/nicothin/NTH-start-project"
},
{
"id": "30",
"name": "Контакты автора",
"url": "https://nicothin.pro/contact"
},
];
/* global document Element */
import ready from 'Utils/documentReady.js';
ready(function() {
// Добавление/удаление модификаторов при фокусировке на ссылочном элементе
var linkClassName = 'main-nav__link';
var linkClassNameShowChild = 'main-nav__item--show-child';
var findLinkClassName = new RegExp(linkClassName);
// Слежение за всплывшим событием focus (нужно добавить класс, показывающий потомков)
document.addEventListener('focus', function(event) {
// Если событие всплыло от одной из ссылок гл. меню
if (findLinkClassName.test(event.target.className)) {
// Добавим классы, показывающие списки вложенных уровней, на всех родителей
var parents = getParents(event.target, '.main-nav__item');
for (var i = 0; i < parents.length; i++) {
parents[i].classList.add(linkClassNameShowChild);
}
}
}, true);
// Слежение за всплывшим событием blur (нужно убрать класс, показывающий потомков)
document.addEventListener('blur', function(event) {
// Если событие всплыло от одной из ссылок гл. меню
if (findLinkClassName.test(event.target.className)) {
// Уберем все классы, показывающие списки 2+ уровней
var parents = document.querySelectorAll('.'+linkClassNameShowChild)
for (var i = 0; i < parents.length; i++) {
parents[i].classList.remove(linkClassNameShowChild);
}
}
}, true);
// eslint-disable
/*! getParents.js | (c) 2017 Chris Ferdinandi | MIT License | http://github.com/cferdinandi/getParents */
/**
* Get all of an element's parent elements up the DOM tree
* @param {Node} elem The element
* @param {String} selector Selector to match against [optional]
* @return {Array} The parent elements
*/
var getParents = function ( elem, selector ) {
// Element.matches() polyfill
if (!Element.prototype.matches) {
Element.prototype.matches =
Element.prototype.matchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector ||
Element.prototype.oMatchesSelector ||
Element.prototype.webkitMatchesSelector ||
function(s) {
var matches = (this.document || this.ownerDocument).querySelectorAll(s),
i = matches.length;
while (--i >= 0 && matches.item(i) !== this) {} // eslint-disable-line
return i > -1;
};
}
// Setup parents array
var parents = [];
// Get matching parent elements
for ( ; elem && elem !== document; elem = elem.parentNode ) {
// Add matching parents to array
if ( selector ) {
if ( elem.matches( selector ) ) {
parents.push( elem );
}
} else {
parents.push( elem );
}
}
return parents;
};
});
//- Все примеси в этом файле должны начинаться c имени блока (main-nav)
//- Структура меню — в main-nav.data.pug
mixin main-nav(selectedID)
//- Принимает:
//- selectedID {string} - id пункта меню (по умолчанию — из main-nav.data.pug)
//- Вызов:
+main-nav('1')
//- подключаем файл со структурой данных блока
include main-nav.data.pug
nav.main-nav#nav&attributes(attributes)
+burger('Показать главное меню', 'nav', 'main-nav--open')(class='main-nav__toggler')
ul.main-nav__list
each menuItem in mainMenu
+main-nav-item(selectedID, menuItem, 1)
if (typeof(menuItem.childs) !== 'undefined' && menuItem.childs)
+main-nav-sublist-wrapper(2)
each menuItem2 in menuItem.childs
+main-nav-item(selectedID, menuItem2, 2)
if (typeof(menuItem2.childs) !== 'undefined' && menuItem2.childs)
+main-nav-sublist-wrapper(3)
each menuItem3 in menuItem2.childs
+main-nav-item(selectedID, menuItem3, 3)
mixin main-nav-item(selectedID, item, level)
-
var thisItemClass = 'main-nav__item';
var thisLinkClass = 'main-nav__link';
if(selectedID == item.id) {
thisItemClass += ' main-nav__item--active';
}
if(item.childs) {
thisItemClass += ' main-nav__item--has-child';
}
if(level > 1) {
thisItemClass += ' main-nav__item--lvl-' + level;
thisLinkClass += ' main-nav__link--lvl-' + level;
}
li(class= thisItemClass)
a(class= thisLinkClass, href=item.url)= item.name
block
mixin main-nav-sublist-wrapper(level)
-
var divClass = 'main-nav__sublist-wrapper main-nav__sublist-wrapper--lvl-' + level;
var listClass = 'main-nav__list main-nav__list--lvl-' + level;
div(class= divClass)
ul(class= listClass)
block
// В этом файле должны быть стили для БЭМ-блока main-nav, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.main-nav {
$block-name: &; // #{$block-name}__element
position: relative;
z-index: 10;
// первый уровень
&__list {
margin: 0;
padding: 0;
list-style: none;
// мобильный вид списка
@media (max-width: ($menu-desktop-width - 1)) {
// открытие/закрытие меню только в случае работающего JS
.js & {
max-height: 0;
overflow: hidden;
transition: max-height 0.2s;
&--lvl-2 {
max-height: none;
}
}
.js #{$block-name}--open & {
max-height: 1000px;
transition: max-height 0.6s;
&--lvl-2 {
max-height: none;
}
}
}
// настольный вид списка
@media (min-width: $menu-desktop-width) {
display: flex;
flex-wrap: wrap;
&--lvl-2 {
display: block;
}
&--lvl-3 {
display: block;
}
}
}
&__item {
position: relative;
&--active {
& > #{$block-name}__link { // stylelint-disable-line selector-combinator-blacklist
color: #000;
}
}
}
&__link {
position: relative;
display: block;
padding: 0.5em 1em;
text-decoration: none;
background-color: #fff;
&:hover,
&:focus {
z-index: 1;
text-decoration: none;
background-color: $gray-lightest;
}
&--lvl-2 {
min-width: 200px;
@media (max-width: ($menu-desktop-width - 1)) {
padding-left: 2em;
}
}
&--lvl-3 {
min-width: 200px;
@media (max-width: ($menu-desktop-width - 1)) {
padding-left: 3em;
}
}
}
&__sublist-wrapper {
&--lvl-2 {
@media (min-width: $menu-desktop-width) {
position: absolute;
z-index: 1;
top: 100%;
left: 0;
height: 0;
opacity: 0;
overflow: hidden;
transition: opacity $transition-time;
// stylelint-disable
#{$block-name}__item--show-child > &,
#{$block-name}__item:hover & {
height: auto;
overflow: visible;
opacity: 1;
}
// stylelint-enable
}
}
&--lvl-3 {
@media (min-width: $menu-desktop-width) {
position: absolute;
z-index: 1;
top: 0;
left: 100%;
opacity: 0;
height: 0;
overflow: hidden;
transition: opacity $transition-time;
// stylelint-disable
#{$block-name}__item--show-child > &,
#{$block-name}__item--lvl-2:hover & {
height: auto;
overflow: visible;
opacity: 1;
}
// stylelint-enable
}
}
}
// переключатель
&__toggler {
display: inline-block;
border: none;
padding: 0;
background: none;
@media (min-width: $menu-desktop-width) {
display: none;
}
.no-js & {
display: none;
}
}
}
Пункты меню — из `main-nav/main-nav.data.json`.
На малых ширинах преобразуется в мобильный вид, скрывается/показывается по клику на бургер, если JS включен (на `html` есть класс `js`).
/* global document window */
import ready from 'Utils/documentReady.js';
import getScrollSize from 'Utils/getScrollSize.js';
ready(function () {
const bodyPaddingRightOriginal = parseInt(window.getComputedStyle(document.body, null).getPropertyValue('padding-right'));
const backdrop = document.createElement('div');
document.addEventListener('click', function (event) {
const target = event.target.closest('a[data-modal], button[data-modal]');
if (target && target.dataset.modal === 'open') {
showModal( document.getElementById((target.hash || target.dataset.modalTarget).slice(1)) );
}
if (target && target.dataset.modal === 'close' || event.target.matches('[aria-modal]')) {
closeAllModals();
}
function showModal(targetModalNode) {
if ((document.body.clientHeight - document.documentElement.clientHeight) > 0) {
document.body.style.paddingRight = bodyPaddingRightOriginal + getScrollSize() + 'px';
}
document.body.classList.add('modal-open');
targetModalNode.classList.add('modal--show');
targetModalNode.style.display = 'block';
targetModalNode.ariaModal = true;
targetModalNode.ariaHidden = null;
targetModalNode.setAttribute('role', 'dialog');
backdrop.className = 'modal-backdrop';
document.body.append(backdrop);
}
function closeAllModals() {
document.body.classList.remove('modal-open');
document.body.style.paddingRight = '';
document.querySelectorAll('.modal').forEach(function (modal) {
modal.classList.remove('modal--show');
modal.style.display = 'none';
modal.ariaModal = null;
modal.ariaHidden = true;
modal.removeAttribute('role');
});
backdrop.remove();
}
});
});
//- Все примеси в этом файле должны начинаться c имени блока (modal)
mixin modal(id, title, mods)
//- Принимает:
//- id {string} - идентификатор
//- title {string} - заголовок
//- mods {string} - список модификаторов
//- Вызов:
+modal('modal-demo-01', 'Заголовок', 'some')
.modal__body
p С «Шапкой», «подвалом» и внутр. отступами на контенте.
footer.modal__footer
button.btn(type='button', data-modal='close') Закрыть
+modal('modal-demo-02')
p Без «шапки», «подвала» и внутр. отступов на контенте.
Показать модальное окно:
button.btn(type='button', data-modal-target='#modal-demo-01', data-modal='open') Показать
a.btn(href='#modal-demo-01', data-modal='open') Показать
Скрыть все модальные окна:
button(type='button', data-modal='close') Скрыть
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' modal--' + modsList[i].trim();
}
}
section.modal(id=id, class=allMods, tabindex='-1', aria-hidden='true')&attributes(attributes)
.modal__dialog
.modal__content
+close('Закрыть', 'sm')(class='modal__close', data-modal='close')
if title
header.modal__header
h5.modal__title!= title
block
// В этом файле должны быть стили для БЭМ-блока modal, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.modal {
$block-name: &; // #{$block-name}__element
position: fixed;
top: 0;
left: 0;
z-index: 1000;
display: none;
width: 100%;
height: 100%;
overflow: hidden;
outline: 0;
&__dialog {
position: relative;
display: flex;
align-items: center;
width: auto;
max-width: 480px;
min-height: calc(100% - 4em); // центровка, удвоенный margin, указанный чуть ниже
margin: 2em auto;
pointer-events: none;
}
&__content {
position: relative;
display: flex;
flex-direction: column;
width: 100%;
min-height: 40px;
pointer-events: auto;
background-color: $white;
outline: 0;
}
&__header {
position: relative;
flex-shrink: 0;
padding: 16px 40px 16px 16px;
border-bottom: 1px solid $gray-lighten;
}
&__title {
margin: 0;
font-size: $font-size-h4;
font-weight: 400;
}
&__close {
position: absolute;
top: 4px;
right: 4px;
outline: 0;
}
&__body {
position: relative;
flex: 1 1 auto;
padding: 16px;
}
&__footer {
display: flex;
flex-wrap: wrap;
flex-shrink: 0;
align-items: flex-start;
justify-content: flex-end;
padding: 16px;
border-top: 1px solid $gray-lighten;
> * {
margin-left: 8px;
}
}
&--show {
#{$block-name}__dialog {
animation: modal-dialog 0.3s 1 ease-out forwards;
}
}
}
.modal-open {
overflow: hidden;
.modal {
overflow-x: hidden;
overflow-y: auto;
}
}
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
z-index: 999;
width: 100vw;
height: 100vh;
background-color: $black;
animation: modal-backdrop 0.3s 1 ease-out forwards;
}
@keyframes modal-backdrop {
from {
opacity: 0;
}
to {
opacity: 0.6;
}
}
@keyframes modal-dialog {
from {
transform: translate(0, -2em);
}
to {
transform: translate(0, 0);
}
}
Простейшее модальное окно без сторонних зависимостей.
/* global document */
import ready from 'Utils/documentReady.js';
ready(function(){
document.addEventListener('click', function(event) {
if(event.target.dataset.toggle === 'off-canvas') {
event.preventDefault();
offCanvasToggle();
}
// возможность совместить переключение off-canvas и встроенную функ-сть
if(event.target.dataset.toggleNative === 'off-canvas') {
offCanvasToggle();
}
});
function offCanvasToggle() {
document.getElementById('off-canvas').classList.toggle('off-canvas--open');
}
});
// В этом файле должны быть стили для БЭМ-блока off-canvas, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
$off-canvas-width: 250px !default; // ширина этого блока
.off-canvas {
$block-name: &; // #{$block-name}__element
position: relative;
overflow: hidden;
.js &--open {
#{$block-name}__overlay {
position: fixed;
z-index: 1;
opacity: 1;
visibility: visible;
transition: opacity $transition-time ease;
}
#{$block-name}__close {
z-index: 1;
opacity: 1;
visibility: visible;
}
#{$block-name}__aside {
transform: translateX(0);
}
#{$block-name}__page-content {
transform: translateX($off-canvas-width);
}
}
&__aside {
max-height: 30vh;
overflow-y: auto;
backface-visibility: hidden;
background-color: $gray-darkest;
color: #fff;
transition: transform $transition-time ease;
.js & {
position: fixed;
z-index: 2;
width: $off-canvas-width;
height: 100%;
max-height: none;
transform: translateX(($off-canvas-width * -1));
}
}
&__menu {
margin: 0;
padding: 0;
list-style: none;
}
&__menu-item {
&--separator {
padding-bottom: 10px;
border-bottom: 1px solid $gray;
margin-bottom: 10px;
}
}
&__menu-link {
display: block;
padding: 8px 14px;
line-height: 1.2;
color: inherit;
text-decoration: none;
&:hover,
&:focus {
color: inherit;
text-decoration: none;
background-color: $gray-darker;
}
}
&__page-content {
backface-visibility: hidden;
transition: transform $transition-time ease;
}
&__overlay {
position: absolute;
z-index: -1;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.2);
opacity: 0;
visibility: hidden;
}
}
Меню показывается по клику на любой элемент с `data-toggle="off-canvas"` или `data-toggle-native="off-canvas"` (в последнем случае произойдет и&nbsp;действие браузера по умолчанию).
//- Все примеси в этом файле должны начинаться c имени блока (or)
mixin or(text, mods)
//- Принимает:
//- text {string} - текст
//- mods {string} - список модификаторов
//- Вызов:
+or('Текст', 'some-mod')
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' or--' + modsList[i].trim();
}
}
if(typeof(text) === 'undefined')
text = 'или'
.or(class=allMods)&attributes(attributes)
.or__inner!= text
// В этом файле должны быть стили для БЭМ-блока or, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.or {
display: flex;
align-items: center;
&:before,
&:after {
content: '';
flex-grow: 1;
height: 1px;
background-color: $gray-light;
}
&__inner {
padding-left: 0.5rem;
padding-right: 0.5rem;
font-size: $font-size-sm;
color: $gray-light;
}
}
//- Все примеси в этом файле должны начинаться c имени блока (page-footer)
mixin page-footer(mods)
//- Принимает:
//- mods {string} - список модификаторов
//- Вызов:
+page-footer()
p «Подвал»
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' page-footer--' + modsList[i].trim();
}
}
footer.page-footer(class=allMods, role='contentinfo')&attributes(attributes)
.page-footer__inner
block
// В этом файле должны быть стили для БЭМ-блока page-footer, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.page-footer {
$block-name: &; // #{$block-name}__element
}
Обертка и внутренняя обертка «подвала» страницы.
//- Все примеси в этом файле должны начинаться c имени блока (page-header)
mixin page-header(mods)
//- Принимает:
//- mods {string} - список модификаторов
//- Вызов:
+page-header()
p «Шапка»
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' page-header--' + modsList[i].trim();
}
}
header.page-header(class=allMods, role='banner')&attributes(attributes)
.page-header__inner
block
// В этом файле должны быть стили для БЭМ-блока page-header, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.page-header {
$block-name: &; // #{$block-name}__element
}
Обертка и внутренняя обертка «шапки» страницы.
// Стилизация БЭМ-блока page.
// Общие стили тегов.
// Подключение шрифтов.
// Типографика.
// stylelint-disable plugin/selector-bem-pattern
// Сделаем viewport великим снова! (привет, IE10) http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/
@at-root {
@-ms-viewport { // stylelint-disable-line
width: device-width;
}
}
/* stylelint-disable */
// @font-face {
// font-family: 'FONTNAME';
// src: url('../fonts/FONTNAME.woff2') format('woff2'),
// url('../fonts/FONTNAME.woff') format('woff');
// font-weight: 400;
// font-style: normal;
// font-display: swap;
// }
/* stylelint-enable */
.page {
box-sizing: border-box;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-text-size-adjust: 100%; // stylelint-disable-line
min-width: 320px;
min-height: 100%;
&__inner {
display: flex;
flex-direction: column;
min-height: 100vh;
}
&__content {
flex-grow: 1;
}
&__footer-wrapper {
flex-shrink: 0;
}
}
*,
*:before,
*:after {
box-sizing: inherit;
}
* {
-webkit-tap-highlight-color: transparent;
}
body {
display: flex; // Исправляем баг в IE для min-height and flexbox (flex-direction:column)
flex-direction: column; // и прижимаем footer в IE 10-11
min-height: 100%;
margin: 0;
word-break: break-word;
font-family: $font-family;
font-size: $font-size;
line-height: $line-height;
text-align: left;
color: $text-color;
background-color: $body-bg;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
[tabindex='-1']:focus {
outline: none !important; // stylelint-disable-line
}
// Коррекция для кликабельных элементов с указанной ролью в iOS
[role='button'] {
cursor: pointer;
}
// Мы любим Сафари (на самом деле, нет).
[type='search']::-webkit-search-decoration {
-webkit-appearance: none; // stylelint-disable-line
}
// area,
// summary,
a,
button,
[role='button'],
input,
label,
select,
textarea {
touch-action: manipulation; // Стилевая коррекция 300ms задержки (для поддерживающих браузеров)
margin: 0; // Убираем margin в Firefox и Safari
}
button,
input {
overflow: visible; // Покажем торчащее в Edge
text-transform: none; // Уберём наследование text transform в Firefox
}
// Коррекция проблем стилизации для Safari
button,
html [type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button; // stylelint-disable-line
}
button::-moz-focus-inner,
[type='button']::-moz-focus-inner,
[type='reset']::-moz-focus-inner,
[type='submit']::-moz-focus-inner {
padding: 0;
border-style: none;
}
// Коррекция для Mobile Safari (https://github.com/twbs/bootstrap/issues/11266)
input[type='date'],
input[type='time'],
input[type='datetime-local'],
input[type='month'] {
-webkit-appearance: listbox; // stylelint-disable-line
}
textarea {
overflow: auto; // Удаление верт. скролла в IE.
resize: vertical; // Правильное изменение размеров
}
svg {
fill: currentColor;
a &,
button & {
pointer-events: none; // Мы любим Edge, это браузер! (почти)
}
&:not(:root) {
overflow: hidden; // Скрыть выступающее за границы в IE
}
}
img {
max-width: 100%;
height: auto;
}
a {
color: $link-color;
background-color: transparent;
transition: color 0.3s;
&:hover,
&:focus {
color: $link-color--hover;
}
// &:visited {
// color: $link-color--hover;
// }
&[href^='tel:'] {
// На больших экранах телефоны-ссылки делаем обычным текстом
@media (min-width: $screen-xl) {
white-space: nowrap;
pointer-events: none;
text-decoration: inherit;
color: inherit;
}
}
}
hr {
box-sizing: content-box; // Проблема Firefox
height: 0; // Проблема Firefox
margin-top: $typo-margin-vertical * 2;
margin-bottom: $typo-margin-vertical * 2;
border: 0;
border-top: 1px solid $border-color;
overflow: visible; // Проблема Edge и IE
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 1em;
margin-bottom: 0.5em;
font-family: $font-family;
font-weight: 700;
line-height: 1.2;
color: currentColor;
}
h1 { font-size: $font-size-h1; } // stylelint-disable-line
h2 { font-size: $font-size-h2; } // stylelint-disable-line
h3 { font-size: $font-size-h3; } // stylelint-disable-line
h4 { font-size: $font-size-h4; } // stylelint-disable-line
h5 { font-size: $font-size-h5; } // stylelint-disable-line
h6 { font-size: $font-size-h6; } // stylelint-disable-line
p,
ul,
ol,
dl,
table,
blockquote,
pre,
address,
figure {
margin-top: 0;
margin-bottom: $typo-margin-vertical;
}
p {
// http://clagnut.com/blog/2395
hyphens: auto;
hyphenate-limit-chars: 6 3 3;
hyphenate-limit-lines: 2;
hyphenate-limit-last: always;
hyphenate-limit-zone: 8%;
}
address {
font-style: normal;
}
ul,
ol {
margin-bottom: $typo-margin-vertical;
padding-left: 1.5em;
ul,
ol {
margin-top: 0;
margin-bottom: 0;
}
}
li {
line-height: inherit;
}
// dt {
// font-weight: 700;
// }
// dd {
// margin-left: 0;
// @media (min-width: $screen-sm) {
// margin-left: 1.5rem;
// }
// & + dt {
// margin-top: ($line-height / 2);
// }
// }
// .small,
small {
font-size: $font-size-sm;
}
sub,
sup {
font-size: $font-size-sm;
}
// .mark,
// mark {
// background: rgba($color-warning, 0.3);
// padding: 0.1em 0.3em;
// }
b,
strong {
font-weight: bolder;
}
// abbr[title] {
// text-decoration: underline dotted;
// }
blockquote {
padding: 0;
margin-left: 0;
margin-right: 0;
}
// samp,
code,
kbd,
pre {
font-family: $font-family--monospace;
}
code {
padding: 0.06em 0.3em;
color: $text-color;
background-color: rgba(#000, 0.08);
border-radius: $border-radius;
}
kbd {
padding: 0.06em 0.3em;
color: $text-color;
background-color: $gray-lightest;
border-radius: $border-radius;
kbd {
padding: 0;
}
}
pre {
display: block;
width: 100%;
margin-bottom: $typo-margin-vertical;
overflow-x: auto;
tab-size: 2;
background-color: rgba(#000, 0.08);
code {
background: transparent;
}
}
Стили страницы (тег `html` имеет класс `page`), глобальные стили, типографика.
В стилизацию блока включена стилизация «прибитого подвала».
//- Все примеси в этом файле должны начинаться c имени блока (pagination)
mixin pagination(mods)
//- Принимает:
//- mods {string} - список модификаторов
//- Вызов:
+pagination()
+pagination-item('1', '/1')
+pagination-item('...')
+pagination-item('4', '/4')
+pagination-item('5', '/5', true)
+pagination-item('6', '/6')
+pagination-item('...')
+pagination-item('999', '/999')
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' pagination--' + modsList[i].trim();
}
}
.pagination(class=allMods, aria-label='Page navigation')&attributes(attributes)
block
mixin pagination-item(text, href, active, mods)
//- Принимает:
//- text {string} - содержимое пункта
//- href {string} - ссылка этого пункта (если пустая, то это не ссылка, а span)
//- active {bool} - флаг «это активный пункт»
//- mods {string} - список модификаторов
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' pagination__item--' + modsList[i].trim();
}
}
if(typeof(active) !== 'undefined' && active) {
allMods = allMods + ' pagination__item--active';
}
if(typeof(href) !== 'undefined' && href)
a.pagination__item(class=allMods, href=href)&attributes(attributes)!= text
else
span.pagination__item(class=allMods)&attributes(attributes)!= text
// В этом файле должны быть стили для БЭМ-блока pagination, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.pagination {
display: flex;
flex-wrap: wrap;
margin-top: $typo-margin-vertical;
margin-bottom: $typo-margin-vertical;
&__item {
display: block;
min-width: 2em;
// border: 1px solid $border-color;
padding: 0.3em 0.6em;
text-decoration: none;
text-align: center;
color: $text-color;
& + & {
border-left: 0;
}
&:hover,
&:focus {
text-decoration: none;
color: $text-color;
}
@at-root a#{&} {
&:hover,
&:focus {
background-color: $gray-lightest;
}
}
@at-root span#{&} {
cursor: default;
}
&--active {
background-color: $gray-lightest;
}
}
}
//- Все примеси в этом файле должны начинаться c имени блока (progress)
mixin progress(percent, text, mods)
//- Принимает:
//- percent {string} - процент заполнения
//- text {string} - текст на заполненной части (если пуст, текстового узда не будет)
//- mods {string} - список модификаторов
//- Вызов:
+progress('30', '30%', 'success')(aria-valuemin='0', aria-valuemax='100')
+progress('10')(style='height: 10px')
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' progress--' + modsList[i].trim();
}
}
.progress(class=allMods)
.progress__bar(role='progressbar', style='width: ' + percent + '%;', aria-valuenow=percent)&attributes(attributes)
if(typeof(text) !== 'undefined' && text)
span.progress__bartext!= text
// В этом файле должны быть стили для БЭМ-блока progress, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.progress {
margin-top: $typo-margin-vertical;
margin-bottom: $typo-margin-vertical;
border: 1px solid $border-color;
border-radius: $border-radius;
text-align: center;
&__bar {
background-color: $gray-lightest;
border-radius: $border-radius;
max-width: 100%;
min-height: 1px;
white-space: nowrap;
transition: width $transition-time;
}
&__bartext {
display: inline-block;
padding: 0.5em;
}
}
По странице ищутся все ссылки-якоря c атрибутом `data-scroll-link`, навешивается слежение за кликом, по клику — плавный скролл (500 мс, ускорение) к тегу с целевым `id`.
/* global document window performance requestAnimationFrame */
import ready from 'Utils/documentReady.js';
ready(function(){
var links = document.querySelectorAll('[href^="#"][data-scroll-link]');
for (var i = 0; i < links.length; i++) {
links[i].addEventListener('click', function(e) {
var hash = this.href.replace(/[^#]*(.*)/, '$1');
if(hash && hash !== '#') {
e.preventDefault();
var scroll = window.pageYOffset;
var targetTop = getOffsetRect(document.querySelector(hash)).top - 10; // С поправкой в 10px
var scrollDiff = (scroll - targetTop) * -1;
animate({
duration: 500,
timing: function(timeFraction) {
return Math.pow(timeFraction, 4); // https://learn.javascript.ru/js-animation
},
draw: function(progress) {
var scrollNow = scroll + progress * scrollDiff;
window.scrollTo(0,scrollNow);
}
});
}
}, false);
}
function animate(_ref) {
var timing = _ref.timing,
draw = _ref.draw,
duration = _ref.duration;
var start = performance.now();
requestAnimationFrame(function animate(time) {
var timeFraction = (time - start) / duration;
if (timeFraction > 1) timeFraction = 1;
var progress = timing(timeFraction);
draw(progress);
if (timeFraction < 1) {
requestAnimationFrame(animate);
}
});
}
function getOffsetRect(elem) {
var box = elem.getBoundingClientRect()
var body = document.body
var docElem = document.documentElement
var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop
var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft
var clientTop = docElem.clientTop || body.clientTop || 0
var clientLeft = docElem.clientLeft || body.clientLeft || 0
var top = box.top + scrollTop - clientTop
var left = box.left + scrollLeft - clientLeft
return { top: Math.round(top), left: Math.round(left) }
}
});
//- Все примеси в этом файле должны начинаться c имени блока (scroll-link)
mixin scroll-link(text, href, mods)
//- Принимает:
//- text {string} - текст
//- href {string} - значение href
//- mods {string} - список модификаторов
//- Вызов:
+scroll-link('Страница', '#page', 'some-mod')
+scroll-link('Страница', '#page')
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' scroll-link--' + modsList[i].trim();
}
}
a.scroll-link(class=allMods, href=href, data-scroll-link='')&attributes(attributes)!= text
Социальные ссылки. Изображения иконок лучше хранить в SVG-спрайте.
По умолчанию оставлены только стили для facebook, прочие закомментированы. См. `social/social.scss`.
//- Все примеси в этом файле должны начинаться c имени блока (social)
mixin social(mods)
//- Принимает:
//- mods: '' {string} - модификаторы блока
//- Вызов:
+social()
+social-link('https://www.facebook.com/profile.php?id=100011672930358', 'fb')(title='Facebook')
svg(xmlns='http://www.w3.org/2000/svg', width='16', height='16', viewBox='0 0 470.513 470.513')
path(d='M271.521 154.17v-40.541c0-6.086.28-10.8.849-14.13.567-3.335 1.857-6.615 3.859-9.853 1.999-3.236 5.236-5.47 9.706-6.708 4.476-1.24 10.424-1.858 17.85-1.858h40.539V0h-64.809c-37.5 0-64.433 8.897-80.803 26.691-16.368 17.798-24.551 44.014-24.551 78.658v48.82h-48.542v81.086h48.539v235.256h97.362V235.256h64.805l8.566-81.086h-73.37z')
+social-link('https://vk.com/n.gromov', 'vk')(title='ВКонтакте')
svg(xmlns='http://www.w3.org/2000/svg', width='16', height='16', viewBox='0 0 548.358 548.358')
path(d='M545.451 400.298c-.664-1.431-1.283-2.618-1.858-3.569-9.514-17.135-27.695-38.167-54.532-63.102l-.567-.571-.284-.28-.287-.287h-.288c-12.18-11.611-19.893-19.418-23.123-23.415-5.91-7.614-7.234-15.321-4.004-23.13 2.282-5.9 10.854-18.36 25.696-37.397 7.807-10.089 13.99-18.175 18.556-24.267 32.931-43.78 47.208-71.756 42.828-83.939l-1.701-2.847c-1.143-1.714-4.093-3.282-8.846-4.712-4.764-1.427-10.853-1.663-18.278-.712l-82.224.568c-1.332-.472-3.234-.428-5.712.144l-3.713.859-1.431.715-1.136.859c-.952.568-1.999 1.567-3.142 2.995-1.137 1.423-2.088 3.093-2.848 4.996-8.952 23.031-19.13 44.444-30.553 64.238-7.043 11.803-13.511 22.032-19.418 30.693-5.899 8.658-10.848 15.037-14.842 19.126-4 4.093-7.61 7.372-10.852 9.849-3.237 2.478-5.708 3.525-7.419 3.142-1.715-.383-3.33-.763-4.859-1.143-2.663-1.714-4.805-4.045-6.42-6.995-1.622-2.95-2.714-6.663-3.285-11.136-.568-4.476-.904-8.326-1-11.563-.089-3.233-.048-7.806.145-13.706.198-5.903.287-9.897.287-11.991 0-7.234.141-15.085.424-23.555.288-8.47.521-15.181.716-20.125.194-4.949.284-10.185.284-15.705s-.336-9.849-1-12.991a44.442 44.442 0 0 0-2.99-9.137c-1.335-2.95-3.289-5.232-5.853-6.852-2.569-1.618-5.763-2.902-9.564-3.856-10.089-2.283-22.936-3.518-38.547-3.71-35.401-.38-58.148 1.906-68.236 6.855-3.997 2.091-7.614 4.948-10.848 8.562-3.427 4.189-3.905 6.475-1.431 6.851 11.422 1.711 19.508 5.804 24.267 12.275l1.715 3.429c1.334 2.474 2.666 6.854 3.999 13.134 1.331 6.28 2.19 13.227 2.568 20.837.95 13.897.95 25.793 0 35.689-.953 9.9-1.853 17.607-2.712 23.127-.859 5.52-2.143 9.993-3.855 13.418-1.715 3.426-2.856 5.52-3.428 6.28-.571.76-1.047 1.239-1.425 1.427a21.387 21.387 0 0 1-7.71 1.431c-2.667 0-5.901-1.334-9.707-4-3.805-2.666-7.754-6.328-11.847-10.992-4.093-4.665-8.709-11.184-13.85-19.558-5.137-8.374-10.467-18.271-15.987-29.691l-4.567-8.282c-2.855-5.328-6.755-13.086-11.704-23.267-4.952-10.185-9.329-20.037-13.134-29.554-1.521-3.997-3.806-7.04-6.851-9.134l-1.429-.859c-.95-.76-2.475-1.567-4.567-2.427a30.301 30.301 0 0 0-6.567-1.854l-78.229.568c-7.994 0-13.418 1.811-16.274 5.428l-1.143 1.711c-.571.953-.859 2.475-.859 4.57 0 2.094.571 4.664 1.714 7.707 11.42 26.84 23.839 52.725 37.257 77.659 13.418 24.934 25.078 45.019 34.973 60.237 9.897 15.229 19.985 29.602 30.264 43.112 10.279 13.515 17.083 22.176 20.412 25.981 3.333 3.812 5.951 6.662 7.854 8.565l7.139 6.851c4.568 4.569 11.276 10.041 20.127 16.416 8.853 6.379 18.654 12.659 29.408 18.85 10.756 6.181 23.269 11.225 37.546 15.126 14.275 3.905 28.169 5.472 41.684 4.716h32.834c6.659-.575 11.704-2.669 15.133-6.283l1.136-1.431c.764-1.136 1.479-2.901 2.139-5.276.668-2.379 1-5 1-7.851-.195-8.183.428-15.558 1.852-22.124 1.423-6.564 3.045-11.513 4.859-14.846 1.813-3.33 3.859-6.14 6.136-8.418 2.282-2.283 3.908-3.666 4.862-4.142.948-.479 1.705-.804 2.276-.999 4.568-1.522 9.944-.048 16.136 4.429 6.187 4.473 11.99 9.996 17.418 16.56 5.425 6.57 11.943 13.941 19.555 22.124 7.617 8.186 14.277 14.271 19.985 18.274l5.708 3.426c3.812 2.286 8.761 4.38 14.853 6.283 6.081 1.902 11.409 2.378 15.984 1.427l73.087-1.14c7.229 0 12.854-1.197 16.844-3.572 3.998-2.379 6.373-5 7.139-7.851.764-2.854.805-6.092.145-9.712-.677-3.611-1.344-6.136-2.008-7.563z')
+social-link('https://github.com/nicothin', 'gh')(title='Github')
svg(xmlns='http://www.w3.org/2000/svg', width='16', height='16', viewBox='0 0 475.084 475.084')
path(d='M436.244 146.752c5.14-15.422 7.713-31.409 7.713-47.967 0-22.08-4.859-42.828-14.564-62.242-20.362 0-38.349 3.715-53.961 11.136-15.604 7.423-33.4 18.938-53.379 34.545-25.122-6.09-51.777-9.135-79.941-9.135-30.837 0-60.245 3.333-88.223 9.994-20.364-15.99-38.351-27.74-53.959-35.26-15.608-7.52-33.689-11.279-54.247-11.279-9.707 19.414-14.56 40.163-14.56 62.242 0 16.751 2.568 32.93 7.708 48.535C12.942 177.587 0 215.272 0 260.383c0 39.595 5.898 71.092 17.701 94.507 6.283 12.367 14.465 23.312 24.554 32.832 10.085 9.514 21.601 17.228 34.545 23.13 12.946 5.896 25.981 10.801 39.116 14.699 13.134 3.9 27.646 6.758 43.54 8.559 15.893 1.816 29.93 3.004 42.111 3.579 12.181.564 25.693.853 40.544.853 17.508 0 33.396-.432 47.678-1.283 14.277-.855 30.594-2.953 48.964-6.276 18.367-3.333 34.547-7.857 48.54-13.565 13.99-5.708 27.412-13.895 40.259-24.551 12.847-10.663 22.884-23.318 30.121-37.976 11.604-23.603 17.412-55.107 17.412-94.507-.007-45.302-12.95-83.178-38.841-113.632zm-34.249 207.703c-6.092 12.471-13.802 22.265-23.127 29.41-9.329 7.139-20.938 12.847-34.831 17.135-13.9 4.281-27.217 7.087-39.971 8.415-12.758 1.334-26.933 1.998-42.545 1.998h-47.966c-15.607 0-29.79-.664-42.541-1.998-12.752-1.328-26.075-4.134-39.971-8.415-13.891-4.288-25.5-9.996-34.829-17.135-9.329-7.146-17.037-16.939-23.128-29.41-6.09-12.471-9.136-27.076-9.136-43.824 0-22.847 6.567-42.264 19.702-58.245 13.134-15.99 30.929-23.982 53.387-23.982 8.188 0 26.746 1.997 55.677 5.995 13.513 2.093 28.456 3.14 44.823 3.14 16.372 0 31.313-1.044 44.824-3.14 29.317-3.999 47.869-5.995 55.678-5.995 22.457 0 40.252 7.996 53.386 23.982 13.135 15.988 19.698 35.398 19.698 58.245 0 16.751-3.046 31.364-9.13 43.824z')
path(d='M166.875 265.52c-5.806-6.475-12.703-9.712-20.699-9.712-7.998 0-14.896 3.241-20.701 9.712-5.802 6.468-9.897 13.703-12.275 21.689-2.383 8.002-3.571 15.804-3.571 23.422 0 7.61 1.191 15.413 3.571 23.414 2.375 7.991 6.468 15.222 12.275 21.689 5.808 6.475 12.703 9.713 20.701 9.713 7.996 0 14.896-3.244 20.699-9.713 5.804-6.468 9.897-13.698 12.275-21.689 2.38-8.001 3.571-15.804 3.571-23.414 0-7.611-1.188-15.42-3.571-23.422-2.379-7.983-6.468-15.215-12.275-21.689zM349.601 265.52c-5.804-6.475-12.703-9.712-20.697-9.712-7.991 0-14.894 3.241-20.701 9.712-5.804 6.468-9.896 13.703-12.271 21.689-2.385 8.002-3.576 15.804-3.576 23.422 0 7.61 1.191 15.413 3.576 23.414 2.375 7.991 6.468 15.222 12.271 21.689 5.808 6.475 12.71 9.713 20.701 9.713 7.994 0 14.894-3.244 20.697-9.713 5.801-6.468 9.896-13.698 12.278-21.689 2.379-8.001 3.569-15.804 3.569-23.414 0-7.611-1.19-15.42-3.569-23.422-2.381-7.983-6.477-15.215-12.278-21.689z')
+social-link('https://twitter.com/nicothin', 'tw')(title='twitter')
svg(xmlns='http://www.w3.org/2000/svg', width='16', height='16', viewBox='0 0 449.956 449.956')
path(d='M449.956 85.657c-17.702 7.614-35.408 12.369-53.102 14.279 19.985-11.991 33.503-28.931 40.546-50.819-18.281 10.847-37.787 18.268-58.532 22.267-18.274-19.414-40.73-29.125-67.383-29.125-25.502 0-47.246 8.992-65.24 26.98-17.984 17.987-26.977 39.731-26.977 65.235 0 6.851.76 13.896 2.284 21.128-37.688-1.903-73.042-11.372-106.068-28.407C82.46 110.158 54.433 87.46 31.403 59.101c-8.375 14.272-12.564 29.787-12.564 46.536 0 15.798 3.711 30.456 11.138 43.97 7.422 13.512 17.417 24.455 29.98 32.831-14.849-.572-28.743-4.475-41.684-11.708v1.142c0 22.271 6.995 41.824 20.983 58.674 13.99 16.848 31.645 27.453 52.961 31.833a95.543 95.543 0 0 1-24.269 3.138c-5.33 0-11.136-.475-17.416-1.42 5.9 18.459 16.75 33.633 32.546 45.535 15.799 11.896 33.691 18.028 53.677 18.418-33.498 26.262-71.66 39.393-114.486 39.393-8.186 0-15.607-.373-22.27-1.139 42.827 27.596 90.03 41.394 141.612 41.394 32.738 0 63.478-5.181 92.21-15.557 28.746-10.369 53.297-24.267 73.665-41.686 20.362-17.415 37.925-37.448 52.674-60.097 14.75-22.651 25.738-46.298 32.977-70.946 7.23-24.653 10.848-49.344 10.848-74.092 0-5.33-.096-9.325-.287-11.991 18.087-13.127 33.504-29.023 46.258-47.672z')
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' social--' + modsList[i].trim();
}
}
ul.social(class=allMods)&attributes(attributes)
block
mixin social-link(link, mods)
//- Принимает:
//- link {string} - ссылка
//- mods {string} - список модификаторов
//- Вызов:
+social-link('https://vk.com/n.gromov', 'some-mod')
span ВКонтакте
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' social__link-wrap--' + modsList[i].trim();
}
}
li.social__link-wrap(class=allMods)&attributes(attributes)
a.social__link(href=link)
block
// В этом файле должны быть стили для БЭМ-блока social, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.social {
$block-name: &; // #{$block-name}__
display: flex;
flex-wrap: wrap;
align-items: center;
margin-top: $typo-margin-vertical;
margin-bottom: $typo-margin-vertical;
padding: 0;
list-style: none;
&__link-wrap {
margin-right: 10px;
}
&__link {
display: block;
padding: 5px;
background-color: $gray;
#{$block-name}__link-wrap--fb & {
background-color: $color-fb;
}
// #{$block-name}__link-wrap--vk & {
// background-color: $color-vk;
// }
// #{$block-name}__link-wrap--tw & {
// background-color: $color-tw;
// }
// #{$block-name}__link-wrap--gh & {
// background-color: $color-gh;
// }
svg {
display: block;
width: 16px;
height: 16px;
fill: #fff;
}
}
&__text {
@include hide-text;
}
}
Из файлов `sprite-png/png/` генерируется файл спрайта `sprite-png/img/sprite-ЧИСЛОВОЙ_ИНДЕКС.svg`.
<p class="alert alert--warning">Блок не имеет классов, упоминаемых в разметке. Чтобы взять его в сборку, упомяните <code>sprite-png</code> в <code>config.js#alwaysAddBlocks</code>.</p>
**Стилевой файл блока генерируется автоматически** и содержит примеси для использования спрайтов.
Пример использования для конкретного селектора:
<pre class="code">
<code>.selector {</code>
<code> @include sprite($icon-left); // $icon-left — $ИМЯ-ФАЙЛА-КАРТИНКИ</code>
<code>}</code>
</pre>
Из файлов `sprite-svg/svg/` генерируется файл спрайта `sprite-svg/img/sprite.svg`.
<p class="alert alert--warning">Блок не имеет классов, упоминаемых в разметке. Чтобы взять его в сборку, упомяните <code>sprite-svg</code> в <code>config.js#alwaysAddBlocks</code>.</p>
Для вставки на страницу используйте <code>svg &gt; use</code> со ссылками на <code>id</code> символа:
<pre class="code">
<code>svg(width='32', height='32')</code>
<code> use(xlink:href='img/sprite.svg#icon-boo')</code>
</pre>
<p class="alert alert--warning">При использовании блока в проекте в сборку берётся <a href="https://www.npmjs.com/package/svg4everybody">svg4everybody</a>.</p>
const svg4everybody = require('svg4everybody');
svg4everybody();
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 31.494 31.494">
<path d="M10.273 5.01c.444-.445 1.143-.445 1.587 0 .43.428.43 1.142 0 1.57l-8.047 8.047h26.554c.62 0 1.127.492 1.127 1.11 0 .62-.508 1.128-1.127 1.128H3.813l8.047 8.032c.43.444.43 1.16 0 1.587-.444.444-1.143.444-1.587 0L.32 16.532c-.428-.43-.428-1.143 0-1.57l9.953-9.953z" fill="#FFFFFF"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 31.49 31.49">
<path d="M21.205 5.007c-.43-.444-1.143-.444-1.587 0-.43.43-.43 1.143 0 1.57l8.047 8.048H1.11c-.618 0-1.11.493-1.11 1.112 0 .62.492 1.127 1.11 1.127h26.555l-8.047 8.032c-.43.444-.43 1.16 0 1.587.444.444 1.16.444 1.587 0l9.952-9.952c.444-.426.444-1.14 0-1.57l-9.952-9.95z" fill="#1E201D"/>
</svg>
<p class="alert alert--warning">Требует добавления атрибутов <code>data-label</code> для каждой ячейки, написания <code>thead</code> и <code>tbody</code>, хорошо работает только при горизонтальном расположении заголовочных ячеек.</p>
//- Все примеси в этом файле должны начинаться c имени блока (table-responsive)
mixin table-responsive(mods)
//- Принимает:
//- mods {string} - список модификаторов
//- Вызов:
+table-responsive()
thead
tr
th Имя и фамилия
th Тип мироощущения
tbody
tr
td(data-label='Имя и фамилия') Анатоле Вассерман
td(data-label='Тип мироощущения') Девственность
tr
td(data-label='Имя и фамилия') Джакомо Казанова
td(data-label='Тип мироощущения') Небольшая распущенность с лёгкой примесью леденящих душу извращений, чудовищной лжи и имитатора известного органа, обитого телячьей кожей.
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' table-responsive--' + modsList[i].trim();
}
}
table.table-responsive(class=allMods)&attributes(attributes)
block
// В этом файле должны быть стили для БЭМ-блока table-responsive, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, $media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.table-responsive {
width: 100%;
margin-top: $typo-margin-vertical;
margin-bottom: $typo-margin-vertical;
border-collapse: collapse;
border: 1px solid $border-color;
caption {
caption-side: top;
text-align: left;
border: 1px solid $border-color;
border-bottom: 0;
padding: 0.4em $field-padding-horizontal;
font-size: $font-size-sm;
line-height: 1em;
color: $text-color-muted;
@media (max-width: ($screen-md - 1)) {
display: block;
margin-top: 1em;
margin-bottom: 0.5em;
border: 0;
padding: 0;
font-family: $font-family;
font-size: $font-size-h3;
font-weight: 700;
line-height: 1.2;
color: currentColor;
}
}
td,
th {
padding: $field-padding-vertical $field-padding-horizontal;
vertical-align: top;
border-bottom: 1px solid $border-color;
&:not(:first-child) {
border-left: 1px solid $border-color;
}
}
th {
text-align: inherit;
}
@media (max-width: ($screen-md - 1)) {
display: block;
border: 0;
thead {
display: none;
}
tbody {
display: block;
width: 100%;
tr {
display: block;
border: 1px solid $border-color;
border-bottom: 0;
&:not(:last-child) { // stylelint-disable-line max-nesting-depth
margin-bottom: 1em * $line-height;
}
}
td {
display: block;
width: 100%;
border-right: 0 !important; // stylelint-disable-line declaration-no-important
border-left: 0 !important; // stylelint-disable-line declaration-no-important
&:before { // stylelint-disable-line max-nesting-depth
content: attr(data-label);
display: block;
font-weight: 700;
}
}
}
}
}
//- Все примеси в этом файле должны начинаться c имени блока (table)
mixin table(mods)
//- Принимает:
//- mods {string} - список модификаторов
//- Вызов:
+table()
tr
th Имя
th Фамилия
th Мыло
tr
td Иннокентий
td Иванов
td Хозяйственное
tr
td Васисуалий
td Римский-Корсаков
td Душистое
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' table--' + modsList[i].trim();
}
}
table.table(class=allMods)&attributes(attributes)
block
// В этом файле должны быть стили для БЭМ-блока table, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.table {
display: table;
width: 100%;
margin-top: $typo-margin-vertical;
margin-bottom: $typo-margin-vertical;
border: 1px solid $border-color;
border-collapse: collapse;
td,
th {
padding: $field-padding-vertical $field-padding-horizontal;
vertical-align: top;
border-bottom: 1px solid $border-color;
&:not(:first-child) {
border-left: 1px solid $border-color;
}
}
th {
text-align: inherit;
}
// caption {
// caption-side: top;
// text-align: left;
// padding: 0.4rem $field-padding-horizontal;
// border-bottom: 1px solid $border-color;
// font-size: $font-size-sm;
// line-height: 1em;
// color: $text-color-muted;
// }
}
/* global document window history location */
document.addEventListener('DOMContentLoaded', function(){
if(location.hash) {
showTab(location.hash);
}
// Следим за поднимающимися кликами
document.addEventListener('click', function(event) {
if(event.target.dataset.toggle === 'tab') {
event.preventDefault();
var target = event.target.hash === undefined ? event.target.dataset.target : event.target.hash;
if ( target !== undefined ) {
showTab(target);
if(history && history.pushState && history.replaceState) {
var stateObject = {'url' : target};
if (window.location.hash && stateObject.url !== window.location.hash) {
window.history.pushState(stateObject, document.title, window.location.pathname + target);
} else {
window.history.replaceState(stateObject, document.title, window.location.pathname + target);
}
}
}
}
});
/**
* Показывает таб
* @param {string} tabId ID таба, который нужно показать
*/
function showTab(tabId){
var element = document.querySelector(tabId);
if ( element && element.classList.contains('tabs__content-item') ) {
var tabsParent = document.querySelector(tabId).closest('.tabs');
var activeTabClassName = 'tabs__link-wrap--active';
var activeTabContentClassName = 'tabs__content-item--active';
// таб
tabsParent.querySelectorAll('.'+activeTabClassName).forEach(function(item){
item.classList.remove(activeTabClassName);
});
var activeTab = tabsParent.querySelector('[href="'+tabId+'"]') ? tabsParent.querySelector('[href="'+tabId+'"]') : tabsParent.querySelector('[data-target="'+tabId+'"]')
activeTab.closest('.tabs__link-wrap').classList.add(activeTabClassName);
// контент таба
tabsParent.querySelectorAll('.'+activeTabContentClassName).forEach(function(item){
item.classList.remove(activeTabContentClassName);
});
tabsParent.querySelector(tabId).classList.add(activeTabContentClassName);
}
}
});
//- Все примеси в этом файле должны начинаться c имени блока (tabs)
mixin tabs(props, mods)
//- Принимает:
//- props {array}
//- {object}
//- id {string} - id вкладки
//- text {string} - текст на вкладке
//- isActive {bool} - флаг «это активная»
//- mods {string} - список модификаторов
//- Вызов:
+tabs([
{
'id': 'tab00',
'text': 'Вкладка ноль',
'isActive': true,
},
{
'id': 'tab01',
'text': 'Вкладка один',
},
], 'some-mod')
+tabs-item('tab00', true, 'some')
p Далеко-далеко за словесными горами в стране, гласных и согласных живут рыбные тексты.0
+tabs-item('tab01')
p За словесными горами в стране, гласных и согласных живут рыбные тексты.1
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' tabs--' + modsList[i].trim();
}
}
.tabs(class=allMods)&attributes(attributes)
ul.tabs__links(role='tablist')
each tab in props
-
var isActive = (typeof(tab.isActive) !== 'undefined' && tab.isActive) ? ' tabs__link-wrap--active' : ''
li.tabs__link-wrap(class=isActive, role='presentation')
a.tabs__link(href='#'+tab.id, data-toggle='tab', role='tab')!= tab.text
.tabs__content-wrapper
block
mixin tabs-item(id, isActive, mods)
//- Принимает:
//- isActive {bool} - флаг «это активная»
//- mods {string} - список модификаторов
//- Вызов:
+tabs-item('tab01', true, 'some-mod')
p Вкладка
+tabs-item('tab02', false)
p Вкладка
+tabs-item('tab03', false)
p Вкладка
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' tabs__content-item--' + modsList[i].trim();
}
}
if(typeof(isActive) !== 'undefined' && isActive)
allMods = allMods + ' tabs__content-item--active';
.tabs__content-item(class=allMods, id=id, role='tabpanel')&attributes(attributes)
block
// В этом файле должны быть стили для БЭМ-блока tabs, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, @media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
.tabs {
$block-name: &; // #{$block-name}__element
margin-top: $typo-margin-vertical;
margin-bottom: $typo-margin-vertical;
&__links {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
margin: 0;
padding: 0;
list-style: none;
&:after {
content: '';
display: none;
flex-grow: 1;
height: 1px;
background-color: $border-color;
@media (min-width: $screen-md) {
display: block;
}
}
.no-js & {
display: none;
}
}
&__link-wrap {
position: relative;
margin: 0 0.3em 0.3em 0;
border: 1px solid $border-color;
color: $text-color-muted;
@media (min-width: $screen-md) {
margin: 0;
&:not(:last-child) {
border-right: none;
}
}
&--active {
color: $text-color;
@media (min-width: $screen-md) {
border-bottom: 1px solid transparent;
}
#{$block-name}__link {
background-color: #fff;
}
}
}
&__link {
display: block;
padding: $field-padding-vertical $field-padding-horizontal;
color: currentColor;
text-decoration: none;
background-color: $gray-lightest;
&:hover,
&:focus {
color: currentColor;
}
}
&__content-wrapper {
display: block;
border: 1px solid $border-color;
padding: $field-padding-vertical $field-padding-horizontal;
@media (min-width: $screen-md) {
border-top: none;
}
}
&__content-item {
.js & {
display: none;
}
& > *:last-child { // stylelint-disable-line selector-combinator-blacklist
margin-bottom: 0;
}
&--active {
.js & {
display: block;
}
}
}
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17 11" width="17" height="11"><path fill="#fff" d="M.202 1.97L1.956.204A.61.61 0 0 1 2.43 0a.61.61 0 0 1 .47.204L8.5 5.84 14.1.204A.61.61 0 0 1 14.57 0a.61.61 0 0 1 .474.204l1.754 1.766a.618.618 0 0 1 .202.473.62.62 0 0 1-.202.476l-7.826 7.875A.61.61 0 0 1 8.5 11a.61.61 0 0 1-.472-.204L.202 2.92A.618.618 0 0 1 0 2.444c0-.203.067-.34.202-.475z"/></svg>
Ссылка «вверх» в нижнем правом углу страницы. Видима только если скролл больше указанного в Javascript-файле блока значения.
/* global document window performance requestAnimationFrame */
import ready from 'Utils/documentReady.js';
ready(function(){
if(document.getElementById('to-top')) {
document.getElementById('to-top').addEventListener('click', function(e) {
e.preventDefault();
var scroll = window.pageYOffset;
var targetTop = 0;
var scrollDiff = (scroll - targetTop) * -1;
animate({
duration: 500,
timing: function(timeFraction) {
return Math.pow(timeFraction, 4); // https://learn.javascript.ru/js-animation
},
draw: function(progress) {
var scrollNow = scroll + progress * scrollDiff;
window.scrollTo(0,scrollNow);
}
});
}, false);
window.addEventListener('scroll', visibilityToggle);
visibilityToggle();
}
function visibilityToggle() {
if(window.pageYOffset >= 500) {
document.getElementById('to-top').classList.add('to-top--visible');
}
else {
document.getElementById('to-top').classList.remove('to-top--visible');
}
}
function animate(_ref) {
var timing = _ref.timing,
draw = _ref.draw,
duration = _ref.duration;
var start = performance.now();
requestAnimationFrame(function animate(time) {
var timeFraction = (time - start) / duration;
if (timeFraction > 1) timeFraction = 1;
var progress = timing(timeFraction);
draw(progress);
if (timeFraction < 1) {
requestAnimationFrame(animate);
}
});
}
});
//- Все примеси в этом файле должны начинаться c имени блока (to-top)
mixin to-top(mods)
//- Принимает:
//- mods {string} - список модификаторов
//- Вызов:
+to-top()
-
// список модификаторов
var allMods = '';
if(typeof(mods) !== 'undefined' && mods) {
var modsList = mods.split(',');
for (var i = 0; i < modsList.length; i++) {
allMods = allMods + ' to-top--' + modsList[i].trim();
}
}
a.to-top#to-top(class=allMods, href='#')&attributes(attributes) ↑
// В этом файле должны быть стили для БЭМ-блока to-top, его элементов,
// модификаторов, псевдоселекторов, псевдоэлементов, $media-условий...
// Очередность: http://nicothin.github.io/idiomatic-pre-CSS/#priority
$transition-time: 0.3s !default;
.to-top {
$block-name: &; // #{$block-name}__element
position: fixed;
z-index: 99;
right: 1rem;
bottom: 1rem;
width: 3rem;
height: 3rem;
border-radius: 50%;
background-color: #000;
color: transparent;
font: 0/0 a; // stylelint-disable-line
// см. bg-img/to-up.svg
// http://yoksel.github.io/url-encoder/ru/
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 17 11' width='17' height='11'%3E%3Cpath fill='%23fff' d='M.202 1.97L1.956.204A.61.61 0 0 1 2.43 0a.61.61 0 0 1 .47.204L8.5 5.84 14.1.204A.61.61 0 0 1 14.57 0a.61.61 0 0 1 .474.204l1.754 1.766a.618.618 0 0 1 .202.473.62.62 0 0 1-.202.476l-7.826 7.875A.61.61 0 0 1 8.5 11a.61.61 0 0 1-.472-.204L.202 2.92A.618.618 0 0 1 0 2.444c0-.203.067-.34.202-.475z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
transform: rotate(180deg);
transition: opacity $transition-time;
.js & {
opacity: 0;
visibility: hidden;
&--visible {
opacity: 1;
visibility: visible;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/img/favicon/mstile-150x150.png"/>
<TileColor>#ffffff</TileColor>
</tile>
</msapplication>
</browserconfig>
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="260.000000pt" height="260.000000pt" viewBox="0 0 260.000000 260.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,260.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M0 2544 l0 -55 315 -315 315 -315 0 -235 0 -234 -315 0 -315 0 0 -85
0 -85 315 0 315 0 0 -235 0 -235 -315 -315 -315 -315 0 -60 0 -60 60 0 60 0
315 315 315 315 235 0 235 0 0 -315 0 -315 85 0 85 0 0 315 0 315 234 0 235 0
315 -315 314 -315 56 0 56 0 0 66 0 66 -310 309 -310 310 0 234 0 235 310 0
310 0 0 85 0 85 -310 0 -310 0 0 235 0 235 310 310 310 310 0 60 0 60 -60 0
-60 0 -310 -310 -310 -310 -235 0 -235 0 0 310 0 310 -85 0 -85 0 0 -310 0
-310 -234 0 -235 0 -310 310 -310 310 -65 0 -66 0 0 -56z"/>
</g>
</svg>
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/img/favicon/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/img/favicon/android-chrome-256x256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}
# Шрифты
Исходные файлы шрифтов. Упомяните их в `config.js` для копирования в папку сборки.
Пример секции в `config.js`:
```js
"addAssets": {
"./src/fonts/open-sans-regular.woff2": "fonts/",
"./src/fonts/open-sans-regular.woff": "fonts/",
},
```
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="15"><path d="M10.525 8.945q0 1.819-1.546 2.96-1.538 1.132-4.183 1.132-1.53 0-2.671-.264Q.992 12.5 0 12.086V9.135h.347q.984.785 2.2 1.207 1.223.422 2.348.422.29 0 .76-.05.472-.05.77-.165.363-.15.595-.372.24-.224.24-.662 0-.405-.348-.694-.339-.298-1-.455Q5.217 8.2 4.44 8.06q-.769-.149-1.447-.372Q1.44 7.183.753 6.324q-.679-.869-.679-2.15 0-1.72 1.538-2.803Q3.158.279 5.582.279q1.214 0 2.397.24 1.19.232 2.059.587v2.836h-.34q-.744-.595-1.827-.992-1.075-.405-2.199-.405-.397 0-.794.058-.388.05-.752.198-.323.124-.554.38-.232.249-.232.571 0 .488.372.753.373.256 1.406.47.678.141 1.298.274.629.132 1.348.363 1.414.463 2.083 1.266.678.793.678 2.067zM23.87.503l-4.49 12.311h-3.555L11.335.503h3.333l2.976 8.648L20.621.503zm12.659 11.608q-.827.323-2.191.645-1.364.314-2.72.314-3.142 0-4.92-1.703-1.778-1.711-1.778-4.721 0-2.87 1.795-4.622Q28.509.263 31.717.263q1.215 0 2.315.223 1.1.215 2.447.868V4.24h-.355q-.232-.174-.678-.488-.447-.322-.86-.546-.48-.264-1.124-.454-.637-.19-1.356-.19-.844 0-1.53.248t-1.232.76q-.521.496-.827 1.265-.298.76-.298 1.761 0 2.043 1.084 3.126 1.083 1.083 3.2 1.083.181 0 .396-.008.224-.008.405-.025V8.358H30.85V6.034h5.68zm6.924.703h-.972v-8.04h.972zm6.811-2.884q0 1.411-.724 2.228-.723.816-1.938.816-1.224 0-1.948-.816-.718-.817-.718-2.227 0-1.411.718-2.228.724-.821 1.948-.821 1.215 0 1.938.821.724.817.724 2.228zm-1.003 0q0-1.12-.44-1.663-.438-.548-1.219-.548-.79 0-1.23.548-.434.542-.434 1.664 0 1.085.44 1.648.439.558 1.224.558.775 0 1.215-.553.444-.558.444-1.653zm7.193 2.228q0 1.467-.666 2.155-.667.687-2.052.687-.46 0-.899-.067-.434-.062-.858-.181v-.992h.052q.238.093.754.227.517.14 1.034.14.496 0 .822-.12.325-.118.506-.33.18-.201.258-.486.078-.284.078-.635v-.527q-.44.351-.842.527-.398.17-1.018.17-1.034 0-1.644-.744-.604-.75-.604-2.108 0-.744.206-1.282.212-.542.574-.935.336-.367.816-.569.481-.206.956-.206.502 0 .838.103.34.098.718.305l.062-.248h.91zm-.971-.93V8.08q-.388-.176-.724-.249-.33-.077-.661-.077-.801 0-1.26.537-.46.538-.46 1.561 0 .972.34 1.473.341.501 1.132.501.424 0 .847-.16.43-.166.786-.44zm7.803-1.298q0 1.411-.723 2.228-.724.816-1.938.816-1.225 0-1.948-.816-.719-.817-.719-2.227 0-1.411.719-2.228.723-.821 1.948-.821 1.214 0 1.938.821.723.817.723 2.228zm-1.002 0q0-1.12-.44-1.663-.439-.548-1.22-.548-.79 0-1.23.548-.433.542-.433 1.664 0 1.085.439 1.648.44.558 1.225.558.775 0 1.214-.553.445-.558.445-1.653z"/></svg>
\ No newline at end of file
// Убрать на html класс "no-js", добавить класс "js": с уровня стилей знаем работает ли JS
document.documentElement.className = document.documentElement.className.replace('no-js', 'js');
// Браузер и ОС
function cth(c){document.documentElement.classList.add(c)}
'ontouchstart' in window?cth('touch'):cth('no-touch');
if(typeof InstallTrigger!=='undefined')cth('firefox');
if(/constructor/i.test(window.HTMLElement)||(function(p){return p.toString()==="[object SafariRemoteNotification]"})(!window['safari']||(typeof safari!=='undefined'&&safari.pushNotification)))cth('safari');
if(/*@cc_on!@*/false||!!document.documentMode)cth('ie');
if(!(/*@cc_on!@*/false||!!document.documentMode)&&!!window.StyleMedia)cth('edge');
if(!!window.chrome&&(!!window.chrome.webstore||!!window.chrome.runtime))cth('chrome');
if(~navigator.appVersion.indexOf("Win"))cth('windows');
if(~navigator.appVersion.indexOf("Mac"))cth('osx');
if(~['iPad','iPhone','iPod'].indexOf(navigator.platform))cth('ios');
if(~navigator.appVersion.indexOf("Linux"))cth('linux');
// Добавление 1vh (использование: height: 100vh; height: calc(var(--vh, 1vh) * 100);) для фикса 100vh на мобилках
let vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
window.addEventListener('resize', () => {
let vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
});
// /* global document console */
// import ready from 'Utils/documentReady.js';
// import getScrollSize from 'Utils/getScrollSize.js';
// ready(function() {
// console.log('DOM героически построен!');
// // Добавление кастомного свойства с системной шириной скролла
// document.documentElement.style.setProperty('--css-scroll-size', `${getScrollSize()}px`);
// });
// import $ from 'jquery'; // Перед использованием установить как зависимость
// $(function() {
// console.log('jQuery героически сработал!');
// });
/* global document */
const ready = function (fn) {
if (document.attachEvent ? document.readyState === "complete" : document.readyState !== "loading"){
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
export default ready;
/* global document */
const getScrollSize = function () {
const outer = document.createElement('div');
const inner = document.createElement('div');
outer.style.overflow = 'scroll';
outer.classList.add('scrollbar');
document.body.appendChild(outer);
outer.appendChild(inner);
const scrollbarSize = outer.offsetWidth - inner.offsetWidth;
document.body.removeChild(outer);
return scrollbarSize;
}
export default getScrollSize;
extends ../pug/layout.pug
block meta
title Error 404
meta(name='description', content='')
//- block append head
//- link(rel='stylesheet', href='css/some.css')
block nav
+main-nav('-1')
block content
h1 Error 404
p Page not found.
//- block footer
//- p footer
//- block append page-bottom
//- script(src='js/some.js')
extends ../pug/layout-block-lib.pug
block meta
title Библиотека
meta(name='description', content='')
block append head
style.
.components-demo {
display: block;
padding: 1em;
margin: auto;
max-width: 1200px;
font-family: sans-serif;
}
.off-canvas__aside {
padding-top: 1em;
padding-bottom: 1em;
}
script.
document.addEventListener('DOMContentLoaded', function(){
// наполнение бокого меню
var demoBlocks = document.querySelectorAll('.blocks-library__item');
var offCanvasMenu = '<li class="off-canvas__menu-item"><a class="off-canvas__menu-link" data-toggle-native="off-canvas" href="/">Главная страница</a></li><li class="off-canvas__menu-item"><a class="off-canvas__menu-link" data-toggle-native="off-canvas" href="https://github.com/nicothin/NTH-start-project">Исходный репозиторий</a></li><li class="off-canvas__menu-item off-canvas__menu-item--separator"><a class="off-canvas__menu-link" data-toggle-native="off-canvas" href="http://nicothin.github.io/NTH-start-project/blocks-demo.html">Демка на github pages</a></li>';
for (var i=0; i<demoBlocks.length; i++){
var classes = 'off-canvas__menu-item';
var block = demoBlocks[i];
if (block.classList.contains('blocks-library__item--menusep')) { classes += ' off-canvas__menu-item--separator'; }
offCanvasMenu += '<li class="'+classes+'"><a class="off-canvas__menu-link" data-toggle-native="off-canvas" href="#'+block.getAttribute('id')+'" data-scroll-link>'+block.querySelector('.blocks-library__item-title').textContent+'</a></li>';
}
var offCanvasMenuContent = document.getElementById('off-canvas-menu');
offCanvasMenuContent.innerHTML = offCanvasMenu;
// просмотр примера кода для блока
var codeShowTriggers = document.querySelectorAll('.blocks-library__code-show-trigger');
for (var i = 0; i < codeShowTriggers.length; i++) {
codeShowTriggers[i].addEventListener('click', function(){
this.closest('.blocks-library__code-wrapper').querySelector('.blocks-library__code').classList.toggle('blocks-library__code--shown');
});
}
});
//- script(src='js/SOME.js')
block off-canvas
ul.off-canvas__menu#off-canvas-menu
block header
block content
//- Спрайт с картинкой папки (картинки перед заголовками с названием блоков)
svg(xmlns='http://www.w3.org/2000/svg', style='display:none')
symbol#folder(viewbox='0 0 60 60')
path(d='M56.98 11.5H28.02V6.52c0-1.665-1.355-3.02-3.02-3.02H3.02C1.353 3.5 0 4.854 0 6.52v46.96c0 1.665 1.354 3.02 3.02 3.02h53.96c1.665 0 3.02-1.354 3.02-3.02V14.52c0-1.666-1.354-3.02-3.02-3.02zM58 53.48c0 .563-.457 1.02-1.02 1.02H3.02c-.563 0-1.02-.457-1.02-1.02V22.5h56v30.98zM2 20.5V6.52c0-.563.457-1.02 1.02-1.02H25c.562 0 1.02.457 1.02 1.02v6.98h30.96c.563 0 1.02.457 1.02 1.02v5.98H2z')
path(d='M19 33.5h14c.552 0 1-.447 1-1s-.448-1-1-1H19c-.552 0-1 .447-1 1s.448 1 1 1zm0 6h22c.552 0 1-.447 1-1s-.448-1-1-1H19c-.552 0-1 .447-1 1s.448 1 1 1zm0 6h22c.552 0 1-.447 1-1s-.448-1-1-1H19c-.552 0-1 .447-1 1s.448 1 1 1z')
//- Вывод блока библиотеки
mixin block-lib(id, title, sep, realBlockId)
if(typeof(realBlockId) !== 'undefined' && realBlockId)
- var realId = realBlockId
else
- var realId = id
section.blocks-library__item(class= sep ? 'blocks-library__item--menusep' : '', id=id, data-id=realId)
h2.blocks-library__item-title
a(href=repoUrl + '/tree/master/src/blocks/' + realId, class='blocks-library__item-title-link', title='Папка блока в репозитории')
svg(width='20', height='20')
use(xlink:href='#folder')
!= title
block
//- Вывод фрагмента кода в блоке библиотеки
mixin block-lib-code(id, hideBurger)
.blocks-library__code-wrapper
span.blocks-library__code-show-trigger(title='Показать код')
- if (!hideBurger)
i.blocks-library__code-icon
.blocks-library__code(id=id)
block
//- Пример вызова примесей вывода блока:
+block-lib('close', 'Закрыть', false)
include:markdown-it(linkify) ../blocks/close/readme.md
div
+close('Закрыть')
+block-lib-code()
include:show-code() ../blocks/close/close.pug
h1.blocks-library__title Блоки стартового репозитория #[a(href='index.html')]
+block-lib('page', 'Страница', false)
include:markdown-it(html='true') ../blocks/page/readme.md
+block-lib-code()
pre.code
code &lt;!DOCTYPE html>
code &lt;html class="no-js page" lang="ru">
code &lt;head>...&lt;/head>
code &lt;body>
code &lt;div class="page__inner">
code &lt;div class="page__content">
code Основной содержимое
code &lt;/div>
code &lt;div class="page__footer-wrapper">
code «Прибитый» «подвал»
code &lt;/div>
code &lt;/div>
code &lt;/body>
code &lt;/html>
+block-lib('page-header', '«Шапка» страницы', false)
include:markdown-it(html='true') ../blocks/page-header/readme.md
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/page-header/page-header.pug
+block-lib('page-footer', '«Подвал» страницы', false)
include:markdown-it(html='true') ../blocks/page-footer/readme.md
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/page-footer/page-footer.pug
+block-lib('logo', 'Логотип', false)
include:markdown-it(html='true') ../blocks/logo/readme.md
+logo('/')
img(src='img/demo-logo.svg', alt='Logo', width='64', height='15')
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/logo/logo.pug
+block-lib('main-nav', 'Главная навигация', false)
include:markdown-it(html='true') ../blocks/main-nav/readme.md
+main-nav('1')
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/main-nav/main-nav.pug
+block-lib('close', 'Закрыть', false)
include:markdown-it(html='true') ../blocks/close/readme.md
div
+close('Закрыть')
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/close/close.pug
+block-lib('burger', 'Бургер', false)
include:markdown-it(html='true') ../blocks/burger/readme.md
div
+burger('Показать пример кода', 'burger-code', 'blocks-library__code--shown')
+block-lib-code('burger-code', true)
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/burger/burger.pug
+block-lib('alert', 'Сообщения', false)
include:markdown-it(html='true') ../blocks/alert/readme.md
+alert()
p Обычное сообщение
+close('Закрыть', 'sm')(class='alert__close', aria-label='Закрыть')
+alert('Заголовок', 'warning, some-mod-name')
p Блок сообщения с модификаторами.
+close('Закрыть', 'sm')(class='alert__close', aria-label='Закрыть')
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/alert/alert.pug
+block-lib('card', 'Мини-карточка товара', false)
include:markdown-it(html='true') ../blocks/card/readme.md
.blocks-demo__cards-list(style='display: flex; flex-wrap: wrap;')
+card({
url: 'good01',
img: 'img/demo-product.jpg',
name: 'Товар1',
descr: 'Описание1',
price: 7777,
new: true,
}, 'mod')
+card({
img: 'img/demo-product.jpg',
name: 'Товар2',
price: 12000,
})
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/card/card.pug
+block-lib('label', 'Лейбл', false)
include:markdown-it(html='true') ../blocks/label/readme.md
+label('Текст1')
+label('Текст2', '', 'div')
+label('Текст3', '', 'a')(href='/')
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/label/label.pug
+block-lib('loader', 'Загрузчик', false)
include:markdown-it(html='true') ../blocks/loader/readme.md
+loader()
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/loader/loader.pug
+block-lib('pagination', 'Пагинация', false)
include:markdown-it(html='true') ../blocks/pagination/readme.md
+pagination()
+pagination-item('1', '/1')
+pagination-item('...')
+pagination-item('4', '/4')
+pagination-item('5', '/5', true)
+pagination-item('6', '/6')
+pagination-item('...')
+pagination-item('999', '/999')
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/pagination/pagination.pug
+block-lib('breadcrumbs', 'Хлебные крошки', false)
include:markdown-it(html='true') ../blocks/breadcrumbs/readme.md
+breadcrumbs()
+breadcrumbs-item('Главная', '/home')
+breadcrumbs-item('Услуги', '/services')
+breadcrumbs-item('Стриптиз')
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/breadcrumbs/breadcrumbs.pug
+block-lib('progress', 'Прогресс', false)
include:markdown-it(html='true') ../blocks/progress/readme.md
+progress('30', '30% (без копеек)', 'success')(aria-valuemin='0', aria-valuemax='100')
+progress('10')(style='height:10px')
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/progress/progress.pug
+block-lib('comment', 'Комментарий', false)
include:markdown-it(html='true') ../blocks/comment/readme.md
+comment({
username: 'Джокер',
avatarURL: 'img/demo-avatar-m.png',
authorURL: '/users/joker',
content: '<p>Далеко-далеко за словесными горами в стране, гласных и согласных живут рыбные тексты.</p>',
mods: 'admin',
})
+comment({
username: 'Джокересса',
avatarURL: 'img/demo-avatar-f.png',
content: '<p>Далеко-далеко за словесными горами в стране, гласных и согласных живут рыбные тексты.</p>',
})
+comment({
username: 'Джокер 2',
avatarURL: 'img/demo-avatar-m.png',
content: '<p>Далеко-далеко за словесными горами в стране, гласных и согласных живут рыбные тексты.</p>',
})
+comment({
username: 'Джокер',
avatarURL: 'img/demo-avatar-m.png',
content: '<p>Далеко-далеко за словесными горами в стране, гласных и согласных живут рыбные тексты.</p>',
})
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/comment/comment.pug
+block-lib('social', 'Социальные ссылки', false)
include:markdown-it(html='true') ../blocks/social/readme.md
+social()
+social-link('https://www.facebook.com/profile.php?id=100011672930358', 'fb')(title='Facebook')
svg(xmlns='http://www.w3.org/2000/svg', width='32', height='32', viewBox='0 0 470.513 470.513')
path(d='M271.521 154.17v-40.541c0-6.086.28-10.8.849-14.13.567-3.335 1.857-6.615 3.859-9.853 1.999-3.236 5.236-5.47 9.706-6.708 4.476-1.24 10.424-1.858 17.85-1.858h40.539V0h-64.809c-37.5 0-64.433 8.897-80.803 26.691-16.368 17.798-24.551 44.014-24.551 78.658v48.82h-48.542v81.086h48.539v235.256h97.362V235.256h64.805l8.566-81.086h-73.37z')
+social-link('https://vk.com/n.gromov', 'vk')(title='ВКонтакте')
svg(xmlns='http://www.w3.org/2000/svg', width='32', height='32', viewBox='0 0 548.358 548.358')
path(d='M545.451 400.298c-.664-1.431-1.283-2.618-1.858-3.569-9.514-17.135-27.695-38.167-54.532-63.102l-.567-.571-.284-.28-.287-.287h-.288c-12.18-11.611-19.893-19.418-23.123-23.415-5.91-7.614-7.234-15.321-4.004-23.13 2.282-5.9 10.854-18.36 25.696-37.397 7.807-10.089 13.99-18.175 18.556-24.267 32.931-43.78 47.208-71.756 42.828-83.939l-1.701-2.847c-1.143-1.714-4.093-3.282-8.846-4.712-4.764-1.427-10.853-1.663-18.278-.712l-82.224.568c-1.332-.472-3.234-.428-5.712.144l-3.713.859-1.431.715-1.136.859c-.952.568-1.999 1.567-3.142 2.995-1.137 1.423-2.088 3.093-2.848 4.996-8.952 23.031-19.13 44.444-30.553 64.238-7.043 11.803-13.511 22.032-19.418 30.693-5.899 8.658-10.848 15.037-14.842 19.126-4 4.093-7.61 7.372-10.852 9.849-3.237 2.478-5.708 3.525-7.419 3.142-1.715-.383-3.33-.763-4.859-1.143-2.663-1.714-4.805-4.045-6.42-6.995-1.622-2.95-2.714-6.663-3.285-11.136-.568-4.476-.904-8.326-1-11.563-.089-3.233-.048-7.806.145-13.706.198-5.903.287-9.897.287-11.991 0-7.234.141-15.085.424-23.555.288-8.47.521-15.181.716-20.125.194-4.949.284-10.185.284-15.705s-.336-9.849-1-12.991a44.442 44.442 0 0 0-2.99-9.137c-1.335-2.95-3.289-5.232-5.853-6.852-2.569-1.618-5.763-2.902-9.564-3.856-10.089-2.283-22.936-3.518-38.547-3.71-35.401-.38-58.148 1.906-68.236 6.855-3.997 2.091-7.614 4.948-10.848 8.562-3.427 4.189-3.905 6.475-1.431 6.851 11.422 1.711 19.508 5.804 24.267 12.275l1.715 3.429c1.334 2.474 2.666 6.854 3.999 13.134 1.331 6.28 2.19 13.227 2.568 20.837.95 13.897.95 25.793 0 35.689-.953 9.9-1.853 17.607-2.712 23.127-.859 5.52-2.143 9.993-3.855 13.418-1.715 3.426-2.856 5.52-3.428 6.28-.571.76-1.047 1.239-1.425 1.427a21.387 21.387 0 0 1-7.71 1.431c-2.667 0-5.901-1.334-9.707-4-3.805-2.666-7.754-6.328-11.847-10.992-4.093-4.665-8.709-11.184-13.85-19.558-5.137-8.374-10.467-18.271-15.987-29.691l-4.567-8.282c-2.855-5.328-6.755-13.086-11.704-23.267-4.952-10.185-9.329-20.037-13.134-29.554-1.521-3.997-3.806-7.04-6.851-9.134l-1.429-.859c-.95-.76-2.475-1.567-4.567-2.427a30.301 30.301 0 0 0-6.567-1.854l-78.229.568c-7.994 0-13.418 1.811-16.274 5.428l-1.143 1.711c-.571.953-.859 2.475-.859 4.57 0 2.094.571 4.664 1.714 7.707 11.42 26.84 23.839 52.725 37.257 77.659 13.418 24.934 25.078 45.019 34.973 60.237 9.897 15.229 19.985 29.602 30.264 43.112 10.279 13.515 17.083 22.176 20.412 25.981 3.333 3.812 5.951 6.662 7.854 8.565l7.139 6.851c4.568 4.569 11.276 10.041 20.127 16.416 8.853 6.379 18.654 12.659 29.408 18.85 10.756 6.181 23.269 11.225 37.546 15.126 14.275 3.905 28.169 5.472 41.684 4.716h32.834c6.659-.575 11.704-2.669 15.133-6.283l1.136-1.431c.764-1.136 1.479-2.901 2.139-5.276.668-2.379 1-5 1-7.851-.195-8.183.428-15.558 1.852-22.124 1.423-6.564 3.045-11.513 4.859-14.846 1.813-3.33 3.859-6.14 6.136-8.418 2.282-2.283 3.908-3.666 4.862-4.142.948-.479 1.705-.804 2.276-.999 4.568-1.522 9.944-.048 16.136 4.429 6.187 4.473 11.99 9.996 17.418 16.56 5.425 6.57 11.943 13.941 19.555 22.124 7.617 8.186 14.277 14.271 19.985 18.274l5.708 3.426c3.812 2.286 8.761 4.38 14.853 6.283 6.081 1.902 11.409 2.378 15.984 1.427l73.087-1.14c7.229 0 12.854-1.197 16.844-3.572 3.998-2.379 6.373-5 7.139-7.851.764-2.854.805-6.092.145-9.712-.677-3.611-1.344-6.136-2.008-7.563z')
+social-link('https://github.com/nicothin', 'gh')(title='Github')
svg(xmlns='http://www.w3.org/2000/svg', width='32', height='32', viewBox='0 0 475.084 475.084')
path(d='M436.244 146.752c5.14-15.422 7.713-31.409 7.713-47.967 0-22.08-4.859-42.828-14.564-62.242-20.362 0-38.349 3.715-53.961 11.136-15.604 7.423-33.4 18.938-53.379 34.545-25.122-6.09-51.777-9.135-79.941-9.135-30.837 0-60.245 3.333-88.223 9.994-20.364-15.99-38.351-27.74-53.959-35.26-15.608-7.52-33.689-11.279-54.247-11.279-9.707 19.414-14.56 40.163-14.56 62.242 0 16.751 2.568 32.93 7.708 48.535C12.942 177.587 0 215.272 0 260.383c0 39.595 5.898 71.092 17.701 94.507 6.283 12.367 14.465 23.312 24.554 32.832 10.085 9.514 21.601 17.228 34.545 23.13 12.946 5.896 25.981 10.801 39.116 14.699 13.134 3.9 27.646 6.758 43.54 8.559 15.893 1.816 29.93 3.004 42.111 3.579 12.181.564 25.693.853 40.544.853 17.508 0 33.396-.432 47.678-1.283 14.277-.855 30.594-2.953 48.964-6.276 18.367-3.333 34.547-7.857 48.54-13.565 13.99-5.708 27.412-13.895 40.259-24.551 12.847-10.663 22.884-23.318 30.121-37.976 11.604-23.603 17.412-55.107 17.412-94.507-.007-45.302-12.95-83.178-38.841-113.632zm-34.249 207.703c-6.092 12.471-13.802 22.265-23.127 29.41-9.329 7.139-20.938 12.847-34.831 17.135-13.9 4.281-27.217 7.087-39.971 8.415-12.758 1.334-26.933 1.998-42.545 1.998h-47.966c-15.607 0-29.79-.664-42.541-1.998-12.752-1.328-26.075-4.134-39.971-8.415-13.891-4.288-25.5-9.996-34.829-17.135-9.329-7.146-17.037-16.939-23.128-29.41-6.09-12.471-9.136-27.076-9.136-43.824 0-22.847 6.567-42.264 19.702-58.245 13.134-15.99 30.929-23.982 53.387-23.982 8.188 0 26.746 1.997 55.677 5.995 13.513 2.093 28.456 3.14 44.823 3.14 16.372 0 31.313-1.044 44.824-3.14 29.317-3.999 47.869-5.995 55.678-5.995 22.457 0 40.252 7.996 53.386 23.982 13.135 15.988 19.698 35.398 19.698 58.245 0 16.751-3.046 31.364-9.13 43.824z')
path(d='M166.875 265.52c-5.806-6.475-12.703-9.712-20.699-9.712-7.998 0-14.896 3.241-20.701 9.712-5.802 6.468-9.897 13.703-12.275 21.689-2.383 8.002-3.571 15.804-3.571 23.422 0 7.61 1.191 15.413 3.571 23.414 2.375 7.991 6.468 15.222 12.275 21.689 5.808 6.475 12.703 9.713 20.701 9.713 7.996 0 14.896-3.244 20.699-9.713 5.804-6.468 9.897-13.698 12.275-21.689 2.38-8.001 3.571-15.804 3.571-23.414 0-7.611-1.188-15.42-3.571-23.422-2.379-7.983-6.468-15.215-12.275-21.689zM349.601 265.52c-5.804-6.475-12.703-9.712-20.697-9.712-7.991 0-14.894 3.241-20.701 9.712-5.804 6.468-9.896 13.703-12.271 21.689-2.385 8.002-3.576 15.804-3.576 23.422 0 7.61 1.191 15.413 3.576 23.414 2.375 7.991 6.468 15.222 12.271 21.689 5.808 6.475 12.71 9.713 20.701 9.713 7.994 0 14.894-3.244 20.697-9.713 5.801-6.468 9.896-13.698 12.278-21.689 2.379-8.001 3.569-15.804 3.569-23.414 0-7.611-1.19-15.42-3.569-23.422-2.381-7.983-6.477-15.215-12.278-21.689z')
+social-link('https://twitter.com/nicothin', 'tw')(title='twitter')
svg(xmlns='http://www.w3.org/2000/svg', width='32', height='32', viewBox='0 0 449.956 449.956')
path(d='M449.956 85.657c-17.702 7.614-35.408 12.369-53.102 14.279 19.985-11.991 33.503-28.931 40.546-50.819-18.281 10.847-37.787 18.268-58.532 22.267-18.274-19.414-40.73-29.125-67.383-29.125-25.502 0-47.246 8.992-65.24 26.98-17.984 17.987-26.977 39.731-26.977 65.235 0 6.851.76 13.896 2.284 21.128-37.688-1.903-73.042-11.372-106.068-28.407C82.46 110.158 54.433 87.46 31.403 59.101c-8.375 14.272-12.564 29.787-12.564 46.536 0 15.798 3.711 30.456 11.138 43.97 7.422 13.512 17.417 24.455 29.98 32.831-14.849-.572-28.743-4.475-41.684-11.708v1.142c0 22.271 6.995 41.824 20.983 58.674 13.99 16.848 31.645 27.453 52.961 31.833a95.543 95.543 0 0 1-24.269 3.138c-5.33 0-11.136-.475-17.416-1.42 5.9 18.459 16.75 33.633 32.546 45.535 15.799 11.896 33.691 18.028 53.677 18.418-33.498 26.262-71.66 39.393-114.486 39.393-8.186 0-15.607-.373-22.27-1.139 42.827 27.596 90.03 41.394 141.612 41.394 32.738 0 63.478-5.181 92.21-15.557 28.746-10.369 53.297-24.267 73.665-41.686 20.362-17.415 37.925-37.448 52.674-60.097 14.75-22.651 25.738-46.298 32.977-70.946 7.23-24.653 10.848-49.344 10.848-74.092 0-5.33-.096-9.325-.287-11.991 18.087-13.127 33.504-29.023 46.258-47.672z')
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/social/social.pug
+block-lib('or', 'Разделитель «или»', false)
include:markdown-it(html='true') ../blocks/or/readme.md
+or()
+or('Текст взамен «или»', 'some-mod')
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/or/or.pug
+block-lib('embed-responsive', 'Адаптирующийся медиаконтент', false)
include:markdown-it(html='true') ../blocks/embed-responsive/readme.md
style.
@media (min-width: 768px) {
.embed-responsive-demo {
display: flex;
}
.embed-responsive-demo__item {
flex-basis: 50%;
}
.embed-responsive-demo__item:first-child {
padding-right: 5px;
}
.embed-responsive-demo__item:last-child {
padding-left: 5px;
}
.embed-responsive-demo .embed-responsive {
margin: 0;
}
}
.embed-responsive-demo
.embed-responsive-demo__item
+embed-responsive('4-3')
iframe(src='https://www.youtube.com/embed/7pOr3dBFAeY', frameborder='0', allowfullscreen='')
.embed-responsive-demo__item
+embed-responsive()
iframe(src='https://www.youtube.com/embed/B0Q1rKpDNE4', frameborder='0', allowfullscreen='')
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/embed-responsive/embed-responsive.pug
+block-lib('table', 'Таблица', false)
include:markdown-it(html='true') ../blocks/table/readme.md
+table()
tr
th Имя
th Фамилия
th Мыло
th Статус
th Политическая ориентация
tr
td Иннокентий
td Иванов
td Хозяйственное
td Администратор
td «ВСЕГДА!»
tr
td Васисуалий
td Римский-Корсаков
td Душистое
td Пользователь
td Нижняя
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/table/table.pug
+block-lib('table-responsive', 'Адаптивная таблица', false)
include:markdown-it(html='true') ../blocks/table-responsive/readme.md
+table-responsive()
caption Таблица
thead
tr
th Имя и фамилия
th Тип мироощущения
tbody
tr
td(data-label='Имя и фамилия') Анатоле Вассерман
td(data-label='Тип мироощущения') Девственность
tr
td(data-label='Имя и фамилия') Джакомо Казанова
td(data-label='Тип мироощущения') Небольшая распущенность с лёгкой примесью леденящих душу извращений, чудовищной лжи и имитатора известного органа, обитого телячьей кожей.
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/table-responsive/table-responsive.pug
+block-lib('code', 'Код', true)
include:markdown-it(html='true') ../blocks/code/readme.md
+code()
code &lt;div class="some">...&lt;/div>
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/code/code.pug
+block-lib('btn', 'Кнопка', false)
include:markdown-it(html='true') ../blocks/btn/readme.md
p.
#[+btn('Кнопка-ссылка')(href='/')]
#[+btn('Кнопка-input', '', true)(name='submit')]
#[+btn('Кнопка-button', 'main')]
p.
#[+btn('Кнопка-ссылка', 'disabled')(href='/')]
#[+btn('Кнопка-input', '', true)(name='submit', disabled='')]
#[+btn('Кнопка-button', 'main')(disabled='')]
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/btn/btn.pug
+block-lib('field-text', 'Текстовое поле', false)
include:markdown-it(html='true') ../blocks/field-text/readme.md
+field-text({
title: 'Текстовое поле',
helpText: 'Подсказка под полем',
isTextarea: true,
mods: '',
val: 'Текст внутри',
attrs: {
name: 'test',
placeholder: 'Подсказка',
}
})
+field-text({
title: 'Текстовое поле ошибочное',
helpText: 'Описание ошибки',
mods: 'error',
attrs: {
placeholder: 'Подсказка',
}
})
+field-text({
title: 'Текстовое поле блокированное',
helpText: 'Описание ошибки',
attrs: {
placeholder: 'Подсказка',
disabled: true,
}
})
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/field-text/field-text.pug
+block-lib('field-num', 'Выбор количества', false)
include:markdown-it(html='true') ../blocks/field-num/readme.md
+field-num({
title: 'Количество',
helpText: 'От 1 до 10, шаг 1',
val: '9',
attrs: {
name: 'quantity',
max: '10',
min: '1',
step: '1',
}
})
+field-num({
mods: 'error',
attrs: {
name: 'quantity1',
}
})
+field-num({
attrs: {
name: 'quantity2',
disabled: true,
}
})
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/field-num/field-num.pug
+block-lib('field-checkbox', 'Флажок/чекбокс', false)
include:markdown-it(html='true') ../blocks/field-checkbox/readme.md
+field-checkbox([
{
title: 'Чекбокс без группы',
helpText: 'Отдельно расположенный чекбокс',
attrs: {
name: 'check0',
}
},
])
+field-checkbox([
{
title: 'Чекбокс ошибочный',
helpText: 'И, возможно, единственный',
mods: 'error',
attrs: {
name: 'check1',
checked: true,
}
},
{
title: 'Чекбокс блокирован',
helpText: 'А, нет. Не единственный...',
attrs: {
name: 'check2',
disabled: true,
}
},
], 'Общее необязательное название')
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/field-checkbox/field-checkbox.pug
+block-lib('field-radio', 'Радиокнопки', false)
include:markdown-it(html='true') ../blocks/field-radio/readme.md
+field-radio([
{
title: 'Радиокнопка',
helpText: 'И, возможно, единственный',
attrs: {
name: 'radio',
checked: true,
}
},
{
title: 'Радиокнопка ошибочная',
helpText: 'А, нет. Не единственный...',
mods: 'error',
attrs: {
name: 'radio',
}
},
{
title: 'Радиокнопка блокированная',
attrs: {
name: 'radio',
disabled: true,
}
},
], 'Общее необязательное название')
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/field-radio/field-radio.pug
+block-lib('field-toggler', 'Переключатель', false)
include:markdown-it(html='true') ../blocks/field-toggler/readme.md
+field-toggler([
{
title: 'Одиночный переключатель',
helpText: 'Это чекбокс, на самом деле',
attrs: {
name: 'check10',
}
},
])
+field-toggler([
{
title: 'Одиночный переключатель ошибочный',
helpText: 'Это чекбокс, на самом деле',
mods: 'error',
attrs: {
name: 'check101',
}
},
])
+field-toggler([
{
title: 'Одиночный переключатель блокированный',
helpText: 'Это чекбокс, на самом деле',
attrs: {
name: 'check102',
disabled: true,
}
},
])
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/field-toggler/field-toggler.pug
+block-lib('field-file', 'Файл', false)
include:markdown-it(html='true') ../blocks/field-file/readme.md
+field-file({
title: 'Название',
helpText: 'Подсказка',
attrs: {
name: 'commentFile',
multiple: true,
},
text: {
selectText: 'Выберите файл(ы)',
nothingText: 'Ничего не выбрано',
flesSelectedText: 'Выбрано файлов:',
},
})
+field-file({
helpText: 'Ошибка',
mods: 'error',
attrs: {
name: 'commentFile3',
},
})
+field-file({
helpText: 'Блокировано',
attrs: {
name: 'commentFile4',
disabled: true,
},
})
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/field-file/field-file.pug
+block-lib('field-range', 'Ползунок', false)
include:markdown-it(html='true') ../blocks/field-range/readme.md
+field-range({
title: 'Название',
helpText: 'Подсказка',
mods: '',
attrs: {
name: 'counter',
min: '1',
max: '100',
step: '1',
value: '40'
}
})
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/field-range/field-range.pug
+block-lib('field-select', 'Кастомный селект с <a href="https://joshuajohnson.co.uk/Choices/">Choices</a>', false)
include:markdown-it(html='true') ../blocks/field-select/readme.md
+field-select(
'Название селекта',
{
name: 'select0',
id: 'select-demo0'
},
[
{
title: 'Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 Опция 1 ',
attrs: {
value: 'val06',
}
},
{
title: 'Опция 2',
attrs: {
value: 'val05',
}
},
],
'Подсказка',
''
)
+field-select(
'Название селекта',
{
name: 'select',
id: 'select-demo',
disabled: true,
},
[
{
attrs: {
label: 'Группа',
},
child: [
{
title: 'Опция1',
attrs: {
value: 'val01',
}
},
{
title: 'Опция2',
attrs: {
value: 'val02',
}
},
]
},
{
title: 'Опция вне группы',
attrs: {
value: 'val03',
}
}
],
'Подсказка',
''
)
+field-select(
'Название селекта',
{
name: 'select1',
id: 'select-demo1',
},
[
{
attrs: {
label: 'Группа',
},
child: [
{
title: 'Опция1',
attrs: {
value: 'val01',
}
},
{
title: 'Опция2',
attrs: {
value: 'val02',
}
},
]
},
{
title: 'Опция вне группы',
attrs: {
value: 'val03',
}
}
],
'Подсказка',
'error'
)
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/field-select/field-select.pug
+block-lib('field-actions', 'Действия формы', false)
include:markdown-it(html='true') ../blocks/field-actions/readme.md
+field-actions('* — обязательные поля')
+btn('Отправить')
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/field-actions/field-actions.pug
+block-lib('form', 'Форма', true)
include:markdown-it(html='true') ../blocks/form/readme.md
+form()
+fieldset('Название1')
+field-text({
helpText: 'Подсказка',
attrs: {
placeholder: 'Пример',
}
})
+field-text({
helpText: 'Подсказка',
attrs: {
placeholder: 'Пример'
}
})
+fieldset('Название2')
+field-text({
helpText: 'Подсказка',
attrs: {
placeholder: 'Пример'
}
})
+field-text({
helpText: 'Подсказка',
attrs: {
placeholder: 'Пример'
}
})
+field-actions()
+btn('Отправить', 'primary')
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/form/form.pug
+block-lib('form-validation', 'Валидатор форм', true)
include:markdown-it(html='true') ../blocks/form-validation/readme.md
+form()(class='blocks-library__row', data-check-form='true')
.blocks-library__col-1-2
+field-text({
title: 'Имя',
helpText: 'Имя юзера. Не менее 3-х символов. Обязательно.',
attrs: {
require: true,
'data-check-pattern': '^[a-zа-яё]+.{2,}'
}
})
.blocks-library__col-1-2
+field-text({
title: 'Фамилия',
helpText: 'Фамилия. Не менее 2-х символов. Не обязательно.',
attrs: {
'data-check-pattern': '^[a-zа-яё]+.+'
}
})
.blocks-library__col-1-2
+field-text({
title: 'Телефон',
helpText: 'Обязательно.',
attrs: {
type: 'tel',
require: true,
placeholder: '+7 (000) 000-00-00',
'data-check-pattern': '[\\+]?\\d?[\\s|-]?[(]?\\d{0,3}[)]?[\\s|-]?\\d{3}[\\s|-]?\\d{2}[\\s|-]?\\d{2}'
}
})
.blocks-library__col-1-2
+field-text({
title: 'Электромыло',
helpText: 'Обязательно.',
attrs: {
type: 'email',
require: true,
'data-check-pattern': '[a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?'
}
})
.blocks-library__col-2-2
+field-checkbox([
{
title: 'Принимаю условия, передаю квартиру во владение Николаю.',
helpText: 'Это шутка, чекбокс можно спокойно отмечать. Или же нет.',
attrs: {
name: 'checkCheck',
'data-check-state': 'on',
}
},
{
title: 'Должен. Быть. Неотмеченным.',
helpText: 'Иначе всё — тлен!',
attrs: {
name: 'checkOff',
'data-check-state': 'off',
checked: true,
}
},
])
.blocks-library__col-2-2
+field-actions()
+btn('Отправить', 'primary')
+block-lib-code()
pre.code
code +field-text({
code title: 'Фамилия',
code helpText: 'Фамилия. Не менее 2-х символов. Не обязательно.',
code attrs: {
code 'data-check-pattern': '^[a-zа-яё]+.+'
code }
code })
code +field-text({
code title: 'Телефон',
code helpText: 'Обязательно.',
code attrs: {
code type: 'tel',
code require: true,
code placeholder: '+7 (000) 000-00-00',
code 'data-check-pattern': '[\\+]?\\d?[\\s|-]?[(]?\\d{0,3}[)]?[\\s|-]?\\d{3}[\\s|-]?\\d{2}[\\s|-]?\\d{2}'
code }
code })
code +field-text({
code title: 'Электромыло',
code helpText: 'Обязательно.',
code attrs: {
code type: 'email',
code require: true,
code 'data-check-pattern': '[a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?'
code }
code })
+block-lib('scroll-link', 'Якорные ссылки', false)
include:markdown-it(html='true') ../blocks/scroll-link/readme.md
+scroll-link('Описание блока <code>page</code>', '#page')
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/scroll-link/scroll-link.pug
+block-lib('to-top-block', 'Ссылка «вверх»', false, 'to-top')
include:markdown-it(html='true') ../blocks/to-top/readme.md
+to-top()
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/to-top/to-top.pug
+block-lib('off-canvas', 'Боковое меню страницы', false)
include:markdown-it(html='true') ../blocks/off-canvas/readme.md
p #[button.btn(type='button', data-toggle='off-canvas') Показать/скрыть боковое меню]
+block-lib-code()
pre.code
code //- Разметка, необходимая для использования бокового меню
code #off-canvas.off-canvas
code aside.off-canvas__aside(role='complementary', aria-label='Боковое меню') Меню
code .off-canvas__page-content Контент
code .off-canvas__overlay(data-toggle='off-canvas')
code
code button.btn(type='button', data-toggle='off-canvas') Показать/скрыть боковое меню
code a(href='#SOME_HASH', data-toggle-native='off-canvas') Показать/скрыть боковое меню и перейти на якорь
+block-lib('modal', 'Модальное окно', false)
include:markdown-it(html='true') ../blocks/modal/readme.md
p #[button.btn(type='button', data-modal-target='#modal-demo-01', data-modal='open') Показать окно с «шапкой» и «подвалом»] #[a(href='#modal-demo-02', data-modal='open') Показать окно без внутренних отступов]
+modal('modal-demo-01', 'Заголовок', 'some')
.modal__body
p С «Шапкой», «подвалом» и внутр. отступами на контенте.
p С «Шапкой», «подвалом» и внутр. отступами на контенте.
p С «Шапкой», «подвалом» и внутр. отступами на контенте.
footer.modal__footer
button.btn(type='button', data-modal='close') Закрыть
+modal('modal-demo-02')
p Без «шапки», «подвала» и внутр. отступов на контенте.
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/modal/modal.pug
+block-lib('tabs', 'Табы', false)
include:markdown-it(html='true') ../blocks/tabs/readme.md
+tabs([
{
'id': 'tab00',
'text': 'Вкладка ноль',
'isActive': true,
},
{
'id': 'tab01',
'text': 'Вкладка один',
},
{
'id': 'tab02',
'text': 'Вкладка два',
},
{
'id': 'tab03',
'text': 'Вкладка три',
},
], 'some-mod')
+tabs-item('tab00', true, 'some-mod')
p Далеко-далеко за словесными горами в стране, гласных и согласных живут рыбные тексты.0
+tabs-item('tab01')
p За словесными горами в стране, гласных и согласных живут рыбные тексты.1
+tabs-item('tab02')
p Словесными горами в стране, гласных и согласных живут рыбные тексты.2
+tabs-item('tab03')
p Горами в стране, гласных и согласных живут рыбные тексты.3
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/tabs/tabs.pug
+block-lib('baron', 'Кастомный скролл с <a href="https://www.npmjs.com/package/baron">baron</a>', false)
include:markdown-it(html='true') ../blocks/baron/readme.md
+baron('baron-demo', 'mods')(style='height: 100px; border: 1px solid #E6E6E6; margin-bottom: 1rem;')
p Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt dicta aliquid nemo, sit possimus, eveniet tempore minus, doloremque incidunt, nihil quos aperiam ab iure quia. Ipsa sit porro incidunt, sed!
p Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt dicta aliquid nemo, sit possimus, eveniet tempore minus, doloremque incidunt, nihil quos aperiam ab iure quia. Ipsa sit porro incidunt, sed!
p Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt dicta aliquid nemo, sit possimus, eveniet tempore minus, doloremque incidunt, nihil quos aperiam ab iure quia. Ipsa sit porro incidunt, sed!
p Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt dicta aliquid nemo, sit possimus, eveniet tempore minus, doloremque incidunt, nihil quos aperiam ab iure quia. Ipsa sit porro incidunt, sed!
p Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt dicta aliquid nemo, sit possimus, eveniet tempore minus, doloremque incidunt, nihil quos aperiam ab iure quia. Ipsa sit porro incidunt, sed!
+block-lib-code()
include:show-code(first-line='//- Pug-файл этого блока:') ../blocks/baron/baron.pug
+block-lib('sprite-svg', 'SVG-спрайт c <a href="https://github.com/w0rm/gulp-svgstore">gulp-svgstore</a>', false)
include:markdown-it(html='true') ../blocks/sprite-svg/readme.md
+block-lib('sprite-png', 'PNG-спрайт с <a href="https://github.com/Ensighten/spritesmith">spritesmith</a>', false)
include:markdown-it(html='true') ../blocks/sprite-png/readme.md
.burger.blocks-library__menu-toggler(data-toggle='off-canvas')
span(data-toggle='off-canvas')
block footer
extends ../pug/layout.pug
block meta
title Главная
meta(name='description', content='')
//- block append head
//- link(rel='stylesheet', href='css/some.css')
block nav
+main-nav('10')
block content
h1 Заголовок
p Содержимое. #[a(href='blocks-demo.html') Библиотека блоков].
p Lorem ipsum dolor sit amet, consectetur adipisicing elit. Praesentium illo earum ipsa dicta? Harum architecto incidunt earum beatae eius rem nulla ab blanditiis placeat voluptas. Hic velit praesentium odit eius!
//- block footer
//- p footer
//- block append page-bottom
//- script(src='js/some.js')
//- Шаблон страницы с боковым меню (off-canvas)
include mixins.pug
doctype html
html(class='no-js page', lang='ru')
head
meta(charset='utf-8')
meta(name='viewport', content='width=device-width, initial-scale=1')
//- Edge и ряд мобильных браузеров слишком умны. Отключим это.
meta(name='format-detection', content='telephone=no')
meta(name='format-detection', content='date=no')
meta(name='format-detection', content='address=no')
meta(name='format-detection', content='email=no')
meta(content='notranslate', name='google')
//- Фавиконки (https://realfavicongenerator.net)
//- link(rel='apple-touch-icon', sizes='180x180', href='/img/favicon/apple-touch-icon.png')
//- link(rel='icon', type='image/png', sizes='32x32', href='/img/favicon/favicon-32x32.png')
//- link(rel='icon', type='image/png', sizes='16x16', href='/img/favicon/favicon-16x16.png')
//- link(rel='manifest', href='/img/favicon/site.webmanifest')
//- link(rel='mask-icon', href='/img/favicon/safari-pinned-tab.svg', color='#5bbad5')
//- meta(name='msapplication-TileColor', content='#5bbad5')
meta(name='theme-color', content='#000')
meta(name='msapplication-navbutton-color', content='#000')
meta(name='apple-mobile-web-app-status-bar-style', content='#000')
block meta
title Home
block head
link(rel='stylesheet', href='css/style.css')
link(rel='stylesheet', href='css/blocks-library.css')
script
include ../js/head-script.js
body
noscript У вас отключен JavaScript. Это пугает.
.off-canvas#off-canvas
aside.off-canvas__aside(role='complementary', aria-label='Меню')
block off-canvas
main.off-canvas__page-content.components-demo.blocks-library(role='main')
//- Реальный контент страницы
.page__inner
.page__content
block header
p «шапка»
block content
p содержимое страницы
.page__footer-wrapper
block footer
p «подвал»
.off-canvas__overlay(data-toggle='off-canvas')
block page-bottom
script(src='js/blocks-library.js')
//- Стандартный шаблон страницы
include mixins.pug
doctype html
html(class='page no-js', lang='ru')
head
meta(charset='utf-8')
meta(name='viewport', content='width=device-width, initial-scale=1')
//- Edge и ряд мобильных браузеров слишком умны. Отключим это.
meta(name='format-detection', content='telephone=no')
meta(name='format-detection', content='date=no')
meta(name='format-detection', content='address=no')
meta(name='format-detection', content='email=no')
meta(content='notranslate', name='google')
//- Предзагрузка шрифтов или иных ресурсов
//- link(rel='preload', href='fonts/Bloggersans.woff2', as='font', type='font/woff2', crossorigin='anonymous')
//- Фавиконки (https://realfavicongenerator.net)
//- link(rel='apple-touch-icon', sizes='180x180', href='/img/favicon/apple-touch-icon.png')
//- link(rel='icon', type='image/png', sizes='32x32', href='/img/favicon/favicon-32x32.png')
//- link(rel='icon', type='image/png', sizes='16x16', href='/img/favicon/favicon-16x16.png')
//- link(rel='manifest', href='/img/favicon/site.webmanifest')
//- link(rel='mask-icon', href='/img/favicon/safari-pinned-tab.svg', color='#5bbad5')
//- meta(name='msapplication-TileColor', content='#5bbad5')
//- meta(name='theme-color', content='#000')
//- meta(name='msapplication-navbutton-color', content='#000')
//- meta(name='apple-mobile-web-app-status-bar-style', content='#000')
block meta
title Home
block head
link(rel='stylesheet', href='css/style.css')
script
include ../js/head-script.js
body
noscript У вас отключен JavaScript. Это пугает.
.page__inner
.page__content
+page-header()
+logo('/')
img(src='img/demo-logo.svg', alt='Logo', width='64', height='15')
block nav
block content
p содержимое
.page__footer-wrapper
+page-footer()
block footer
p «Подвал»
p Контактный телефон: #[a(href='tel:+70000000000') +7 000 000 00 00]
block page-bottom
script(src='js/bundle.js')
Это исходные файлы проекта, из которых делается сборка.
Редактировать — эти исходники.
Смотреть — сборку.
@import 'mixins/form-mixins.scss';
@import 'mixins/text-mixins.scss';
// stylelint-disable plugin/selector-bem-pattern
@mixin field-text {
display: inline-block;
vertical-align: middle;
width: 100%;
min-height: calc(#{$line-height}em + #{$field-padding-vertical} + #{$field-padding-vertical} + 2px); // Привет, IE
margin: 0;
border: 1px solid $border-color;
border-radius: $border-radius;
padding: $field-padding-vertical $field-padding-horizontal;
line-height: $line-height;
font-size: 1em;
font-family: inherit;
background-color: #fff;
-moz-appearance: textfield; // stylelint-disable-line
appearance: textfield;
&:focus {
@include field-focus;
}
&[disabled],
&[readonly] {
cursor: not-allowed;
background-color: $gray-lighter;
}
&::-webkit-search-cancel-button,
&::-webkit-search-decoration,
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
-webkit-appearance: none; // stylelint-disable-line property-no-vendor-prefix
}
&[type='color'] {
padding: 0;
}
&::placeholder {
color: $text-color-muted;
opacity: 1;
}
}
@mixin field-help-text {
display: block;
padding-top: 0.4em;
font-size: $font-size-sm;
line-height: 1.2em;
color: $text-color-muted;
&:empty { // stylelint-disable-line
display: none;
}
}
@mixin field-focus {
outline: 0;
box-shadow: 0 0 0 2px rgba($color-main, $opacity);
}
@mixin field-name {
display: block;
margin-bottom: 0.4em;
line-height: $line-height;
font-weight: 700;
}
@mixin hide-text {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
clip: rect(0 0 0 0);
overflow: hidden;
}
@media print {
*,
*:before,
*:after {
background: transparent !important; // stylelint-disable-line declaration-no-important
color: #000 !important; // stylelint-disable-line declaration-no-important
box-shadow: none !important; // stylelint-disable-line declaration-no-important
text-shadow: none !important; // stylelint-disable-line declaration-no-important
}
a,
a:visited {
text-decoration: underline !important; // stylelint-disable-line declaration-no-important
}
a[href]:after {
content: ' (' attr(href) ')';
}
a[href^='#']:after,
a[href^='javascript:']:after {
content: '';
}
abbr[title]:after {
content: ' (' attr(title) ')';
}
pre,
blockquote {
border: 1px solid #999 !important; // stylelint-disable-line declaration-no-important
page-break-inside: avoid;
}
thead {
display: table-header-group;
}
tr,
img {
page-break-inside: avoid;
}
img {
max-width: 100% !important; // stylelint-disable-line declaration-no-important
}
p,
h2,
h3 {
orphans: 3;
widows: 3;
}
h2,
h3 {
page-break-after: avoid;
}
table {
border-collapse: collapse !important; // stylelint-disable-line declaration-no-important
td,
th {
background-color: #fff !important; // stylelint-disable-line declaration-no-important
}
}
}
`style.scss` генерируется автоматически, не пишите туда ничего.
// Базовые цвета https://colorscheme.ru/color-converter.html
$black: hsl(0, 0%, 0%);
$gray-darkest: hsl(0, 0%, 10%);
$gray-darker: hsl(0, 0%, 20%);
$gray-darken: hsl(0, 0%, 30%);
$gray-dark: hsl(0, 0%, 40%);
$gray: hsl(0, 0%, 50%);
$gray-light: hsl(0, 0%, 60%);
$gray-lighten: hsl(0, 0%, 70%);
$gray-lighter: hsl(0, 0%, 80%);
$gray-lightest: hsl(0, 0%, 90%);
$white: hsl(0, 0%, 100%);
$color-main: hsl(208, 98%, 43%);
$color-success: hsl(120, 39%, 54%);
$color-danger: hsl(2, 64%, 58%);
$color-warning: hsl(35, 84%, 62%);
$color-vk: #4a76a8;
$color-fb: #4267b2;
$color-tw: #40a2f5;
$color-gh: #24292e;
// Семантические цвета
$text-color: $gray-darkest;
$text-color-muted: $gray;
$body-bg: $white;
$link-color: $color-main;
$link-color--hover: darken($color-main, 15%);
$border-color: $gray-lighten;
// Базовая типографика
$font-size: 16px;
$font-size-h1: 40px;
$font-size-h2: 32px;
$font-size-h3: 24px;
$font-size-h4: 18px;
$font-size-h5: 16px;
$font-size-h6: 16px;
$font-size-sm: 0.75em; // 12px от 16px
$line-height: 1.5;
$typo-margin-vertical: 1em;
$font-family: -apple-system, BlinkMacSystemFont, 'Roboto', 'Ubuntu', 'Droid Sans', 'Helvetica Neue', 'Arial', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
$font-family--monospace: SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
// Georgia, Times New Roman, Times, serif
// Ширины
$screen-xs: 0;
$screen-sm: 480px;
$screen-md: 768px;
$screen-lg: 992px;
$screen-xl: 1200px;
$screen-xxl: 1800px;
$container-sm: 100%;
$container-md: 100%;
$container-lg: $screen-lg - 30;
$container-xl: $screen-xl - 30;
$container-xxl: $screen-xxl - 30;
// Модульная сетка
$grid-columns: 12;
$grid-gutter-width: 30px;
// Разное
$field-padding-vertical: 5px;
$field-padding-horizontal: 12px;
$border-radius: 3px;
$opacity: 0.7;
$transition-time: 0.3s;
$shadow: 0 4px 2px -2px rgba(0, 0, 0, 0.3);
$menu-desktop-width: $screen-lg;
$toggler-part-height: 2px;
$z-index-modal: 100;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment