griffincss-ui 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,90 @@
1
+ // Griffincss UI — Alert
2
+ // Сообщение о статусе: информация, успех, предупреждение, ошибка.
3
+ //
4
+ // Статус читается словом, а не только цветом. Цветная полоса не переживает
5
+ // чёрно-белую печать, дальтонизм и монохромный режим высокого контраста —
6
+ // слово переживает. Поэтому .gr-alert-title в разметке обязателен, а иконка
7
+ // в слоте — третий, необязательный признак поверх цвета и текста.
8
+ //
9
+ // Роль объявляется разметкой: role="alert" для того, что требует немедленного
10
+ // внимания, role="status" для остального. Модуль их не подменяет.
11
+ //
12
+ // Статус задаёт две переменные: --gr-alert-color красит слово и иконку,
13
+ // --gr-alert-fill — полосу и подложку. У трёх статусов из четырёх они
14
+ // совпадают. Расходятся у предупреждения: чтобы читаться текстом на белом,
15
+ // его цвет обязан быть тёмно-коричневым, а полоса такого цвета не читается
16
+ // как «внимание».
17
+
18
+ @use 'sass:list';
19
+
20
+ .gr-alert {
21
+ --gr-alert-color: var(--gr-color-info);
22
+ --gr-alert-fill: var(--gr-color-info);
23
+
24
+ display: flex;
25
+ box-sizing: border-box;
26
+ align-items: flex-start;
27
+ gap: var(--gr-ui-gap);
28
+ padding: var(--gr-gap);
29
+ border: var(--gr-border-width) solid var(--gr-alert-fill);
30
+ border-inline-start-width: calc(var(--gr-border-width) * 4);
31
+ border-radius: var(--gr-radius);
32
+ background-color: color-mix(in srgb, var(--gr-alert-fill) 10%, var(--gr-color-bg));
33
+ color: var(--gr-color-text);
34
+ line-height: 1.5;
35
+ }
36
+
37
+ // Слово статуса. Цвет — тот, что безопасен для текста: у предупреждения
38
+ // он темнее полосы, у остальных совпадает с ней. Само слово при этом
39
+ // остаётся читаемым и вовсе без цвета — это и есть смысл требования.
40
+ .gr-alert-title {
41
+ display: block;
42
+ margin: 0;
43
+ color: var(--gr-alert-color);
44
+ font-size: inherit;
45
+ font-weight: 600;
46
+ }
47
+
48
+ // Слот под иконку. Своих иконок библиотека не везёт: сюда кладут <svg>,
49
+ // эмодзи или любой знак — размер и выравнивание задаёт слот.
50
+ .gr-alert-icon {
51
+ flex: none;
52
+ inline-size: 1.25em;
53
+ block-size: 1.25em;
54
+ color: var(--gr-alert-color);
55
+ line-height: 1.25;
56
+ text-align: center;
57
+ }
58
+
59
+ // Текст забирает остаток строки, кнопка закрытия прижимается к концу.
60
+ .gr-alert-body {
61
+ flex: 1 1 auto;
62
+ min-inline-size: 0;
63
+ }
64
+
65
+ // --- Статусы ----------------------------------------------------------------
66
+
67
+ // Парами: цвет слова и иконки, цвет полосы и подложки.
68
+ $gr-alert-statuses: (
69
+ info: (var(--gr-color-info), var(--gr-color-info)),
70
+ success: (var(--gr-color-success), var(--gr-color-success)),
71
+ warning: (var(--gr-color-warning), var(--gr-color-warning-surface)),
72
+ danger: (var(--gr-color-danger), var(--gr-color-danger))
73
+ );
74
+
75
+ @each $gr-name, $gr-value in $gr-alert-statuses {
76
+ .gr-alert-#{$gr-name} {
77
+ --gr-alert-color: #{list.nth($gr-value, 1)};
78
+ --gr-alert-fill: #{list.nth($gr-value, 2)};
79
+ }
80
+ }
81
+
82
+ // --- Закрытие ---------------------------------------------------------------
83
+
84
+ // Отступ под кнопку закрытия. Сама кнопка — обычная .gr-btn-icon.gr-btn-ghost
85
+ // в конце строки; закрывает её делегированный обработчик [data-gr-dismiss]
86
+ // из griffincss-ui.js (волна 8b). Без скрипта кнопку просто не ставят —
87
+ // сообщение остаётся на месте, а не превращается в мёртвый крестик.
88
+ .gr-alert-dismissible {
89
+ padding-inline-end: calc(var(--gr-gap) * 0.5);
90
+ }
@@ -0,0 +1,90 @@
1
+ // Griffincss UI — Avatar
2
+ // Портрет пользователя: картинка, а если её нет — инициалы.
3
+ //
4
+ // Фолбэк не требует ни скрипта, ни второго элемента. Класс ставится
5
+ // на контейнер с инициалами внутри, а <img> кладётся поверх и закрывает их
6
+ // собой. Нет картинки — нет и перекрытия, инициалы остаются видны; битая
7
+ // ссылка даёт тот же результат, потому что сломанный <img> ничего не рисует.
8
+
9
+ .gr-avatar {
10
+ --gr-avatar-size: 2.5rem;
11
+
12
+ display: inline-flex;
13
+ position: relative;
14
+ box-sizing: border-box;
15
+ flex: none;
16
+ align-items: center;
17
+ justify-content: center;
18
+ inline-size: var(--gr-avatar-size);
19
+ block-size: var(--gr-avatar-size);
20
+ border-radius: var(--gr-radius-pill);
21
+ background-color: var(--gr-color-surface-raised);
22
+ color: var(--gr-color-text-secondary);
23
+ font-size: calc(var(--gr-avatar-size) * 0.38);
24
+ font-weight: 600;
25
+ line-height: 1;
26
+ text-transform: uppercase;
27
+ user-select: none;
28
+ overflow: hidden;
29
+ }
30
+
31
+ // Картинка перекрывает инициалы целиком. object-fit: cover не даёт
32
+ // непропорциональному портрету растянуться в овал.
33
+ .gr-avatar img {
34
+ position: absolute;
35
+ inset: 0;
36
+ inline-size: 100%;
37
+ block-size: 100%;
38
+ border-radius: inherit;
39
+ object-fit: cover;
40
+ }
41
+
42
+ // --- Размеры ----------------------------------------------------------------
43
+
44
+ $gr-avatar-sizes: (
45
+ xs: 1.5rem,
46
+ sm: 2rem,
47
+ lg: 3rem,
48
+ xl: 4rem
49
+ );
50
+
51
+ @each $gr-name, $gr-size in $gr-avatar-sizes {
52
+ .gr-avatar-#{$gr-name} {
53
+ --gr-avatar-size: #{$gr-size};
54
+ }
55
+ }
56
+
57
+ // --- Форма ------------------------------------------------------------------
58
+
59
+ // Скруглённый квадрат вместо круга — для логотипов и обложек,
60
+ // которым круг срезает углы вместе со смыслом.
61
+ .gr-avatar-square {
62
+ border-radius: var(--gr-radius);
63
+ }
64
+
65
+ // --- Стопка -----------------------------------------------------------------
66
+
67
+ // Ряд с перекрытием: «и ещё пятеро». Кольцо цветом фона отделяет соседей
68
+ // друг от друга — без него силуэты слипаются в одно пятно.
69
+ .gr-avatar-group {
70
+ display: inline-flex;
71
+ align-items: center;
72
+ }
73
+
74
+ .gr-avatar-group > .gr-avatar {
75
+ box-shadow: 0 0 0 calc(var(--gr-border-width) * 2) var(--gr-color-bg);
76
+ }
77
+
78
+ .gr-avatar-group > .gr-avatar + .gr-avatar {
79
+ margin-inline-start: calc(var(--gr-avatar-size) * -0.3);
80
+ }
81
+
82
+ // В стопке каждый следующий аватар накрывает предыдущему край — так их
83
+ // и рисует браузер по порядку разметки. Тот, на который навели или перешли
84
+ // табом, поднимается наверх и показывается целиком.
85
+ .gr-avatar-group > .gr-avatar:hover,
86
+ .gr-avatar-group > .gr-avatar:focus-visible,
87
+ .gr-avatar-group > .gr-avatar:focus-within {
88
+ position: relative;
89
+ z-index: 1;
90
+ }
@@ -0,0 +1,162 @@
1
+ // Griffincss UI — Badge
2
+ // Мелкие маркеры: плашка статуса, тег с крестиком, точка-индикатор
3
+ // и счётчик непрочитанного.
4
+ //
5
+ // Все четыре — строчные: стоят внутри строки текста, заголовка или кнопки
6
+ // и не ломают её высоту. Кегль задан в em, а не в rem: плашка рядом
7
+ // с заголовком должна быть крупнее, чем та же плашка в подписи.
8
+ //
9
+ // Статус задаёт три переменные, а не одну: --gr-badge-color для текста
10
+ // и линии, --gr-badge-fill для заливки и --gr-badge-on-fill для чернил
11
+ // на ней. У четырёх статусов из пяти первые две совпадают; расходятся они
12
+ // только у предупреждения — его текстовый цвет в светлой теме обязан быть
13
+ // тёмно-коричневым, чтобы читаться на белом, а точка или счётчик такого
14
+ // цвета выглядят не предупреждением, а грязью.
15
+
16
+ @use 'sass:list';
17
+
18
+ .gr-badge {
19
+ --gr-badge-color: var(--gr-color-text-secondary);
20
+ --gr-badge-fill: var(--gr-color-text-secondary);
21
+ --gr-badge-on-fill: var(--gr-color-on-accent);
22
+
23
+ display: inline-flex;
24
+ box-sizing: border-box;
25
+ align-items: center;
26
+ gap: calc(var(--gr-ui-gap) * 0.5);
27
+ padding-block: 0.125em;
28
+ padding-inline: 0.5em;
29
+ border: var(--gr-border-width) solid transparent;
30
+ border-radius: calc(var(--gr-radius) * 0.5);
31
+ background-color: color-mix(in srgb, var(--gr-badge-fill) 16%, var(--gr-color-bg));
32
+ color: var(--gr-badge-color);
33
+ font-size: 0.8125em;
34
+ font-weight: 600;
35
+ line-height: 1.4;
36
+ white-space: nowrap;
37
+ vertical-align: middle;
38
+ }
39
+
40
+ // Тройками: текст и линия, заливка, чернила на заливке.
41
+ $gr-badge-statuses: (
42
+ accent: (var(--gr-color-accent), var(--gr-color-accent), var(--gr-color-on-accent)),
43
+ success: (var(--gr-color-success), var(--gr-color-success-surface), var(--gr-color-on-success)),
44
+ warning: (var(--gr-color-warning), var(--gr-color-warning-surface), var(--gr-color-on-warning)),
45
+ danger: (var(--gr-color-danger), var(--gr-color-danger-surface), var(--gr-color-on-danger)),
46
+ info: (var(--gr-color-info), var(--gr-color-info-surface), var(--gr-color-on-info))
47
+ );
48
+
49
+ @each $gr-name, $gr-value in $gr-badge-statuses {
50
+ .gr-badge-#{$gr-name} {
51
+ --gr-badge-color: #{list.nth($gr-value, 1)};
52
+ --gr-badge-fill: #{list.nth($gr-value, 2)};
53
+ --gr-badge-on-fill: #{list.nth($gr-value, 3)};
54
+ }
55
+ }
56
+
57
+ // Скруглённая до полукруга — под счётчики и короткие метки.
58
+ .gr-badge-pill {
59
+ padding-inline: 0.6em;
60
+ border-radius: var(--gr-radius-pill);
61
+ }
62
+
63
+ // Контурная: фон уходит, остаётся линия. Полезна поверх цветной подложки,
64
+ // где заливка плашки слилась бы с фоном.
65
+ .gr-badge-outline {
66
+ border-color: currentcolor;
67
+ background-color: transparent;
68
+ }
69
+
70
+ // --- Счётчик ----------------------------------------------------------------
71
+
72
+ // Круг с числом. min-inline-size равна высоте, поэтому одна цифра даёт круг,
73
+ // а три — вытянутую пилюлю, и обе версии остаются одного роста.
74
+ //
75
+ // Здесь цвет статуса работает заливкой, а не текстом, поэтому берётся
76
+ // --gr-badge-fill, и чернила — соответствующие ему.
77
+ .gr-badge-count {
78
+ justify-content: center;
79
+ min-inline-size: 1.6em;
80
+ padding-inline: 0.4em;
81
+ border-radius: var(--gr-radius-pill);
82
+ background-color: var(--gr-badge-fill);
83
+ color: var(--gr-badge-on-fill);
84
+ font-variant-numeric: tabular-nums;
85
+ }
86
+
87
+ // --- Тег --------------------------------------------------------------------
88
+
89
+ // Плашка, которую можно снять: тот же корпус, но с местом под крестик.
90
+ // Крестик — обычная кнопка внутри; закрытие делает [data-gr-dismiss]
91
+ // из griffincss-ui.js (волна 8b).
92
+ .gr-tag {
93
+ display: inline-flex;
94
+ box-sizing: border-box;
95
+ align-items: center;
96
+ gap: calc(var(--gr-ui-gap) * 0.5);
97
+ padding-block: 0.125em;
98
+ padding-inline: 0.5em;
99
+ border: var(--gr-border-width) solid var(--gr-color-border);
100
+ border-radius: calc(var(--gr-radius) * 0.5);
101
+ background-color: var(--gr-color-surface-raised);
102
+ color: var(--gr-color-text);
103
+ font-size: 0.875em;
104
+ line-height: 1.4;
105
+ white-space: nowrap;
106
+ vertical-align: middle;
107
+ }
108
+
109
+ .gr-tag-remove {
110
+ display: inline-flex;
111
+ box-sizing: border-box;
112
+ align-items: center;
113
+ justify-content: center;
114
+ inline-size: 1.2em;
115
+ block-size: 1.2em;
116
+ padding: 0;
117
+ border: 0;
118
+ border-radius: var(--gr-radius-pill);
119
+ background-color: transparent;
120
+ color: var(--gr-color-text-secondary);
121
+ font-family: inherit;
122
+ font-size: inherit;
123
+ line-height: 1;
124
+ cursor: pointer;
125
+ }
126
+
127
+ @media (hover: hover) {
128
+ .gr-tag-remove:hover {
129
+ background-color: var(--gr-color-border);
130
+ color: var(--gr-color-text);
131
+ }
132
+ }
133
+
134
+ .gr-tag-remove:focus-visible {
135
+ outline: var(--gr-focus-width) solid var(--gr-color-focus);
136
+ outline-offset: 1px;
137
+ }
138
+
139
+ // --- Точка ------------------------------------------------------------------
140
+
141
+ // Индикатор состояния без текста. Сам по себе он ничего не сообщает
142
+ // скринридеру, поэтому в разметке рядом обязана стоять подпись —
143
+ // хотя бы .gr-sr-only из griffincss-utils.
144
+ //
145
+ // Точка — чистая заливка, текста на ней нет, поэтому цвет берётся
146
+ // из --gr-badge-fill, а не из --gr-badge-color.
147
+ .gr-dot {
148
+ --gr-badge-fill: var(--gr-color-text-secondary);
149
+
150
+ display: inline-block;
151
+ inline-size: 0.6em;
152
+ block-size: 0.6em;
153
+ border-radius: var(--gr-radius-pill);
154
+ background-color: var(--gr-badge-fill);
155
+ vertical-align: middle;
156
+ }
157
+
158
+ @each $gr-name, $gr-value in $gr-badge-statuses {
159
+ .gr-dot-#{$gr-name} {
160
+ --gr-badge-fill: #{list.nth($gr-value, 2)};
161
+ }
162
+ }
@@ -0,0 +1,86 @@
1
+ // Griffincss UI — Breadcrumb
2
+ // Путь до текущей страницы. Разметка — <nav aria-label="Хлебные крошки">
3
+ // со списком <ol>: порядок здесь значащий, и нумерованный список сообщает
4
+ // об этом программе чтения с экрана.
5
+ //
6
+ // Разделитель рисует ::before у каждого пункта, кроме первого. Он объявлен
7
+ // декоративным (aria-hidden не нужен — сгенерированное содержимое в дерево
8
+ // доступности не попадает у современных движков), поэтому путь читается
9
+ // вслух как «Главная, Каталог, Товар», а не «Главная слэш Каталог».
10
+ //
11
+ // Символ разделителя — переменная --gr-breadcrumb-sep: её меняют на месте,
12
+ // не переписывая правило.
13
+
14
+ .gr-breadcrumb {
15
+ --gr-breadcrumb-sep: "/";
16
+
17
+ display: flex;
18
+ box-sizing: border-box;
19
+ flex-wrap: wrap;
20
+ align-items: center;
21
+ gap: calc(var(--gr-ui-gap) * 0.5);
22
+ margin: 0;
23
+ padding: 0;
24
+ color: var(--gr-color-text-secondary);
25
+ font-size: 0.875rem;
26
+ line-height: 1.4;
27
+ list-style: none;
28
+ }
29
+
30
+ // Пункт находится и по <li>, и по классу: список может быть развёрнут
31
+ // в <nav> со ссылками подряд, без обёрток.
32
+ .gr-breadcrumb > :where(li, .gr-breadcrumb-item) {
33
+ display: flex;
34
+ align-items: center;
35
+ gap: calc(var(--gr-ui-gap) * 0.5);
36
+ margin: 0;
37
+ padding: 0;
38
+ list-style: none;
39
+ }
40
+
41
+ .gr-breadcrumb > :where(li, .gr-breadcrumb-item) + :where(li, .gr-breadcrumb-item)::before {
42
+ content: var(--gr-breadcrumb-sep);
43
+ color: var(--gr-color-text-muted);
44
+ }
45
+
46
+ .gr-breadcrumb a {
47
+ color: inherit;
48
+ text-decoration: none;
49
+ }
50
+
51
+ @media (hover: hover) {
52
+ .gr-breadcrumb a:hover {
53
+ color: var(--gr-color-link-hover);
54
+ text-decoration-line: underline;
55
+ }
56
+ }
57
+
58
+ .gr-breadcrumb a:focus-visible {
59
+ outline: var(--gr-focus-width) solid var(--gr-color-focus);
60
+ outline-offset: 2px;
61
+ border-radius: calc(var(--gr-radius) * 0.5);
62
+ }
63
+
64
+ // Последний пункт — сама страница. Ссылкой он не бывает: вести ему некуда,
65
+ // поэтому в разметке это текст с aria-current="page", а здесь — цвет
66
+ // и вес, отличающие конец пути от его середины.
67
+ .gr-breadcrumb [aria-current="page"] {
68
+ color: var(--gr-color-text);
69
+ font-weight: 600;
70
+ }
71
+
72
+ // --- Свёрнутый путь ---------------------------------------------------------
73
+
74
+ // Длинный путь на узком экране: середина уезжает в горизонтальную прокрутку,
75
+ // а не переносится на три строки. Первый и последний пункты при этом
76
+ // остаются на виду.
77
+ .gr-breadcrumb-scroll {
78
+ flex-wrap: nowrap;
79
+ overflow: auto hidden;
80
+ white-space: nowrap;
81
+ scrollbar-width: thin;
82
+ }
83
+
84
+ .gr-breadcrumb-scroll > :where(li, .gr-breadcrumb-item) {
85
+ flex: none;
86
+ }
@@ -0,0 +1,216 @@
1
+ // Griffincss UI — Button
2
+ // Кнопка и её варианты. Класс ставится на любой элемент: <button>, <a>,
3
+ // <input type="submit"> — оформление одинаковое, семантику выбирает разметка.
4
+ //
5
+ // Ресет опционален, поэтому компонент не рассчитывает на него: box-sizing,
6
+ // font-family и обнуление полей задаются здесь.
7
+ //
8
+ // Цвета варианта живут в трёх локальных переменных — --gr-btn-bg, --gr-btn-fg,
9
+ // --gr-btn-border. Благодаря этому правила :hover и :active написаны один раз
10
+ // на все девять вариантов: они подмешивают к фону варианта его же цвет текста,
11
+ // а насколько это темнее или светлее — решает действующая тема.
12
+
13
+ @use 'sass:list';
14
+
15
+ .gr-btn {
16
+ --gr-btn-height: var(--gr-control-height);
17
+ --gr-btn-bg: var(--gr-color-surface-raised);
18
+ --gr-btn-fg: var(--gr-color-text);
19
+ --gr-btn-border: var(--gr-color-border);
20
+ --gr-btn-bg-hover: color-mix(in srgb, var(--gr-btn-bg) 88%, var(--gr-btn-fg));
21
+ --gr-btn-bg-active: color-mix(in srgb, var(--gr-btn-bg) 76%, var(--gr-btn-fg));
22
+
23
+ display: inline-flex;
24
+ box-sizing: border-box;
25
+ align-items: center;
26
+ justify-content: center;
27
+ gap: var(--gr-ui-gap);
28
+ min-block-size: var(--gr-btn-height);
29
+ margin: 0;
30
+ padding-block: 0;
31
+ padding-inline: var(--gr-control-padding-x);
32
+ border: var(--gr-border-width) solid var(--gr-btn-border);
33
+ border-radius: var(--gr-radius);
34
+ background-color: var(--gr-btn-bg);
35
+ color: var(--gr-btn-fg);
36
+ font-family: inherit;
37
+ font-size: var(--gr-control-font-size);
38
+ line-height: 1.2;
39
+ text-align: center;
40
+ text-decoration: none;
41
+ white-space: nowrap;
42
+ vertical-align: middle;
43
+ cursor: pointer;
44
+ transition:
45
+ background-color var(--gr-transition),
46
+ border-color var(--gr-transition),
47
+ color var(--gr-transition);
48
+ }
49
+
50
+ // Наведение — только там, где указатель есть на самом деле: на сенсорном
51
+ // экране :hover залипает после тапа и кнопка остаётся подсвеченной.
52
+ @media (hover: hover) {
53
+ .gr-btn:hover:not(:disabled, [aria-disabled="true"]) {
54
+ background-color: var(--gr-btn-bg-hover);
55
+ }
56
+ }
57
+
58
+ .gr-btn:active:not(:disabled, [aria-disabled="true"]) {
59
+ background-color: var(--gr-btn-bg-active);
60
+ }
61
+
62
+ .gr-btn:focus-visible {
63
+ outline: var(--gr-focus-width) solid var(--gr-color-focus);
64
+ outline-offset: 2px;
65
+ }
66
+
67
+ // aria-disabled оформляется наравне с :disabled: на <a> атрибута disabled
68
+ // не существует, а недоступная ссылка выглядеть должна так же.
69
+ .gr-btn:disabled,
70
+ .gr-btn[aria-disabled="true"] {
71
+ opacity: 0.55;
72
+ cursor: not-allowed;
73
+ }
74
+
75
+ // Ожидание. Кольцо загрузки приедет с модулем _spinner (волна 8c);
76
+ // здесь — то, что видно и без него: курсор и приглушение.
77
+ .gr-btn[aria-busy="true"] {
78
+ opacity: 0.7;
79
+ cursor: progress;
80
+ }
81
+
82
+ // --- Варианты ---------------------------------------------------------------
83
+
84
+ // Заливка. Парами: цвет заливки и чернила НА ней. Общего токена чернил
85
+ // у статусов нет намеренно — он был, и это ломалось: стоило оси оформления
86
+ // перекрасить акцент под свою пастель, как чернила акцента уезжали
87
+ // на зелёную и красную кнопки. Своя пара у каждого статуса позволяет стилю
88
+ // сделать заливку светлее, а чернила — темнее, не спрашивая у соседей.
89
+ //
90
+ // Предупреждение больше не исключение, а обычный член этого списка: его
91
+ // текстовый цвет в светлой теме тёмно-коричневый (иначе не читался бы
92
+ // текстом на белом), а прямоугольник такого цвета читается как «оливковый»,
93
+ // а не как «внимание». Ровно та же развилка теперь описана у всех четырёх.
94
+ $gr-btn-filled: (
95
+ primary: (var(--gr-color-accent), var(--gr-color-on-accent)),
96
+ success: (var(--gr-color-success-surface), var(--gr-color-on-success)),
97
+ warning: (var(--gr-color-warning-surface), var(--gr-color-on-warning)),
98
+ danger: (var(--gr-color-danger-surface), var(--gr-color-on-danger)),
99
+ info: (var(--gr-color-info-surface), var(--gr-color-on-info))
100
+ );
101
+
102
+ @each $gr-name, $gr-pair in $gr-btn-filled {
103
+ .gr-btn-#{$gr-name} {
104
+ --gr-btn-bg: #{list.nth($gr-pair, 1)};
105
+ --gr-btn-fg: #{list.nth($gr-pair, 2)};
106
+ --gr-btn-border: #{list.nth($gr-pair, 1)};
107
+ }
108
+ }
109
+
110
+ // Контурная: акцент линией и текстом, фон появляется при наведении.
111
+ .gr-btn-secondary {
112
+ --gr-btn-bg: transparent;
113
+ --gr-btn-fg: var(--gr-color-accent);
114
+ --gr-btn-border: var(--gr-color-accent);
115
+ }
116
+
117
+ // Без фона и границы. Граница остаётся объявленной прозрачной, иначе кнопка
118
+ // прыгала бы на пиксель рядом с обычной в одном ряду.
119
+ .gr-btn-ghost {
120
+ --gr-btn-bg: transparent;
121
+ --gr-btn-fg: var(--gr-color-text);
122
+ --gr-btn-border: transparent;
123
+ }
124
+
125
+ // Кнопка, выглядящая ссылкой. Подсветки фона у неё нет — меняется цвет,
126
+ // как у настоящей ссылки.
127
+ .gr-btn-link {
128
+ --gr-btn-bg: transparent;
129
+ --gr-btn-fg: var(--gr-color-link);
130
+ --gr-btn-border: transparent;
131
+ --gr-btn-bg-hover: transparent;
132
+ --gr-btn-bg-active: transparent;
133
+
134
+ padding-inline: 0;
135
+
136
+ // Линия объявлена длинными свойствами и берёт толщину у шрифта — та же
137
+ // линия, что у .gr-link. Сокращённое text-decoration сбросило бы
138
+ // text-decoration-thickness в initial, и подчёркивание кнопки-ссылки
139
+ // не совпадало бы с подчёркиванием настоящей ссылки рядом.
140
+ text-decoration-line: underline;
141
+ text-decoration-thickness: from-font;
142
+ text-underline-offset: 0.2em;
143
+ }
144
+
145
+ @media (hover: hover) {
146
+ .gr-btn-link:hover:not(:disabled, [aria-disabled="true"]) {
147
+ --gr-btn-fg: var(--gr-color-link-hover);
148
+ }
149
+ }
150
+
151
+ // --- Размеры ----------------------------------------------------------------
152
+
153
+ .gr-btn-sm {
154
+ --gr-btn-height: var(--gr-control-height-sm);
155
+
156
+ padding-inline: calc(var(--gr-control-padding-x) * 0.7);
157
+ font-size: 0.875rem;
158
+ }
159
+
160
+ .gr-btn-lg {
161
+ --gr-btn-height: var(--gr-control-height-lg);
162
+
163
+ padding-inline: calc(var(--gr-control-padding-x) * 1.4);
164
+ font-size: 1.125rem;
165
+ }
166
+
167
+ // --- Форма ------------------------------------------------------------------
168
+
169
+ // Во всю ширину родителя. display меняется на flex: inline-flex шириной
170
+ // в 100% всё равно оставил бы за собой строку.
171
+ .gr-btn-block {
172
+ display: flex;
173
+ inline-size: 100%;
174
+ }
175
+
176
+ // Квадратная — под одну иконку. Ширина берётся из той же переменной,
177
+ // что и высота, поэтому .gr-btn-icon.gr-btn-lg остаётся квадратом.
178
+ .gr-btn-icon {
179
+ min-inline-size: var(--gr-btn-height);
180
+ padding-inline: 0;
181
+ }
182
+
183
+ // --- Группа -----------------------------------------------------------------
184
+
185
+ // Слипшийся ряд. Соседние границы наезжают друг на друга на свою толщину,
186
+ // иначе на стыке получилась бы двойная линия.
187
+ .gr-btn-group {
188
+ display: inline-flex;
189
+ vertical-align: middle;
190
+ }
191
+
192
+ .gr-btn-group > .gr-btn {
193
+ border-radius: 0;
194
+ }
195
+
196
+ .gr-btn-group > .gr-btn:first-child {
197
+ border-start-start-radius: var(--gr-radius);
198
+ border-end-start-radius: var(--gr-radius);
199
+ }
200
+
201
+ .gr-btn-group > .gr-btn:last-child {
202
+ border-start-end-radius: var(--gr-radius);
203
+ border-end-end-radius: var(--gr-radius);
204
+ }
205
+
206
+ .gr-btn-group > .gr-btn + .gr-btn {
207
+ margin-inline-start: calc(-1 * var(--gr-border-width));
208
+ }
209
+
210
+ // Активная кнопка поднимается над соседями: иначе её граница и контур фокуса
211
+ // оказались бы наполовину закрыты следующей кнопкой ряда.
212
+ .gr-btn-group > .gr-btn:hover,
213
+ .gr-btn-group > .gr-btn:focus-visible {
214
+ position: relative;
215
+ z-index: 1;
216
+ }