@olddevs/ui 4.2.2 → 4.4.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.
Files changed (84) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/components/navigation/MegaMenu.d.ts +43 -23
  3. package/dist/components/navigation/MegaMenu.d.ts.map +1 -1
  4. package/dist/components/navigation/MegaMenu.js +110 -47
  5. package/dist/components/navigation/MegaMenu.js.map +1 -1
  6. package/dist/components/navigation/useMenuAim.d.ts +8 -8
  7. package/dist/components/navigation/useMenuAim.d.ts.map +1 -1
  8. package/dist/components/overlays/Menu.d.ts +4 -2
  9. package/dist/components/overlays/Menu.d.ts.map +1 -1
  10. package/dist/components/overlays/Menu.js.map +1 -1
  11. package/dist/editor/RichTextEditor.d.ts.map +1 -1
  12. package/dist/editor/RichTextEditor.js +38 -4
  13. package/dist/editor/RichTextEditor.js.map +1 -1
  14. package/dist/editor/core/handle.d.ts +1 -5
  15. package/dist/editor/core/handle.d.ts.map +1 -1
  16. package/dist/editor/core/handle.js +1 -13
  17. package/dist/editor/core/handle.js.map +1 -1
  18. package/dist/editor/core/handleWatchers.d.ts +6 -0
  19. package/dist/editor/core/handleWatchers.d.ts.map +1 -0
  20. package/dist/editor/core/handleWatchers.js +14 -0
  21. package/dist/editor/core/handleWatchers.js.map +1 -0
  22. package/dist/editor/core/tools.d.ts +7 -2
  23. package/dist/editor/core/tools.d.ts.map +1 -1
  24. package/dist/editor/core/tools.js +2 -1
  25. package/dist/editor/core/tools.js.map +1 -1
  26. package/dist/editor/features/toc/RichTextToc.d.ts +63 -0
  27. package/dist/editor/features/toc/RichTextToc.d.ts.map +1 -0
  28. package/dist/editor/features/toc/RichTextToc.js +372 -0
  29. package/dist/editor/features/toc/RichTextToc.js.map +1 -0
  30. package/dist/editor/features/toc/headings.d.ts +31 -0
  31. package/dist/editor/features/toc/headings.d.ts.map +1 -0
  32. package/dist/editor/features/toc/headings.js +25 -0
  33. package/dist/editor/features/toc/headings.js.map +1 -0
  34. package/dist/editor/features/toc/stickyOffset.d.ts +3 -0
  35. package/dist/editor/features/toc/stickyOffset.d.ts.map +1 -0
  36. package/dist/editor/features/toc/stickyOffset.js +14 -0
  37. package/dist/editor/features/toc/stickyOffset.js.map +1 -0
  38. package/dist/editor/index.d.ts +2 -0
  39. package/dist/editor/index.d.ts.map +1 -1
  40. package/dist/editor/index.js +1 -0
  41. package/dist/editor/index.js.map +1 -1
  42. package/dist/i18n/defaults.d.ts.map +1 -1
  43. package/dist/i18n/defaults.js +1 -0
  44. package/dist/i18n/defaults.js.map +1 -1
  45. package/dist/i18n/messages.d.ts +5 -0
  46. package/dist/i18n/messages.d.ts.map +1 -1
  47. package/dist/locales/ru.d.ts.map +1 -1
  48. package/dist/locales/ru.js +1 -0
  49. package/dist/locales/ru.js.map +1 -1
  50. package/docs/api/messages.en.md +743 -0
  51. package/docs/api/messages.md +1 -0
  52. package/docs/api/utilities.en.md +233 -0
  53. package/docs/components/content/rich-text-toc.en.md +130 -0
  54. package/docs/components/content/rich-text-toc.md +129 -0
  55. package/docs/components/index.en.md +3 -2
  56. package/docs/components/index.md +2 -1
  57. package/docs/components/inputs/rich-text-editor.en.md +52 -34
  58. package/docs/components/inputs/rich-text-editor.md +20 -2
  59. package/docs/components/navigation/mega-menu.en.md +86 -61
  60. package/docs/components/navigation/mega-menu.md +81 -56
  61. package/docs/components/overlays/menu.en.md +8 -4
  62. package/docs/components/overlays/menu.md +6 -3
  63. package/docs/conventions.en.md +318 -0
  64. package/docs/getting-started.en.md +5 -5
  65. package/docs/getting-started.md +4 -4
  66. package/docs/glossary.en.md +24 -23
  67. package/docs/glossary.md +23 -22
  68. package/docs/index.en.md +53 -0
  69. package/llms.txt +2 -1
  70. package/package.json +1 -1
  71. package/src/components/navigation/MegaMenu.tsx +300 -140
  72. package/src/components/navigation/useMenuAim.ts +19 -19
  73. package/src/components/overlays/Menu.tsx +12 -2
  74. package/src/editor/RichTextEditor.tsx +107 -3
  75. package/src/editor/core/handle.ts +2 -23
  76. package/src/editor/core/handleWatchers.ts +27 -0
  77. package/src/editor/core/tools.ts +9 -2
  78. package/src/editor/features/toc/RichTextToc.tsx +712 -0
  79. package/src/editor/features/toc/headings.ts +70 -0
  80. package/src/editor/features/toc/stickyOffset.ts +21 -0
  81. package/src/editor/index.ts +2 -0
  82. package/src/i18n/defaults.ts +1 -0
  83. package/src/i18n/messages.ts +5 -0
  84. package/src/locales/ru.ts +1 -0
@@ -12,9 +12,10 @@ story: src/components/navigation/MegaMenu.stories.tsx
12
12
  ## Кратко для AI
13
13
 
14
14
  `MegaMenu` — каталог магазина в духе Amazon: слева Столбец категорий, справа от наведённой Категории — её
15
- Витрина с колонками ссылок и любым своим содержимым. Это навигация по ссылкам, а не меню команд: разметка по
15
+ Полка с колонками ссылок и любым своим содержимым. Это навигация по ссылкам, а не меню команд: разметка по
16
16
  шаблону WAI-ARIA Disclosure Navigation (`nav`, кнопки с `aria-expanded`, настоящие `a`). Категории —
17
- `MegaMenuCategory` прямыми детьми, колонки — `MegaMenuGroup`, ссылки — `MegaMenuLink`. Тот же каталог
17
+ `MegaMenuCategory` (прямо или внутри своих компонентов, в том числе серверных), колонки — `MegaMenuGroup`,
18
+ ссылки — `MegaMenuLink`. Текущий раздел и текущая страница — проп `active`, как у `NavItem`. Тот же каталог
18
19
  открывается выпадающим оверлеем от кнопки: `MegaMenuTrigger` с `MegaMenuButton` и неизменным `MegaMenu`.
19
20
 
20
21
  ## Когда использовать
@@ -22,7 +23,7 @@ story: src/components/navigation/MegaMenu.stories.tsx
22
23
  - Каталог с десятком и больше разделов, у каждого — подразделы колонками: магазин, маркетплейс, справочный
23
24
  портал.
24
25
  - Разделы надо показать сразу, а подразделы — по наведению, не уводя пользователя со страницы.
25
- - В Витрине рядом со ссылками нужно своё содержимое: промо-карточка, картинка, подборка.
26
+ - В Полке рядом со ссылками нужно своё содержимое: промо-карточка, картинка, подборка.
26
27
 
27
28
  ## Когда не использовать
28
29
 
@@ -37,10 +38,10 @@ story: src/components/navigation/MegaMenu.stories.tsx
37
38
  import { MegaMenu, MegaMenuCategory, MegaMenuGroup, MegaMenuLink } from "@olddevs/ui";
38
39
 
39
40
  <MegaMenu aria-label="Каталог">
40
- <MegaMenuCategory id="computers" label="Компьютеры" href="/computers">
41
+ <MegaMenuCategory id="computers" label="Компьютеры" href="/computers" active>
41
42
  <MegaMenuGroup title="Ноутбуки" href="/computers/laptops">
42
43
  <MegaMenuLink href="/computers/laptops/gaming">Игровые</MegaMenuLink>
43
- <MegaMenuLink href="/computers/laptops/ultrabooks" current>
44
+ <MegaMenuLink href="/computers/laptops/ultrabooks" active>
44
45
  Ультрабуки
45
46
  </MegaMenuLink>
46
47
  </MegaMenuGroup>
@@ -57,62 +58,75 @@ import { MegaMenu, MegaMenuCategory, MegaMenuGroup, MegaMenuLink } from "@olddev
57
58
  | ------------------------------------------ | ------------ | ------------------------------------------------------------------------------------------ |
58
59
  | `aria-label` | — | Доступное имя навигации, обязательно: на странице с несколькими `nav` их различают по нему |
59
60
  | `value` / `defaultValue` / `onValueChange` | `null` | Открытая Категория (её `id`) или `null`, управляемо или нет |
60
- | `showcaseWidth` | `"40rem"` | Ширина Витрины, одна на все Категории: каталог не прыгает при переходе между ними |
61
+ | `showcaseWidth` | `"40rem"` | Ширина Полки, одна на все Категории: каталог не прыгает при переходе между ними |
61
62
  | `className` | — | Классы на `nav`; ширина Столбца по умолчанию — `w-64` |
62
63
 
63
- `MegaMenuCategory`: `id` — ключ для `value`; `label` — подпись в Столбце и имя Витрины; `icon`; `href` или
64
- `link` — адрес раздела целиком; `disabled`; дети — содержимое Витрины. У Категории с Витриной адрес становится
65
- заголовком-ссылкой в начале Витрины, у Категории без детей сама строка Столбца — ссылка. `link` — ссылка роутера
64
+ `MegaMenuCategory`: `id` — ключ для `value`; `label` — подпись в Столбце и имя Полки; `icon`; `href` или
65
+ `link` — адрес раздела целиком; `disabled`; `active` — текущая страница лежит в этом разделе; дети —
66
+ содержимое Полки. У Категории с Полкой адрес становится
67
+ заголовком-ссылкой в начале Полки, у Категории без детей сама строка Столбца — ссылка. `link` — ссылка роутера
66
68
  пустым элементом (`link={<Link href="/x" />}`), подпись уезжает внутрь; это отдельный проп, а не `asChild`,
67
- потому что дети Категории — её Витрина.
69
+ потому что дети Категории — её Полка.
68
70
 
69
- `MegaMenuGroup`: `title` — заголовок колонки, он же подпись списка; `href` делает заголовок ссылкой; дети —
70
- `MegaMenuLink`, каждая уезжает в свой пункт списка.
71
+ `active` у Категории ставит `aria-current="true"` и красит подпись акцентным цветом — тем же, что у текущей
72
+ ссылки. Открыта Полка или нет, отметка остаётся: текущий раздел задаёт маршрут, а не наведение. Открытая
73
+ Категория по-прежнему выделена фоном, так что у одной строки бывают оба признака. Пока ни одна Полка не открыта,
74
+ Tab приходит в Столбец на текущий раздел. Какой раздел текущий, решает потребитель, как с `active` у `NavItem`.
71
75
 
72
- `MegaMenuLink`: обычные пропы `a`, `icon`, `current` и `asChild`. `current` ставит `aria-current="page"` и вид
76
+ Категории не обязаны быть прямыми детьми `MegaMenu`: их можно разложить по своим компонентам и рендерить из
77
+ серверного компонента Next.js. Каждая Категория сама сообщает Столбцу о себе, а Столбец не разбирает детей по
78
+ типу — у серверного компонента тип элемента на клиенте другой. До гидратации, пока Категории о себе не сообщили,
79
+ остановка Tab стоит у каждой доступной Категории; после гидратации Столбец снова одна остановка.
80
+
81
+ `MegaMenuGroup`: `title` — заголовок колонки, он же подпись списка; `href` или `link` делают заголовок ссылкой
82
+ (`link` — ссылка роутера пустым элементом, как у Категории); дети — `MegaMenuLink`, каждая уезжает в свой пункт
83
+ списка, в том числе пришедшие фрагментом.
84
+
85
+ `MegaMenuLink`: обычные пропы `a`, `icon`, `active` и `asChild`. `active` ставит `aria-current="page"` и вид
73
86
  текущей: текущую страницу кит не вычисляет, правила сравнения адресов у каждого роутера свои. `asChild` выводит
74
- ссылку элементом роутера: `<MegaMenuLink asChild><Link href="/x">Подпись</Link></MegaMenuLink>`.
87
+ ссылку элементом роутера: `<MegaMenuLink asChild><Link href="/x">Подпись</Link></MegaMenuLink>`. Прежнее имя
88
+ `current` работает до 5.0 и помечено `@deprecated`: текущий пункт навигации в ките называется `active`.
75
89
 
76
- ## Наведение и Витрина
90
+ ## Наведение и Полка
77
91
 
78
- Наведение мышью на Категорию открывает её Витрину сразу, без задержки. Подсвечена ровно та Категория, чья
79
- Витрина открыта: подсветка идёт по `aria-expanded`, а не по `:hover`. Наведение на Категорию без Витрины
92
+ Наведение мышью на Категорию открывает её Полку сразу, без задержки. Подсвечена ровно та Категория, чья
93
+ Полка открыта: подсветка идёт по `aria-expanded`, а не по `:hover`. Наведение на Категорию без Полки
80
94
  закрывает открытую — с той же оговоркой про Прицел, что ниже.
81
95
 
82
- Витрина закрывается, когда курсор ушёл и со Столбца, и с Витрины, через 150 мс: короткий выход за край не
96
+ Полка закрывается, когда курсор ушёл и со Столбца, и с Полки, через 150 мс: короткий выход за край не
83
97
  сбрасывает каталог. Закрывают её и нажатие указателем вне каталога, и уход фокуса со страницы каталога.
84
98
 
85
- Содержимое Витрины монтируется только у открытой Категории: каталог из сотни ссылок и картинок не грузит всё
99
+ Содержимое Полки монтируется только у открытой Категории: каталог из сотни ссылок и картинок не грузит всё
86
100
  разом. Сам регион есть всегда, скрытым: на него указывает `aria-controls` кнопки.
87
101
 
88
- Витрина — потомок своей Категории в DOM, а не портал. Поэтому порядок Tab идёт «Категория → её ссылки → дальше
89
- по странице», и Витрина встаёт вплотную к Столбцу без расчёта позиции. Предок с `overflow: hidden` её обрежет:
102
+ Полка — потомок своей Категории в DOM, а не портал. Поэтому порядок Tab идёт «Категория → её ссылки → дальше
103
+ по странице», и Полка встаёт вплотную к Столбцу без расчёта позиции. Предок с `overflow: hidden` её обрежет:
90
104
  ставьте каталог туда, где рядом есть место.
91
105
 
92
- Витрина стоит со стороны конца строки — справа, а при письме справа налево слева. Если там не хватает места, а с
106
+ Полка стоит со стороны конца строки — справа, а при письме справа налево слева. Если там не хватает места, а с
93
107
  другой стороны хватает, она переходит на другую сторону; сторону кит меряет заранее и при смене размера окна,
94
- поэтому стрелка «к Витрине» сразу смотрит туда, где Витрина окажется.
108
+ поэтому стрелка «к Полке» сразу смотрит туда, где Полка окажется.
95
109
 
96
110
  ## Касание и узкий экран
97
111
 
98
112
  Касание наведения не знает, поэтому Категорию открывает само касание (`click`), без Прицела. Указатель с
99
- `pointerType` «touch» или «pen» не открывает Категорию наведением и не закрывает Витрину уходом: перо
113
+ `pointerType` «touch» или «pen» не открывает Категорию наведением и не закрывает Полку уходом: перо
100
114
  ведёт себя как палец. Повторное касание открытой Категории её не закрывает и никуда не ведёт: закрывает
101
- Витрину касание вне каталога или Escape.
115
+ Полку касание вне каталога или Escape.
102
116
 
103
117
  Мышь ведёт себя, как раньше: наведение, Прицел и закрытие через 150 мс остаются, пока экран широкий.
104
118
 
105
- На узком экране — меньше 640px, граница та же, что у подменю `Menu` и подменю Редактора, — Витрине некуда
119
+ На узком экране — меньше 640px, граница та же, что у подменю `Menu` и подменю Редактора, — Полке некуда
106
120
  встать сбоку: справа от Столбца её обрезал бы край окна. Поэтому каталог идёт «спуском»:
107
121
 
108
122
  - Столбец занимает всю ширину, подписи Категорий переносятся, а не обрезаются.
109
- - Открытая Категория (касанием, щелчком, Enter или Пробелом) заменяет Столбец своей Витриной. Витрина
123
+ - Открытая Категория (касанием, щелчком, Enter или Пробелом) заменяет Столбец своей Полкой. Полка
110
124
  начинается строкой «Назад» с названием Категории; если у Категории есть своя ссылка, название показывает она,
111
125
  строкой ниже, а не строка «Назад». Ниже — колонки ссылок, одна под другой.
112
126
  - «Назад» возвращает Столбец и ставит фокус на Категорию, из которой человек пришёл. Escape внутри
113
- Витрины работает как «Назад».
127
+ Полки работает как «Назад».
114
128
  - Фокус при спуске переходит на «Назад»: кнопка Категории скрыта вместе со Столбцом, и фокус на ней
115
- пропал бы. Стрелка в сторону Столбца в Витрине на узком экране ничего не делает: Столбца рядом нет.
129
+ пропал бы. Стрелка в сторону Столбца в Полке на узком экране ничего не делает: Столбца рядом нет.
116
130
  - Прицела на узком экране нет, наведение мышью Категорию не открывает, а уход мыши не отменяет спуск:
117
131
  заменять Столбец под курсором значило бы вырывать его из-под руки.
118
132
  - Разметка остаётся по шаблону disclosure: кнопка Категории сохраняет `aria-expanded` и `aria-controls`
@@ -123,25 +137,25 @@ import { MegaMenu, MegaMenuCategory, MegaMenuGroup, MegaMenuLink } from "@olddev
123
137
 
124
138
  Ширину кит читает через `matchMedia`, а не через CSS: от неё зависит состав разметки (прячется Столбец), а
125
139
  не только вид. До гидратации и на сервере хук возвращает «широкий» (ADR-0018): сервер ширины не знает, и
126
- разметка с него всегда обычная. На телефоне после гидратации Витрина, открытая `defaultValue`, один раз
140
+ разметка с него всегда обычная. На телефоне после гидратации Полка, открытая `defaultValue`, один раз
127
141
  перерисовывается спуском. На телефоне `nav` занимает всю ширину классом `max-sm:w-full`; `w-*` из `className` действует на
128
142
  широком экране.
129
143
 
130
144
  ## Прицел
131
145
 
132
- Курсор идёт от Категории к её Витрине наискосок и по пути задевает соседние Категории. Если бы наведение
133
- открывало их сразу, Витрина менялась бы у человека под рукой, и до ссылки было бы не дотянуться. Прицел смотрит,
134
- куда движется курсор: пока он идёт в сторону открытой Витрины, соседняя Категория наведение не перехватывает.
146
+ Курсор идёт от Категории к её Полке наискосок и по пути задевает соседние Категории. Если бы наведение
147
+ открывало их сразу, Полка менялась бы у человека под рукой, и до ссылки было бы не дотянуться. Прицел смотрит,
148
+ куда движется курсор: пока он идёт в сторону открытой Полки, соседняя Категория наведение не перехватывает.
135
149
 
136
150
  - Направление последнего шага курсора сравнивается с сектором от его прежней точки к двум углам ближнего края
137
- Витрины — это и есть треугольник Каменса из раздела «Откуда это». Шаг в сектор — курсор целится.
151
+ Полки — это и есть треугольник Каменса из раздела «Откуда это». Шаг в сектор — курсор целится.
138
152
  - Одно движение мимо прощается, два подряд — нет: тогда открывается Категория под курсором.
139
153
  - Курсор остановился на 300 мс — открывается Категория под ним, даже если он целился. Меню не «залипает».
140
- - Курсор дошёл до Витрины — отложенная смена отменяется.
154
+ - Курсор дошёл до Полки — отложенная смена отменяется.
141
155
  - Щелчок по Категории открывает её сразу, мимо Прицела. Клавиатура Прицела не знает вовсе.
142
156
  - Только мышь, и только на широком экране: касание и перо открывают Категорию сразу, а на узком экране
143
157
  Прицела нет вовсе.
144
- - Первое наведение, пока Витрины нет, открывает Категорию без всякой задержки: целиться ещё не во что.
158
+ - Первое наведение, пока Полки нет, открывает Категорию без всякой задержки: целиться ещё не во что.
145
159
 
146
160
  Модель та же, что у подменю react-aria (`Menu` с `SubmenuTrigger`), чтобы два меню кита вели себя одинаково.
147
161
  Отличий два. Смена Категории откладывается, а не гасится `pointer-events: none` на Столбце: иначе пропала бы
@@ -194,15 +208,20 @@ _Анимация из [поста Бена Каменса](https://bjk5.com/pos
194
208
  ## Клавиатура и имена
195
209
 
196
210
  - Столбец — одна остановка Tab. Стрелки вверх и вниз, Home и End ходят по Категориям; выключенные
197
- пропускаются. Вернувшись Tab'ом, пользователь попадает на ту Категорию, где был.
198
- - Enter, Пробел и стрелка в сторону Витрины открывают её и ставят фокус на первую ссылку. У Категории-ссылки
211
+ пропускаются. Вернувшись Tab'ом, пользователь попадает на ту Категорию, где был; в первый раз — на открытую,
212
+ иначе на текущий раздел (`active`), иначе на первую.
213
+ - Буква на Категории переводит фокус на следующую Категорию, чья подпись с неё начинается, по кругу; та же буква
214
+ ещё раз — на следующую. Буквы, набранные подряд, ищут начало подписи, пауза в полсекунды сбрасывает набранное.
215
+ Пробел посреди набора — часть подписи («Home and garden»), а не открытие Категории.
216
+ Буква с Ctrl, Cmd или Alt — сочетание клавиш, каталог её не трогает.
217
+ - Enter, Пробел и стрелка в сторону Полки открывают её и ставят фокус на первую ссылку. У Категории-ссылки
199
218
  Enter — переход.
200
- - Внутри Витрины работает обычный Tab. Стрелка в сторону Столбца возвращает фокус на Категорию, Escape — тоже,
201
- и закрывает Витрину. Tab за последней ссылкой уходит дальше по странице и закрывает Витрину.
202
- - Пока Витрина открыта, она следует за фокусом стрелок, как следует за наведением.
203
- - Стрелка «к Витрине» — та, что смотрит туда, где Витрина стоит: при письме справа налево и у края экрана
219
+ - Внутри Полки работает обычный Tab. Стрелка в сторону Столбца возвращает фокус на Категорию, Escape — тоже,
220
+ и закрывает Полку. Tab за последней ссылкой уходит дальше по странице и закрывает Полку.
221
+ - Пока Полка открыта, она следует за фокусом стрелок и букв, как следует за наведением.
222
+ - Стрелка «к Полке» — та, что смотрит туда, где Полка стоит: при письме справа налево и у края экрана
204
223
  стрелки меняются местами.
205
- - Скринридер слышит навигацию с именем из `aria-label`, Категории — кнопками «развёрнуто / свёрнуто», Витрину
224
+ - Скринридер слышит навигацию с именем из `aria-label`, Категории — кнопками «развёрнуто / свёрнуто», Полку
206
225
  — регионом с именем Категории, колонку — списком с именем заголовка.
207
226
 
208
227
  ## Выпадающий вид
@@ -242,10 +261,10 @@ import { ListIcon } from "@olddevs/ui/icons";
242
261
  - **Открытие.** Щелчок, Enter, Пробел и стрелка вниз на кнопке. Наведение каталог не открывает: он не должен
243
262
  выпрыгивать от случайного движения мыши.
244
263
  - **Фокус.** При открытии фокус встаёт на первую доступную Категорию (если одна из них уже открыта через `value` или
245
- `defaultValue` — на неё), Витрина закрыта до выбора. Дальше Столбец и Витрина работают как во встроенном виде.
264
+ `defaultValue` — на неё), Полка закрыта до выбора. Дальше Столбец и Полка работают как во встроенном виде.
246
265
  - **Закрытие.** Escape на Категории, нажатие вне каталога, повторный щелчок по кнопке, переход по любой ссылке
247
266
  (Категория, заголовок колонки, `MegaMenuLink`, в том числе ссылки роутера) и Tab за последним элементом.
248
- Первый Escape внутри Витрины закрывает только Витрину и возвращает фокус на её Категорию; оверлей закрывает
267
+ Первый Escape внутри Полки закрывает только Полку и возвращает фокус на её Категорию; оверлей закрывает
249
268
  следующий.
250
269
  - **Возврат фокуса.** На кнопку фокус возвращается при Escape и при повторном щелчке. После Tab за последним
251
270
  элементом фокус идёт дальше по странице, за кнопкой; Shift+Tab перед первым элементом возвращает на кнопку. После
@@ -253,10 +272,14 @@ import { ListIcon } from "@olddevs/ui/icons";
253
272
  - **Скринридер.** Кнопка — раскрыватель с `aria-expanded` и `aria-controls`, без `aria-haspopup`: оверлей — не
254
273
  меню и не диалог, а та же навигация с ссылками. Роли `dialog` у оверлея нет.
255
274
  - **Слои.** Оверлей лежит на слое `popover`: выше затемнения открытого `Dialog`, ниже подтверждения. Внутри окна
256
- первый Escape закрывает каталог, следующий — окно. Если фокус в открытой Витрине, самый первый Escape закрывает
275
+ первый Escape закрывает каталог, следующий — окно. Если фокус в открытой Полке, самый первый Escape закрывает
257
276
  только её и возвращает фокус на Категорию.
258
- - **Высота.** Оверлей не выше видимой области от кнопки до края окна. Столбец и Витрина прокручиваются каждый
259
- внутри себя (`overscroll-contain`), а не оверлей целиком: иначе рамка оверлея обрезала бы Витрину, которая
277
+ - **Узкий экран.** Уже 640px оверлей занимает всю ширину окна за вычетом отступов по 8px с краёв, а не сжимается по
278
+ самой длинной подписи: подписи переносятся, длинная не выталкивает каталог за край, и ширина не меняется при
279
+ спуске в Категорию. Спуск, «Назад» и Escape работают внутри оверлея так же, как во встроенном виде: первый
280
+ Escape в Полке возвращает Столбец, следующий закрывает каталог.
281
+ - **Высота.** Оверлей не выше видимой области от кнопки до края окна. Столбец и Полка прокручиваются каждый
282
+ внутри себя (`overscroll-contain`), а не оверлей целиком: иначе рамка оверлея обрезала бы Полку, которая
260
283
  стоит рядом со Столбцом. На iOS высота ограничена `min(24rem, 60dvh)`, как у панели `Menu`.
261
284
  - **Появление.** Плавное, по шкале движения кита; при `prefers-reduced-motion` без анимации.
262
285
  - **Сервер.** На сервере рисуется закрытая кнопка; оверлей монтируется в портале после открытия.
@@ -265,14 +288,16 @@ import { ListIcon } from "@olddevs/ui/icons";
265
288
 
266
289
  - Реализация: `src/components/navigation/MegaMenu.tsx`, Прицел — `src/components/navigation/useMenuAim.ts`.
267
290
  - Тесты: `tests/mega-menu.test.tsx` — роли и `aria-controls`, наведение и монтирование только открытой
268
- Витрины, закрытие с задержкой, roving tabindex, Enter, Escape и стрелки, Tab за последней ссылкой,
269
- Категория-ссылка, выключенная Категория, `current`, `asChild` и `link`, управляемое значение, нажатие вне
270
- каталога, рендер на сервере; `tests/mega-menu-aim.test.tsx` — Прицел на подменённой геометрии и поддельном
291
+ Полки, закрытие с задержкой, roving tabindex, Enter, Escape и стрелки, Tab за последней ссылкой,
292
+ Категория-ссылка, выключенная Категория, `active` и алиас `current`, `asChild` и `link` (у Категории и у
293
+ колонки), фрагменты в колонке, текущий раздел и остановка Tab, поиск по первой букве, Категории в своих
294
+ компонентах, управляемое значение, нажатие вне каталога, рендер на сервере; `tests/mega-menu-aim.test.tsx` — Прицел на подменённой геометрии и поддельном
271
295
  времени, переворот у края; `tests/mega-menu-touch.test.tsx` — касание и перо, подмена `matchMedia`, спуск
272
- вместо Столбца, «Назад» и фокус, Escape, подписи из каталога сообщений, рендер на сервере; браузерный тест `tests-visual/mega-menu.spec.ts` — настоящая мышь наискосок и вниз
273
- по Столбцу.
296
+ вместо Столбца, «Назад» и фокус, Escape, подписи из каталога сообщений, спуск у Категорий в своих
297
+ компонентах, рендер на сервере; браузерный тест `tests-visual/mega-menu.spec.ts` — настоящая мышь наискосок и
298
+ вниз по Столбцу, ширина выпадающего вида на телефоне.
274
299
  - Выпадающий вид: `tests/mega-menu-dropdown.test.tsx` — открытие щелчком и клавишами с фокусом на первой
275
- Категории, все пути закрытия и возврат фокуса, Escape в Витрине, `open` / `defaultOpen` / `onOpenChange`, слой
276
- `popover`, порядок Escape внутри `Dialog`, рендер на сервере.
300
+ Категории, все пути закрытия и возврат фокуса, Escape в Полке, `open` / `defaultOpen` / `onOpenChange`, слой
301
+ `popover`, порядок Escape внутри `Dialog`, спуск на узком экране, рендер на сервере.
277
302
  - История: `src/components/navigation/MegaMenu.stories.tsx` — в том числе `DropdownInTopBar`, мегаменю в шапке; на ширине
278
- телефона — `PhoneColumn` и `PhoneDrillDown`.
303
+ телефона — `PhoneColumn`, `PhoneDrillDown`, `PhoneDropdown` и `PhoneDropdownDrillDown`.
@@ -5,7 +5,7 @@ category: overlays
5
5
  status: stable
6
6
  source: src/components/overlays/Menu.tsx
7
7
  story: src/components/overlays/Menu.stories.tsx
8
- sourceHash: 473c4ffa0e0a
8
+ sourceHash: e6bb9a14d118
9
9
  ---
10
10
 
11
11
  # Menu
@@ -57,8 +57,11 @@ and `className`; `aria-label` is required if the button has no text, and with
57
57
  `iconOnly` it never does. `Menu` accepts the usual `AriaMenuProps`, plus `offset`,
58
58
  the gap between the panel and the button in pixels (default 6), and `placement`,
59
59
  the side where the panel opens (default for the root menu `bottom start`; also
60
- `bottom end`, `top start`, `top end`, `right top` and `left top`; the submenu
61
- default is described below).
60
+ `bottom end`, `top start`, `top end`, `end top`, `start top`, `right top` and
61
+ `left top`; the submenu default is described below). `end top` and `start top`
62
+ open beside the panel at the end or the start of the line, so they swap sides in
63
+ right-to-left writing; `right top` and `left top` don't depend on the writing
64
+ direction.
62
65
 
63
66
  `MenuItem` accepts the `AriaMenuItem` props, `icon`, `shortcut`, `tone`,
64
67
  `disabled` and `className`; pass the action in `onAction`. `MenuSection` accepts
@@ -72,7 +75,8 @@ the right, react-aria flips it to the left. In a right-to-left layout everything
72
75
  mirrored: the submenu opens to the left of its item. On a narrow screen (up to 639px wide)
73
76
  there is no room on either side, so the submenu opens below its item, over the
74
77
  parent menu. An explicit `placement` on the nested `Menu` wins over the default,
75
- phones included. The root menu opens as before, at `bottom start`. The Editor's
78
+ phones included; to pick the side yourself without breaking right-to-left
79
+ writing, use `end top` or `start top`. The root menu opens as before, at `bottom start`. The Editor's
76
80
  submenus follow the same rule.
77
81
 
78
82
  Moving the pointer diagonally toward a submenu neither closes it nor switches to a
@@ -54,8 +54,10 @@ button look props, `icon`, `children`, `disabled`, `aria-label` и `className`;
54
54
  `aria-label` обязателен, если у кнопки нет подписи; с `iconOnly` её нет всегда.
55
55
  `Menu` принимает обычные `AriaMenuProps`, плюс `offset` — отступ панели от кнопки
56
56
  в пикселях (умолчание 6) и `placement` — сторона раскрытия (умолчание корневого
57
- меню `bottom start`; ещё `bottom end`, `top start`, `top end`, `right top`,
58
- `left top`; умолчание подменю — ниже).
57
+ меню `bottom start`; ещё `bottom end`, `top start`, `top end`, `end top`,
58
+ `start top`, `right top`, `left top`; умолчание подменю — ниже). `end top` и
59
+ `start top` — сбоку по концу и по началу строки: при письме справа налево они
60
+ меняются местами. `right top` и `left top` от направления письма не зависят.
59
61
 
60
62
  `MenuItem` принимает props `AriaMenuItem`, `icon`, `shortcut`, `tone`, `disabled`
61
63
  и `className`; действие передавайте в `onAction`. `MenuSection` принимает
@@ -69,7 +71,8 @@ react-aria переворачивает подменю влево. При пис
69
71
  подменю раскрывается слева от пункта. На узком экране (до 639px шириной) места
70
72
  сбоку нет ни справа, ни слева, и подменю встаёт под своим пунктом, поверх
71
73
  родительского меню. Явный `placement` у вложенного `Menu` побеждает умолчание, в
72
- том числе на телефоне. Корневое меню раскрывается как прежде — `bottom start`.
74
+ том числе на телефоне; чтобы задать сторону самому и не сломать письмо справа
75
+ налево, берите `end top` или `start top`. Корневое меню раскрывается как прежде — `bottom start`.
73
76
  То же правило у подменю Редактора.
74
77
 
75
78
  Движение курсора к подменю по диагонали его не закрывает и не переключает на