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.
- package/LICENSE +21 -0
- package/dist/griffincss-ui-scoped.css +1 -0
- package/dist/griffincss-ui.css +1 -0
- package/dist/griffincss-ui.js +3 -0
- package/dist/griffincss-ui.js.map +1 -0
- package/package.json +53 -0
- package/scss/_accordion.scss +155 -0
- package/scss/_alert.scss +90 -0
- package/scss/_avatar.scss +90 -0
- package/scss/_badge.scss +162 -0
- package/scss/_breadcrumb.scss +86 -0
- package/scss/_button.scss +216 -0
- package/scss/_card.scss +110 -0
- package/scss/_choice.scss +202 -0
- package/scss/_drawer.scss +183 -0
- package/scss/_dropdown.scss +229 -0
- package/scss/_empty.scss +70 -0
- package/scss/_form.scss +236 -0
- package/scss/_keyframes.scss +39 -0
- package/scss/_link.scss +165 -0
- package/scss/_menu.scss +150 -0
- package/scss/_modal.scss +181 -0
- package/scss/_nav.scss +233 -0
- package/scss/_pagination.scss +142 -0
- package/scss/_progress.scss +188 -0
- package/scss/_skeleton.scss +95 -0
- package/scss/_spinner.scss +59 -0
- package/scss/_steps.scss +193 -0
- package/scss/_style-rules.scss +75 -0
- package/scss/_table.scss +141 -0
- package/scss/_tabs.scss +166 -0
- package/scss/_toast.scss +173 -0
- package/scss/_tooltip.scss +143 -0
- package/scss/griffincss-ui-scoped.scss +61 -0
- package/scss/griffincss-ui.scss +64 -0
- package/src/griffincss-ui.js +661 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Griffincss UI — Spinner
|
|
2
|
+
// Кольцо занятости. Три четверти окружности серые, одна — акцентная;
|
|
3
|
+
// вращается keyframe gr-spin.
|
|
4
|
+
//
|
|
5
|
+
// <span class="gr-spinner" role="status" aria-label="Загрузка"></span>
|
|
6
|
+
//
|
|
7
|
+
// Роль и подпись — в разметке: пустой <span> без них молчит для программы
|
|
8
|
+
// чтения с экрана, и человек, который не видит кольца, не узнаёт, что
|
|
9
|
+
// страница чего-то ждёт. Если рядом уже стоит видимое слово «Загрузка»,
|
|
10
|
+
// кольцу достаточно aria-hidden="true".
|
|
11
|
+
//
|
|
12
|
+
// Диаметр и толщина заданы в em, а не в rem: кольцо внутри кнопки должно
|
|
13
|
+
// быть по её кеглю, а не по корневому, — иначе в маленькой кнопке оно
|
|
14
|
+
// не помещается, а в крупной теряется.
|
|
15
|
+
|
|
16
|
+
.gr-spinner {
|
|
17
|
+
--gr-spinner-size: 1.5em;
|
|
18
|
+
--gr-spinner-thickness: 0.15em;
|
|
19
|
+
--gr-spinner-track: color-mix(in srgb, currentcolor 20%, transparent);
|
|
20
|
+
--gr-spinner-color: var(--gr-color-accent);
|
|
21
|
+
|
|
22
|
+
display: inline-block;
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
inline-size: var(--gr-spinner-size);
|
|
25
|
+
block-size: var(--gr-spinner-size);
|
|
26
|
+
border: var(--gr-spinner-thickness) solid var(--gr-spinner-track);
|
|
27
|
+
|
|
28
|
+
// Акцентной оставлена одна сторона: без разрыва в кольце вращение
|
|
29
|
+
// не читалось бы вовсе — равномерный круг выглядит неподвижным.
|
|
30
|
+
border-block-start-color: var(--gr-spinner-color);
|
|
31
|
+
border-radius: var(--gr-radius-pill);
|
|
32
|
+
vertical-align: middle;
|
|
33
|
+
animation: gr-spin 0.8s linear infinite;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.gr-spinner-sm {
|
|
37
|
+
--gr-spinner-size: 1em;
|
|
38
|
+
--gr-spinner-thickness: 0.12em;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.gr-spinner-lg {
|
|
42
|
+
--gr-spinner-size: 2.5em;
|
|
43
|
+
--gr-spinner-thickness: 0.18em;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Кольцо на цветной кнопке: дорожка и стрелка берут цвет её текста,
|
|
47
|
+
// иначе акцентное кольцо на акцентной заливке сливается с ней.
|
|
48
|
+
.gr-spinner-current {
|
|
49
|
+
--gr-spinner-color: currentcolor;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Вращение по настройке уменьшенной анимации замедляется, но не снимается.
|
|
53
|
+
// Остановленный индикатор занятости сообщает «зависло» — ровно обратное
|
|
54
|
+
// тому, зачем он поставлен.
|
|
55
|
+
@media (prefers-reduced-motion: reduce) {
|
|
56
|
+
.gr-spinner {
|
|
57
|
+
animation-duration: 2.4s;
|
|
58
|
+
}
|
|
59
|
+
}
|
package/scss/_steps.scss
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// Griffincss UI — Steps
|
|
2
|
+
// Шаги процесса: оформление заказа, мастер настройки, ход проверки.
|
|
3
|
+
// Разметка — <ol>: порядок здесь значащий, и нумерованный список сообщает
|
|
4
|
+
// об этом программе чтения с экрана.
|
|
5
|
+
//
|
|
6
|
+
// <ol class="gr-steps">
|
|
7
|
+
// <li class="gr-step gr-step-done">
|
|
8
|
+
// <span class="gr-step-marker" aria-hidden="true">✓</span>
|
|
9
|
+
// <span class="gr-step-label">Корзина</span>
|
|
10
|
+
// </li>
|
|
11
|
+
// <li class="gr-step" aria-current="step">
|
|
12
|
+
// <span class="gr-step-marker" aria-hidden="true">2</span>
|
|
13
|
+
// <span class="gr-step-label">Доставка</span>
|
|
14
|
+
// <span class="gr-step-note">Курьером до двери</span>
|
|
15
|
+
// </li>
|
|
16
|
+
// </ol>
|
|
17
|
+
//
|
|
18
|
+
// Состояний три: пройденный (.gr-step-done), текущий (aria-current="step")
|
|
19
|
+
// и ожидающий — он же состояние по умолчанию. Текущий находится по тому же
|
|
20
|
+
// признаку, по которому его находит программа чтения с экрана; класса
|
|
21
|
+
// для него нет намеренно.
|
|
22
|
+
//
|
|
23
|
+
// Знак в маркере кладут в разметку: галочка у пройденного шага и номер
|
|
24
|
+
// у остальных — это содержимое, а не оформление. Цвет остаётся вторым
|
|
25
|
+
// признаком поверх знака и позиции, а не единственным.
|
|
26
|
+
|
|
27
|
+
.gr-steps {
|
|
28
|
+
--gr-step-marker-size: 2rem;
|
|
29
|
+
|
|
30
|
+
display: flex;
|
|
31
|
+
box-sizing: border-box;
|
|
32
|
+
margin: 0;
|
|
33
|
+
padding: 0;
|
|
34
|
+
list-style: none;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Обёртка списка не должна ни добавлять полей, ни перебивать классы,
|
|
38
|
+
// поставленные на сам <li>: :where() держит специфичность нулевой.
|
|
39
|
+
.gr-steps > :where(li) {
|
|
40
|
+
display: flex;
|
|
41
|
+
flex: 1 1 0;
|
|
42
|
+
min-inline-size: 0;
|
|
43
|
+
margin: 0;
|
|
44
|
+
padding: 0;
|
|
45
|
+
list-style: none;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.gr-step {
|
|
49
|
+
--gr-step-color: var(--gr-color-text-muted);
|
|
50
|
+
--gr-step-line: var(--gr-color-border);
|
|
51
|
+
--gr-step-marker-fg: var(--gr-color-text-muted);
|
|
52
|
+
--gr-step-marker-bg: var(--gr-color-bg);
|
|
53
|
+
|
|
54
|
+
position: relative;
|
|
55
|
+
display: flex;
|
|
56
|
+
box-sizing: border-box;
|
|
57
|
+
flex: 1 1 0;
|
|
58
|
+
flex-direction: column;
|
|
59
|
+
align-items: center;
|
|
60
|
+
gap: calc(var(--gr-ui-gap) * 0.5);
|
|
61
|
+
min-inline-size: 0;
|
|
62
|
+
color: var(--gr-step-color);
|
|
63
|
+
text-align: center;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Линия — хвост шага, ведущий к следующему, и залита она у пройденного:
|
|
67
|
+
// «путь от этого шага дальше проделан». Принадлежность именно левому
|
|
68
|
+
// (в колонке — верхнему) выбрана не ради вида, а потому, что только у него
|
|
69
|
+
// длину линии считает сам CSS: низ его блока и есть верх промежутка,
|
|
70
|
+
// а вот сколько текста висит под маркером ПРЕДЫДУЩЕГО шага, правило,
|
|
71
|
+
// написанное на следующего, знать не может. Хвост, растущий вверх,
|
|
72
|
+
// упирался бы в границу своего блока и не доставал до соседнего маркера —
|
|
73
|
+
// ровно это и было видно в колонке.
|
|
74
|
+
.gr-step::after {
|
|
75
|
+
content: "";
|
|
76
|
+
position: absolute;
|
|
77
|
+
inset-block-start: calc(var(--gr-step-marker-size) / 2);
|
|
78
|
+
inset-inline: 50% -50%;
|
|
79
|
+
block-size: var(--gr-border-width);
|
|
80
|
+
margin-block-start: calc(var(--gr-border-width) * -0.5);
|
|
81
|
+
background-color: var(--gr-step-line);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// У последнего шага линии нет: вести ей некуда. Класс .gr-step встречается
|
|
85
|
+
// и на самом <li>, и на элементе внутри него — сняты оба случая.
|
|
86
|
+
.gr-steps > :last-child .gr-step::after,
|
|
87
|
+
.gr-steps > .gr-step:last-child::after {
|
|
88
|
+
content: none;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.gr-step-marker {
|
|
92
|
+
position: relative;
|
|
93
|
+
z-index: 1;
|
|
94
|
+
display: flex;
|
|
95
|
+
box-sizing: border-box;
|
|
96
|
+
flex: none;
|
|
97
|
+
align-items: center;
|
|
98
|
+
justify-content: center;
|
|
99
|
+
inline-size: var(--gr-step-marker-size);
|
|
100
|
+
block-size: var(--gr-step-marker-size);
|
|
101
|
+
border: var(--gr-border-width) solid currentcolor;
|
|
102
|
+
border-radius: var(--gr-radius-pill);
|
|
103
|
+
background-color: var(--gr-step-marker-bg);
|
|
104
|
+
color: var(--gr-step-marker-fg);
|
|
105
|
+
font-size: 0.875rem;
|
|
106
|
+
font-weight: 600;
|
|
107
|
+
line-height: 1;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.gr-step-label {
|
|
111
|
+
color: var(--gr-step-color);
|
|
112
|
+
font-size: 0.9375rem;
|
|
113
|
+
font-weight: 600;
|
|
114
|
+
line-height: 1.3;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.gr-step-note {
|
|
118
|
+
color: var(--gr-color-text-muted);
|
|
119
|
+
font-size: 0.8125rem;
|
|
120
|
+
font-weight: 400;
|
|
121
|
+
line-height: 1.35;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// --- Состояния --------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
// Пройденный: его хвост залит акцентом — путь от него дальше проделан.
|
|
127
|
+
.gr-step-done {
|
|
128
|
+
--gr-step-color: var(--gr-color-text);
|
|
129
|
+
--gr-step-line: var(--gr-color-accent);
|
|
130
|
+
--gr-step-marker-fg: var(--gr-color-accent);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Текущий. Маркер залит, а не обведён: из трёх состояний это единственное,
|
|
134
|
+
// которое нужно найти взглядом сразу. Хвост при этом остаётся серым —
|
|
135
|
+
// он ведёт к тому, до чего ещё не дошли.
|
|
136
|
+
.gr-step[aria-current] {
|
|
137
|
+
--gr-step-color: var(--gr-color-text);
|
|
138
|
+
--gr-step-marker-fg: var(--gr-color-on-accent);
|
|
139
|
+
--gr-step-marker-bg: var(--gr-color-accent);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.gr-step[aria-current] .gr-step-marker {
|
|
143
|
+
border-color: var(--gr-color-accent);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Шаг, до которого не дошли и не дойдут: отменённый или пропущенный.
|
|
147
|
+
.gr-step-disabled {
|
|
148
|
+
--gr-step-color: var(--gr-color-text-disabled);
|
|
149
|
+
--gr-step-marker-fg: var(--gr-color-text-disabled);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// --- Вертикальный вариант ---------------------------------------------------
|
|
153
|
+
|
|
154
|
+
// Колонка: маркер слева, подпись справа. На узком экране это единственный
|
|
155
|
+
// работающий вариант — горизонтальный ряд из четырёх шагов там нечитаем.
|
|
156
|
+
.gr-steps-vertical {
|
|
157
|
+
flex-direction: column;
|
|
158
|
+
gap: var(--gr-gap);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.gr-steps-vertical > :where(li) {
|
|
162
|
+
flex: none;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.gr-steps-vertical .gr-step {
|
|
166
|
+
flex: none;
|
|
167
|
+
flex-direction: row;
|
|
168
|
+
align-items: flex-start;
|
|
169
|
+
gap: var(--gr-ui-gap);
|
|
170
|
+
text-align: start;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// В колонке хвост идёт вниз от маркера до маркера следующего шага. Длину
|
|
174
|
+
// задают оба вылета сразу, а block-size: auto считает её из них: сколько бы
|
|
175
|
+
// текста ни висело под маркером, линия дотянется — это и есть довод
|
|
176
|
+
// в пользу хвоста, а не отрезка фиксированной длины.
|
|
177
|
+
.gr-steps-vertical .gr-step::after {
|
|
178
|
+
inset-block: var(--gr-step-marker-size) calc(var(--gr-gap) * -1);
|
|
179
|
+
inset-inline: calc(var(--gr-step-marker-size) / 2) auto;
|
|
180
|
+
inline-size: var(--gr-border-width);
|
|
181
|
+
block-size: auto;
|
|
182
|
+
margin-block-start: 0;
|
|
183
|
+
margin-inline-start: calc(var(--gr-border-width) * -0.5);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Подпись и пояснение встают в колонку: в строке они разъезжались бы
|
|
187
|
+
// по базовой линии маркера.
|
|
188
|
+
.gr-steps-vertical .gr-step-body {
|
|
189
|
+
display: flex;
|
|
190
|
+
flex-direction: column;
|
|
191
|
+
gap: calc(var(--gr-ui-gap) * 0.25);
|
|
192
|
+
padding-block-start: calc((var(--gr-step-marker-size) - 1.3em) / 2);
|
|
193
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Griffincss UI — Структурные правила оси оформления
|
|
2
|
+
// Здесь только то, что не выражается ручками: скругление, отступы, плотность
|
|
3
|
+
// и цвета компонент берёт из токенов сам, и они действуют в любой сборке.
|
|
4
|
+
//
|
|
5
|
+
// Модуль отдельный, а не секции в модулях компонентов, по двум причинам сразу.
|
|
6
|
+
// Правило проекта запрещает модулю селекторы по атрибуту, который стоит
|
|
7
|
+
// на <html>: внутри @scope (.griffin) селектор неявно получает :scope в начало
|
|
8
|
+
// и требует носитель атрибута внутри области. Поэтому griffincss-ui-scoped.scss
|
|
9
|
+
// этот модуль не подключает — там ось действует на уровне токенов, которые
|
|
10
|
+
// глобальны и объявлены вне области. Тем же приёмом обслужены @keyframes и тема.
|
|
11
|
+
//
|
|
12
|
+
// Правило, которое выражается ручкой, писать здесь нельзя: оно досталось бы
|
|
13
|
+
// только компонентам, а ручка достаётся всей странице.
|
|
14
|
+
|
|
15
|
+
@use 'griffincss-core/scss/style-config' as cfg;
|
|
16
|
+
|
|
17
|
+
@if cfg.gr-style-enabled(strict) {
|
|
18
|
+
// Шапка карточки перестаёт быть подложкой и становится линией: строгий
|
|
19
|
+
// стиль разделяет пояса границей, а не заливкой.
|
|
20
|
+
[data-gr-style="strict"] .gr-card-header {
|
|
21
|
+
background-color: transparent;
|
|
22
|
+
font-weight: 500;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Кнопка без подъёма: строгий стиль не приподнимает элементы управления
|
|
26
|
+
// над листом, он выстраивает их в одной плоскости.
|
|
27
|
+
[data-gr-style="strict"] .gr-btn {
|
|
28
|
+
box-shadow: none;
|
|
29
|
+
font-weight: 500;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Шапка таблицы отличается начертанием, а не заливкой.
|
|
33
|
+
[data-gr-style="strict"] .gr-table th {
|
|
34
|
+
font-weight: 600;
|
|
35
|
+
letter-spacing: 0.01em;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@if cfg.gr-style-enabled(compact) {
|
|
40
|
+
// Плотные ячейки с разделителями строк: журнальный стиль отвечает
|
|
41
|
+
// за объём информации, и строку от строки отделяет линия, а не воздух.
|
|
42
|
+
[data-gr-style="compact"] .gr-table th,
|
|
43
|
+
[data-gr-style="compact"] .gr-table td {
|
|
44
|
+
padding-block: calc(var(--gr-gap) * 0.35);
|
|
45
|
+
border-block-end: var(--gr-border-width) solid var(--gr-color-border);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
[data-gr-style="compact"] .gr-card-header,
|
|
49
|
+
[data-gr-style="compact"] .gr-card-footer {
|
|
50
|
+
padding-block: calc(var(--gr-gap) * 0.5);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
[data-gr-style="compact"] .gr-alert {
|
|
54
|
+
padding: calc(var(--gr-gap) * 0.6);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
[data-gr-style="compact"] .gr-menu-item {
|
|
58
|
+
padding-block: calc(var(--gr-gap) * 0.3);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@if cfg.gr-style-enabled(airy) {
|
|
63
|
+
// Граница уступает тени: воздушный стиль отделяет карточку от фона
|
|
64
|
+
// мягким подъёмом, а не линией.
|
|
65
|
+
[data-gr-style="airy"] .gr-card {
|
|
66
|
+
border-color: transparent;
|
|
67
|
+
box-shadow: var(--gr-shadow-md);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Отклик заметнее — это вторая половина «оживлённых анимаций»,
|
|
71
|
+
// первую даёт кривая в --gr-transition.
|
|
72
|
+
[data-gr-style="airy"] .gr-btn:active:not(:disabled, [aria-disabled="true"]) {
|
|
73
|
+
scale: 0.97;
|
|
74
|
+
}
|
|
75
|
+
}
|
package/scss/_table.scss
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Griffincss UI — Table
|
|
2
|
+
// Таблица данных и обёртка для горизонтальной прокрутки.
|
|
3
|
+
//
|
|
4
|
+
// Селекторы по элементам внутри .gr-table: у таблицы разметка фиксирована
|
|
5
|
+
// стандартом (thead / tbody / tr / th / td), придумывать ей классы нечего.
|
|
6
|
+
//
|
|
7
|
+
// Ресет опционален, поэтому border-collapse задаётся здесь: без него
|
|
8
|
+
// у ячеек остались бы двойные линии и зазоры border-spacing.
|
|
9
|
+
|
|
10
|
+
.gr-table {
|
|
11
|
+
inline-size: 100%;
|
|
12
|
+
border-collapse: collapse;
|
|
13
|
+
color: var(--gr-color-text);
|
|
14
|
+
text-align: start;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.gr-table th,
|
|
18
|
+
.gr-table td {
|
|
19
|
+
padding-block: calc(var(--gr-gap) * 0.5);
|
|
20
|
+
padding-inline: calc(var(--gr-gap) * 0.75);
|
|
21
|
+
border-block-end: var(--gr-border-width) solid var(--gr-color-border);
|
|
22
|
+
text-align: start;
|
|
23
|
+
vertical-align: top;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.gr-table th {
|
|
27
|
+
color: var(--gr-color-text-secondary);
|
|
28
|
+
font-size: 0.875rem;
|
|
29
|
+
font-weight: 600;
|
|
30
|
+
white-space: nowrap;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.gr-table thead th {
|
|
34
|
+
border-block-end: calc(var(--gr-border-width) * 2) solid var(--gr-color-border-strong);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.gr-table tfoot td {
|
|
38
|
+
border-block-end: 0;
|
|
39
|
+
color: var(--gr-color-text-secondary);
|
|
40
|
+
font-size: 0.875rem;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.gr-table caption {
|
|
44
|
+
padding-block-end: calc(var(--gr-gap) * 0.5);
|
|
45
|
+
color: var(--gr-color-text-secondary);
|
|
46
|
+
font-size: 0.875rem;
|
|
47
|
+
text-align: start;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// --- Варианты ---------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
// Чередование строк. nth-child(odd) считается по строкам тела, поэтому
|
|
53
|
+
// полосы не сбиваются от шапки и подвала.
|
|
54
|
+
//
|
|
55
|
+
// Полупрозрачный оттенок собственного цвета текста, а не готовый
|
|
56
|
+
// --gr-color-surface: таблица может лежать на чём угодно — на странице,
|
|
57
|
+
// на карточке, на утопленной панели, — и любой непрозрачный цвет
|
|
58
|
+
// совпадёт с одной из этих подложек и станет невидимым. Оттенок виден
|
|
59
|
+
// на всех и меняет направление вместе с темой сам: в светлой currentcolor
|
|
60
|
+
// тёмный, в тёмной — светлый.
|
|
61
|
+
.gr-table-striped tbody tr:nth-child(odd) {
|
|
62
|
+
background-color: color-mix(in srgb, currentcolor 5%, transparent);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Полная сетка: к горизонтальным линиям добавляются вертикальные
|
|
66
|
+
// и внешняя рамка.
|
|
67
|
+
.gr-table-bordered {
|
|
68
|
+
border: var(--gr-border-width) solid var(--gr-color-border);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.gr-table-bordered th,
|
|
72
|
+
.gr-table-bordered td {
|
|
73
|
+
border: var(--gr-border-width) solid var(--gr-color-border);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.gr-table-compact th,
|
|
77
|
+
.gr-table-compact td {
|
|
78
|
+
padding-block: calc(var(--gr-gap) * 0.25);
|
|
79
|
+
padding-inline: calc(var(--gr-gap) * 0.5);
|
|
80
|
+
font-size: 0.875rem;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Обратное компактному: воздуха больше стандартного. Для таблиц из трёх
|
|
84
|
+
// строк, где плотность не экономит ничего, а разреженность помогает читать.
|
|
85
|
+
.gr-table-relaxed th,
|
|
86
|
+
.gr-table-relaxed td {
|
|
87
|
+
padding-block: calc(var(--gr-gap) * 0.875);
|
|
88
|
+
padding-inline: calc(var(--gr-gap) * 1.25);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Подсветка строки под указателем — только там, где указатель есть:
|
|
92
|
+
// на сенсорном экране она залипала бы после касания.
|
|
93
|
+
//
|
|
94
|
+
// Слоем поверх фона строки (background-image), а не вместо него
|
|
95
|
+
// (background-color). У полосы и подсветки одна специфичность — обе
|
|
96
|
+
// «класс + tbody + tr + псевдокласс», — поэтому подсветка просто заменяла бы
|
|
97
|
+
// полосу: на нечётной строке полосатой таблицы разница между 5 % и 9 %
|
|
98
|
+
// почти не читается. Слой складывается с полосой, и шаг при наведении
|
|
99
|
+
// одинаков на любой строке.
|
|
100
|
+
//
|
|
101
|
+
// Оттенок скромнее полосы вдвое меньше, чем можно было бы: подложка
|
|
102
|
+
// у таблицы может быть какой угодно, а на белом фоне глаз замечает
|
|
103
|
+
// одну и ту же разницу заметно резче, чем на сером.
|
|
104
|
+
@media (hover: hover) {
|
|
105
|
+
.gr-table-hover tbody tr:hover {
|
|
106
|
+
background-image: linear-gradient(
|
|
107
|
+
color-mix(in srgb, currentcolor 6%, transparent),
|
|
108
|
+
color-mix(in srgb, currentcolor 6%, transparent)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// --- Прокрутка и липкая шапка -----------------------------------------------
|
|
114
|
+
|
|
115
|
+
// Широкая таблица не сжимается ниже своего содержимого, поэтому без обёртки
|
|
116
|
+
// вбок уезжала бы вся страница, а не одна таблица. Прокручиваемая область
|
|
117
|
+
// объявлена tabindex="0" в разметке — иначе до неё не добраться с клавиатуры.
|
|
118
|
+
.gr-table-wrap {
|
|
119
|
+
inline-size: 100%;
|
|
120
|
+
max-block-size: 100%;
|
|
121
|
+
overflow: auto;
|
|
122
|
+
overscroll-behavior-x: contain;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Шапка остаётся видна при прокрутке тела. Работает внутри .gr-table-wrap
|
|
126
|
+
// с заданной высотой; собственный фон обязателен — без него сквозь ячейки
|
|
127
|
+
// просвечивали бы уезжающие строки.
|
|
128
|
+
//
|
|
129
|
+
// Линию под шапкой рисует внутренняя тень, а не граница. При
|
|
130
|
+
// border-collapse: collapse границу между ячейками рисует таблица, а не сама
|
|
131
|
+
// ячейка, и с уехавшей липкой шапкой она не двигается — линия оставалась
|
|
132
|
+
// на исходном месте и пропадала из виду. Тень принадлежит элементу
|
|
133
|
+
// и едет вместе с ним.
|
|
134
|
+
.gr-table-sticky thead th {
|
|
135
|
+
position: sticky;
|
|
136
|
+
inset-block-start: 0;
|
|
137
|
+
z-index: 1;
|
|
138
|
+
border-block-end: 0;
|
|
139
|
+
background-color: var(--gr-color-bg);
|
|
140
|
+
box-shadow: inset 0 calc(-2 * var(--gr-border-width)) 0 var(--gr-color-border-strong);
|
|
141
|
+
}
|
package/scss/_tabs.scss
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// Griffincss UI — Tabs
|
|
2
|
+
// Вкладки. Без скрипта это разметка из ссылок-якорей и подряд идущих
|
|
3
|
+
// секций: страница остаётся читаемой и печатается целиком. Со скриптом
|
|
4
|
+
// (griffincss-ui.js) появляются role="tablist", стрелки, Home/End
|
|
5
|
+
// и roving tabindex, а неактивные панели прячутся атрибутом hidden.
|
|
6
|
+
//
|
|
7
|
+
// Панель скрывает платформа, не библиотека: [hidden] убирает её и с экрана,
|
|
8
|
+
// и из дерева доступности. Правила display: none в модуле нет — оно бы
|
|
9
|
+
// сделало панель невидимой и для программы чтения с экрана, но оставило бы
|
|
10
|
+
// в порядке обхода.
|
|
11
|
+
|
|
12
|
+
.gr-tabs {
|
|
13
|
+
display: flex;
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Ряд заголовков. Прокручивается вбок, когда вкладок больше, чем помещается:
|
|
19
|
+
// перенос на вторую строку разорвал бы линию, по которой ряд читается.
|
|
20
|
+
//
|
|
21
|
+
// Линия под рядом нарисована внутренней тенью, а не границей. Причина
|
|
22
|
+
// в том, как линия сочетается с активной вкладкой: граница ряда лежит
|
|
23
|
+
// снаружи его содержимого, и чтобы линия вкладки её перекрыла, вкладку
|
|
24
|
+
// пришлось бы подтягивать вниз отрицательным полем — а ряд прокручивается,
|
|
25
|
+
// и всё, что вылезло за его край, обрезается. Внутренняя тень рисуется
|
|
26
|
+
// в тех же пикселях, что и нижняя граница вкладки, поэтому вкладка
|
|
27
|
+
// перекрывает её просто тем, что нарисована позже.
|
|
28
|
+
.gr-tablist {
|
|
29
|
+
display: flex;
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
flex-wrap: nowrap;
|
|
32
|
+
align-items: stretch;
|
|
33
|
+
gap: calc(var(--gr-ui-gap) * 0.5);
|
|
34
|
+
margin: 0;
|
|
35
|
+
padding: 0;
|
|
36
|
+
box-shadow: inset 0 calc(-1 * var(--gr-border-width)) 0 0 var(--gr-color-border);
|
|
37
|
+
overflow-x: auto;
|
|
38
|
+
scrollbar-width: thin;
|
|
39
|
+
list-style: none;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Ряд на <ul>: обёртки <li> не должны ни добавлять полей, ни ломать
|
|
43
|
+
// растяжение вкладки по высоте ряда.
|
|
44
|
+
.gr-tablist > :where(li) {
|
|
45
|
+
display: flex;
|
|
46
|
+
margin: 0;
|
|
47
|
+
padding: 0;
|
|
48
|
+
list-style: none;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.gr-tab {
|
|
52
|
+
--gr-tab-fg: var(--gr-color-text-secondary);
|
|
53
|
+
|
|
54
|
+
display: inline-flex;
|
|
55
|
+
box-sizing: border-box;
|
|
56
|
+
flex: none;
|
|
57
|
+
align-items: center;
|
|
58
|
+
gap: var(--gr-ui-gap);
|
|
59
|
+
min-block-size: var(--gr-control-height);
|
|
60
|
+
margin: 0;
|
|
61
|
+
padding-block: 0;
|
|
62
|
+
padding-inline: var(--gr-control-padding-x);
|
|
63
|
+
border: 0;
|
|
64
|
+
|
|
65
|
+
// Линия объявлена прозрачной у всех вкладок сразу: появись она только
|
|
66
|
+
// у активной, ряд подпрыгивал бы на её толщину при переключении.
|
|
67
|
+
border-block-end: calc(var(--gr-border-width) * 2) solid transparent;
|
|
68
|
+
background-color: transparent;
|
|
69
|
+
color: var(--gr-tab-fg);
|
|
70
|
+
font-family: inherit;
|
|
71
|
+
font-size: var(--gr-control-font-size);
|
|
72
|
+
line-height: 1.2;
|
|
73
|
+
white-space: nowrap;
|
|
74
|
+
text-decoration: none;
|
|
75
|
+
cursor: pointer;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Подсветка наведения не касается выбранной вкладки: у правила с :hover
|
|
79
|
+
// специфичность заведомо выше, чем у правила состояния, и серая линия
|
|
80
|
+
// подсветки гасила бы акцентную линию выбора ровно в тот момент, когда
|
|
81
|
+
// на неё навели. Оба признака выбора перечислены в :not() — специфичность
|
|
82
|
+
// от этого не растёт: она берётся по самому весомому аргументу.
|
|
83
|
+
@media (hover: hover) {
|
|
84
|
+
.gr-tab:hover:not([aria-selected="true"], [aria-current], [aria-disabled="true"], :disabled) {
|
|
85
|
+
--gr-tab-fg: var(--gr-color-text);
|
|
86
|
+
|
|
87
|
+
border-block-end-color: var(--gr-color-border-strong);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Контур смещён внутрь: у вкладки нет своих полей сверху, и внешний
|
|
92
|
+
// контур срезался бы прокруткой ряда.
|
|
93
|
+
.gr-tab:focus-visible {
|
|
94
|
+
outline: var(--gr-focus-width) solid var(--gr-color-focus);
|
|
95
|
+
outline-offset: calc(-1 * var(--gr-focus-width));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Активная вкладка. Три признака сразу: цвет, вес и линия — по одной линии
|
|
99
|
+
// её видно и в чёрно-белой печати. aria-current — для варианта со ссылками,
|
|
100
|
+
// aria-selected — для варианта со скриптом.
|
|
101
|
+
.gr-tab[aria-selected="true"],
|
|
102
|
+
.gr-tab[aria-current] {
|
|
103
|
+
--gr-tab-fg: var(--gr-color-accent);
|
|
104
|
+
|
|
105
|
+
border-block-end-color: var(--gr-color-accent);
|
|
106
|
+
font-weight: 600;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.gr-tab:disabled,
|
|
110
|
+
.gr-tab[aria-disabled="true"] {
|
|
111
|
+
--gr-tab-fg: var(--gr-color-text-disabled);
|
|
112
|
+
|
|
113
|
+
cursor: not-allowed;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.gr-tab-panel {
|
|
117
|
+
box-sizing: border-box;
|
|
118
|
+
padding-block: var(--gr-gap);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Панель получает фокус от скрипта (tabindex="0"), чтобы после стрелок
|
|
122
|
+
// клавиатура оказывалась в содержимом вкладки, а не в конце страницы.
|
|
123
|
+
.gr-tab-panel:focus-visible {
|
|
124
|
+
outline: var(--gr-focus-width) solid var(--gr-color-focus);
|
|
125
|
+
outline-offset: calc(-1 * var(--gr-focus-width));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// --- Варианты ---------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
// Клавишами: активная вкладка выделена заливкой, а не линией. Годится
|
|
131
|
+
// для переключателей внутри карточки, где линия спорила бы с её границей.
|
|
132
|
+
.gr-tabs-pills .gr-tablist {
|
|
133
|
+
box-shadow: none;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.gr-tabs-pills .gr-tab {
|
|
137
|
+
min-block-size: var(--gr-control-height-sm);
|
|
138
|
+
border-block-end: 0;
|
|
139
|
+
border-radius: var(--gr-radius);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@media (hover: hover) {
|
|
143
|
+
.gr-tabs-pills .gr-tab:hover:not([aria-selected="true"], [aria-current], [aria-disabled="true"], :disabled) {
|
|
144
|
+
background-color: color-mix(in srgb, currentcolor 8%, transparent);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.gr-tabs-pills .gr-tab[aria-selected="true"],
|
|
149
|
+
.gr-tabs-pills .gr-tab[aria-current] {
|
|
150
|
+
background-color: var(--gr-color-accent);
|
|
151
|
+
color: var(--gr-color-on-accent);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Ряд во всю ширину: вкладки делят её поровну. Для двух-трёх коротких
|
|
155
|
+
// заголовков — на узком экране это удобнее прокрутки.
|
|
156
|
+
.gr-tabs-stretch .gr-tablist {
|
|
157
|
+
overflow-x: visible;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.gr-tabs-stretch .gr-tab {
|
|
161
|
+
flex: 1 1 0;
|
|
162
|
+
justify-content: center;
|
|
163
|
+
min-inline-size: 0;
|
|
164
|
+
padding-inline: calc(var(--gr-control-padding-x) * 0.5);
|
|
165
|
+
white-space: normal;
|
|
166
|
+
}
|