@boobooking/dashboard-ui-components 0.14.0 → 0.15.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/README.md CHANGED
@@ -65,42 +65,22 @@ Vue не ниже 3.5, ESM, сборщик, умеющий импорт CSS из
65
65
  Tailwind, `@tailwindcss/forms`, Inertia и какие-либо токены в CSS приложения
66
66
  пакету не нужны.
67
67
 
68
- Меню `DropdownButtonWithAction` стоит на Popover API браузера: Safari 17+,
69
- Chrome 114+, Firefox 125+. В браузерах старше меню видно в потоке всегда.
68
+ Меню `DropdownButtonWithAction` и `HamburgerMenu`, список `SelectSingle` и
69
+ календари `SelectDateInterval` стоят на Popover API браузера: Safari 17+,
70
+ Chrome 114+, Firefox 125+. В браузерах старше меню, список и календари
71
+ видны в потоке всегда.
70
72
 
71
73
  ## Компоненты
72
74
 
73
- Пакет экспортирует семнадцать компонентов: `Popup`, `Dot`, `PickDay`,
74
- `RussianMobileFilter`, `Search`, `SelectDateInterval`, `SelectSingle`,
75
- `SmallBadge`, `ErrorMessages`, `Closer`, `DownloadLink`,
76
- `ConfirmationModal`, `DropdownButtonWithAction`, `DataTable`,
75
+ Пакет экспортирует пятнадцать компонентов: `Dot`, `RussianMobileFilter`,
76
+ `Search`, `SelectDateInterval`, `SelectSingle`, `SmallBadge`,
77
+ `ErrorMessages`, `DownloadLink`, `ConfirmationModal`,
78
+ `DropdownButtonWithAction`, `HamburgerMenu`, `DataTable`,
77
79
  `NavigationMenuElement`, `PageCard`, `NotificationMessage` и плагин
78
80
  `dashboardUi`. В архив пакета (`files: ["dist"]`) исходники не попадают,
79
81
  поэтому контракт каждого компонента — здесь и в playground, а не в исходном
80
82
  коде.
81
83
 
82
- ### Popup
83
-
84
- Всплывающее меню с оверлеем, закрывающим по клику вне, и переходом на
85
- открытие/закрытие. Другие компоненты пакета используют его как обёртку над
86
- своим содержимым — некоторые напрямую, некоторые через внутренние
87
- компоненты.
88
-
89
- <popup v-model="isOpen" align="right">
90
- …содержимое меню…
91
- </popup>
92
-
93
- | Проп | Тип | По умолчанию | Описание |
94
- | --- | --- | --- | --- |
95
- | `modelValue` | `Boolean` | `false` | Открыто ли меню |
96
- | `align` | `String` | `"left"` | `"left"` или `"right"` — с какой стороны прижать меню |
97
-
98
- Событие: `update:modelValue` — при каждом открытии и закрытии, включая клик по
99
- оверлею.
100
-
101
- У `Popup` нет события `changed`, и это не пропуск — см. раздел
102
- «Почему у Popup и Dot нет события changed».
103
-
104
84
  ### Dot
105
85
 
106
86
  Индикатор-точка: красная или зелёная, с пульсацией или без.
@@ -113,26 +93,7 @@ Chrome 114+, Firefox 125+. В браузерах старше меню видн
113
93
  | `withPulse` | `Boolean` | `false` | Добавляет анимацию пульсации |
114
94
 
115
95
  Событий нет. Значения у `Dot` тоже нет — см. раздел
116
- «Почему у Popup и Dot нет события changed».
117
-
118
- ### PickDay
119
-
120
- Поле выбора одной даты на Pikaday, с ластиком очистки. Pikaday загружается
121
- в браузере при монтировании компонента, а не при загрузке пакета, поэтому
122
- календарь появляется, когда загрузка закончится.
123
-
124
- <pick-day v-model="date" placeholder-text="от" />
125
-
126
- | Проп | Тип | По умолчанию | Описание |
127
- | --- | --- | --- | --- |
128
- | `modelValue` | `String` | `""` | Текущая дата |
129
- | `withEraser` | `Boolean` | `true` | Показывать ли ластик очистки |
130
- | `placeholderText` | `String` | `""` | Текст подсказки в пустом поле |
131
- | `lang` | `String` | язык плагина | `"ru"` или `"en"`; календарь берёт язык при монтировании |
132
-
133
- События: `update:modelValue` — при выборе даты в календаре и при очистке
134
- (пустой строкой); `changed` — в тех же двух случаях, для использования наравне
135
- с остальными фильтрами.
96
+ «Почему у Dot нет события changed».
136
97
 
137
98
  ### RussianMobileFilter
138
99
 
@@ -179,7 +140,10 @@ Chrome 114+, Firefox 125+. В браузерах старше меню видн
179
140
 
180
141
  ### SelectDateInterval
181
142
 
182
- Пара `PickDay` под одним заголовком — интервал дат «от» и «до».
143
+ Два поля даты под одним заголовком — интервал дат «от» и «до». Дата
144
+ выбирается в календаре Pikaday, у каждого поля — ластик очистки. Pikaday
145
+ загружается в браузере при монтировании компонента, а не при загрузке
146
+ пакета, поэтому календари появляются, когда загрузка закончится.
183
147
 
184
148
  <select-date-interval
185
149
  header="Период"
@@ -199,9 +163,20 @@ Chrome 114+, Firefox 125+. В браузерах старше меню видн
199
163
  | `isLoading` | `Boolean` | `false` | Индикатор загрузки в заголовке |
200
164
  | `lang` | `String` | язык плагина | `"ru"` или `"en"`; действует и на подсказки полей, и на оба календаря |
201
165
 
202
- События: `update:dateFrom`, `update:dateTo` — на каждый выбор в
203
- соответствующем `PickDay`; `changed` — когда новое значение отличается от
204
- переданного пропса (отдельно для каждой из двух дат).
166
+ События: `update:dateFrom`, `update:dateTo` — на каждый выбор даты
167
+ в соответствующем поле и на его очистку ластиком (пустой строкой);
168
+ `changed` — когда новое значение отличается от переданного пропса
169
+ (отдельно для каждой из двух дат).
170
+
171
+ Клик по любому месту поля открывает его календарь. Календарь открывается
172
+ в верхнем слое браузера (Popover API): его не обрезает обёртка
173
+ с прокруткой. Он открывается под полем, левым краем по левому краю поля.
174
+ Не поместившись снизу, он открывается вверх, если сверху места больше;
175
+ не поместившись справа — правым краем по правому краю поля, если слева
176
+ места больше. Выбор даты, клик вне календаря, Escape, прокрутка страницы
177
+ и изменение размера окна закрывают календарь. Клик вне доходит до элемента
178
+ под курсором, поэтому календарь «до» открывается тем же кликом, что
179
+ закрывает «от».
205
180
 
206
181
  ### SelectSingle
207
182
 
@@ -238,6 +213,17 @@ Chrome 114+, Firefox 125+. В браузерах старше меню видн
238
213
  ставит фокус на него, стрелки продолжают от пункта под курсором. Подсвечен
239
214
  всегда один пункт — тот, на который последними указали клавиатура или мышь.
240
215
 
216
+ Список открывается в верхнем слое браузера (Popover API): его не обрезает
217
+ обёртка с прокруткой. Он открывается под полем, левым краем по левому
218
+ краю поля. Не поместившись снизу, он открывается вверх, если сверху места
219
+ больше; не поместившись справа — правым краем по правому краю поля, если
220
+ слева места больше. Длинный список ограничен высотой до края окна
221
+ и прокручивается внутри. Клик вне списка закрывает его и доходит до
222
+ элемента под курсором: соседний фильтр открывается тем же кликом.
223
+ Прокрутка страницы и изменение размера окна закрывают список, прокрутка
224
+ внутри списка — нет. Открытие другого списка, календаря или меню
225
+ закрывает предыдущее.
226
+
241
227
  ### SmallBadge
242
228
 
243
229
  Бейдж-пилюля с коротким текстом. Текст не переносится.
@@ -265,16 +251,6 @@ Chrome 114+, Firefox 125+. В браузерах старше меню видн
265
251
 
266
252
  Событий нет.
267
253
 
268
- ### Closer
269
-
270
- Кнопка-крестик закрытия. Размер и положение задаёт приложение классами на
271
- корне: без классов размера крестик получает размер SVG по умолчанию
272
- (300×300).
273
-
274
- <closer class="absolute top-0 right-0 w-8 h-8" @clicked="close" />
275
-
276
- Пропов нет. Событие: `clicked` — по клику.
277
-
278
254
  ### DownloadLink
279
255
 
280
256
  Ссылка на файл в виде бейджа с иконкой скачивания. Кликабелен весь бейдж.
@@ -327,58 +303,71 @@ ConfirmationModal нет v-model».
327
303
 
328
304
  ### DropdownButtonWithAction
329
305
 
330
- Кнопка с меню дополнительных действий: основная часть — слот `button`, стрелка
331
- справа открывает меню из пунктов пропа `actions`. Без пунктов стрелки нет, и
332
- кнопка скругляется с обеих сторон.
306
+ Кнопка из списка действий `actions`. Первое действие — основная кнопка,
307
+ остальные — меню за стрелкой справа. Одно действие — кнопка без стрелки;
308
+ без действий компонент ничего не рисует.
333
309
 
334
310
  <dropdown-button-with-action
335
311
  v-model="menuIsOpen"
336
312
  :actions="[
313
+ { label: 'Редактировать', href: route('user.edit', { user: row.getId() }) },
337
314
  { label: 'Поменять пароль', href: route('user.password.edit', { user: row.getId() }) },
338
- { label: 'Удалить', danger: true, onSelect: () => prepareUserDelete(row) },
315
+ { label: 'Удалить', color: 'red', onSelect: () => prepareUserDelete(row) },
339
316
  ]"
340
- >
341
- <template #button>…основное действие…</template>
342
- </dropdown-button-with-action>
317
+ />
343
318
 
344
319
  | Проп | Тип | По умолчанию | Описание |
345
320
  | --- | --- | --- | --- |
346
- | `actions` | `Array` | `[]` | Пункты меню, см. ниже |
321
+ | `actions` | `Array` | `[]` | Действия, см. ниже |
347
322
  | `modelValue` | `Boolean` | `false` | Открыто ли меню |
348
323
  | `lang` | `String` | язык плагина | `"ru"` или `"en"` |
349
324
 
350
- Пункт меню — объект одной из двух форм: переход (`href` без `onSelect`) или
325
+ Действие — объект одной из двух форм: переход (`href` без `onSelect`) или
351
326
  действие (`onSelect` без `href`). Поле со значением `undefined` считается
352
327
  отсутствующим.
353
328
 
354
329
  | Поле | Тип | Описание |
355
330
  | --- | --- | --- |
356
- | `label` | `String`, обязательно | Текст пункта |
331
+ | `label` | `String`, обязательно | Текст кнопки или пункта меню |
357
332
  | `href` | `String` | Переход: обычный клик уходит в `navigate` плагина `dashboardUi`, клик с Cmd/Ctrl/Shift и средней кнопкой — браузеру; без `navigate` — обычная ссылка |
358
- | `onSelect` | `Function` | Действие: вызывается без аргументов при выборе пункта |
359
- | `danger` | `Boolean` | Опасный пункт — красный; по умолчанию `false` |
333
+ | `onSelect` | `Function` | Действие: вызывается без аргументов |
334
+ | `color` | `"yellow"` или `"red"` | Цвет; без поля — обычный |
335
+
336
+ Основная кнопка работает как пункт меню: переход — ссылка, действие —
337
+ кнопка, и `onSelect` вызывает сам компонент — по клику, Enter и пробелу.
338
+ Своего события клика у кнопки нет: странице не нужно ловить клик и
339
+ разбираться в разметке компонента.
340
+
341
+ Цвет первого действия красит основную кнопку вместе со стрелкой, цвет
342
+ пункта меню — только этот пункт. Обычная кнопка — белая с серой рамкой,
343
+ жёлтая и красная — светлый фон, рамка и текст своего цвета. Обычный пункт
344
+ меню — серый текст, при подсветке серый фон; жёлтый и красный — текст
345
+ своего цвета, при подсветке светлый фон того же цвета.
360
346
 
361
347
  Неверный список Vue отмечает предупреждением о пропе в режиме разработки,
362
348
  а компонент не падает: элементы-необъекты пропускает, кнопка без функции
363
- `onSelect` только закрывает меню. `null` — пустой список.
349
+ `onSelect` по клику ничего не вызывает, пункт без неё только закрывает
350
+ меню. `null` — пустой список.
364
351
 
365
- Вид пунктов задаёт компонент: страница передаёт данные, а не разметку,
366
- и классов пунктам не даёт. Обычный пункт — серый текст, при подсветке —
367
- серый фон; опасный — красный фон, при подсветке темнее. Слота `actions`
368
- нет с версии 0.14.0 — см. «Обновление с 0.13».
352
+ Вид кнопки и пунктов задаёт компонент: страница передаёт данные, а не
353
+ разметку, и классов не даёт. Слота `button` и поля `danger` нет с версии
354
+ 0.15.0 — см. «Обновление с 0.14»; слота `actions` — с 0.14.0, см.
355
+ «Обновление с 0.13».
369
356
 
370
357
  Событие: `update:modelValue` — только когда меню открыл или закрыл сам
371
358
  компонент или браузер: клик по стрелке, клик вне меню, Escape, клик
372
- по пункту меню, прокрутка, изменение размера окна. Значение, пришедшее
359
+ по пункту меню, прокрутка, изменение размера окна, а ещё пропажа открытого
360
+ меню, когда действий осталось меньше двух. Значение, пришедшее
373
361
  от родителя, компонент принимает молча: ответное событие вернуло бы
374
362
  родителю его же решение. Без `v-model` меню работает само по себе.
375
363
 
376
364
  Меню открывается в верхнем слое браузера (Popover API): его не обрезает
377
365
  таблица с горизонтальной прокруткой, и оно стоит поверх страницы. Правый
378
- край меню — по правому краю стрелки; меню открывается вниз или вверх —
379
- где больше места, сужается у левого края окна и закрывается кликом
380
- по любому пункту, прокруткой страницы или таблицы и изменением размера
381
- окна. Открытие другого меню закрывает предыдущее.
366
+ край меню — по правому краю стрелки; меню открывается под стрелкой,
367
+ а если там не помещается — туда, где больше места, сужается у левого края
368
+ окна и закрывается кликом по любому пункту, прокруткой страницы или
369
+ таблицы и изменением размера окна. Открытие другого меню закрывает
370
+ предыдущее.
382
371
 
383
372
  Пока меню открыто, стрелки вверх и вниз переводят фокус по пунктам и
384
373
  страницу не прокручивают: вниз — на первый пункт, вверх — на последний,
@@ -388,6 +377,54 @@ ConfirmationModal нет v-model».
388
377
  курсором. Подсвечен всегда один пункт — тот, на который последними указали
389
378
  клавиатура или мышь.
390
379
 
380
+ ### HamburgerMenu
381
+
382
+ Кнопка ☰ с меню из пунктов пропа `actions` — меню профиля в шапке
383
+ дашборда. Кнопку, панель и пункты рисует компонент, страница передаёт
384
+ только пункты. Без пунктов кнопки нет.
385
+
386
+ <hamburger-menu :actions="menuActions" />
387
+
388
+ import { router } from '@inertiajs/vue3';
389
+
390
+ computed: {
391
+ menuActions() {
392
+ return [
393
+ { label: 'Администраторы', href: this.route('users') },
394
+ { label: 'Поменять пароль', href: this.route('my.password.edit') },
395
+ { label: 'Выйти', onSelect: () => router.delete(this.route('logout')) },
396
+ ];
397
+ },
398
+ },
399
+
400
+ | Проп | Тип | По умолчанию | Описание |
401
+ | --- | --- | --- | --- |
402
+ | `actions` | `Array` | `[]` | Пункты меню — в том же формате, что у `DropdownButtonWithAction` |
403
+ | `lang` | `String` | язык плагина | `"ru"` или `"en"` |
404
+
405
+ Пункты, их проверка и вид — как у `DropdownButtonWithAction`: переход —
406
+ `{ label, href }`, действие — `{ label, onSelect }`, цвет —
407
+ `color: 'yellow'` или `color: 'red'`. Выход запросом DELETE — действие:
408
+ Inertia пакет не импортирует, `router.delete` вызывает страница.
409
+
410
+ Событий и `v-model` нет: меню открывают и закрывают кнопка, клик вне меню,
411
+ Escape, клик по пункту, прокрутка и изменение размера окна. Меню
412
+ открывается в верхнем слое браузера (Popover API), правым краем по правому
413
+ краю кнопки, под ней, а если там не помещается — туда, где больше места.
414
+ Открытие другого меню закрывает предыдущее.
415
+
416
+ Это исключение из правила «состояние, которое компонент меняет сам, — через
417
+ `v-model`»: шапке не нужно ни открывать меню извне, ни знать, открыто ли
418
+ оно. Меню строк таблицы на `DropdownButtonWithAction` страница держит под
419
+ `v-model`, чтобы знать, какая строка выбрана; у меню профиля такой нужды нет.
420
+ `v-model`, переданный на `<hamburger-menu>`, в контракт не входит. Атрибуты
421
+ с `<hamburger-menu>` — `class`, `style`, `data-*` — ложатся на обёртку
422
+ кнопки.
423
+
424
+ Клавиатура и мышь — как в меню `DropdownButtonWithAction`: пока меню
425
+ открыто, стрелки вверх и вниз переводят фокус по пунктам, Enter выбирает
426
+ пункт под фокусом; подсветка пункта — это фокус, и мышь ведёт тот же фокус.
427
+
391
428
  ### DataTable
392
429
 
393
430
  Список данных: бейдж «Найдено X: N», таблица в карточке с горизонтальной
@@ -667,15 +704,16 @@ sm:rounded-lg`, `box-sizing: border-box`. Отступов у неё нет: ф
667
704
  | `ConfirmationModal` — кнопка отмены | Отмена | Cancel |
668
705
  | `DownloadLink` — подпись | Скачать | Download |
669
706
  | `DropdownButtonWithAction` — подпись стрелки для скринридера | Открыть меню | Open menu |
670
- | `PickDay` — месяцы, дни недели, кнопки календаря | по-русски | in English |
707
+ | `HamburgerMenu` — подпись кнопки для скринридера | Открыть меню | Open menu |
708
+ | `SelectDateInterval` — месяцы, дни недели, кнопки календарей | по-русски | in English |
671
709
  | `SelectDateInterval` — подсказки полей | от / до | from / to |
672
710
  | `DataTable` — пагинация | Предыдущая / Следующая / Показаны результаты X - Y из Z | Previous / Next / Showing X - Y of Z results |
673
711
  | `PageCard` — доступное имя крестика | Назад | Back |
674
712
  | `NotificationMessage` — подпись крестика для скринридера | Закрыть | Close |
675
713
 
676
714
  Формат даты — `дд.мм.гггг` в обоих языках: это формат значения, а не текст.
677
- Неделя начинается с понедельника. Календарь `PickDay` берёт язык при
678
- монтировании: смена языка после монтирования в нём не отражается.
715
+ Неделя начинается с понедельника. Календари `SelectDateInterval` берут язык
716
+ при монтировании: смена языка после монтирования в них не отражается.
679
717
 
680
718
  ## SSR
681
719
 
@@ -686,8 +724,8 @@ Inertia SSR), а серверная разметка гидратируется
686
724
  - `PageCard` на сервере рисует крестик, только если задан `fallback-url`:
687
725
  адрес возврата известен только в браузере, и крестик, ведущий на него,
688
726
  появляется после монтирования;
689
- - `PickDay` загружает Pikaday в браузере при монтировании: календарь
690
- появляется, когда загрузка закончится;
727
+ - `SelectDateInterval` загружает Pikaday в браузере при монтировании:
728
+ календари появляются, когда загрузка закончится;
691
729
  - остальные компоненты рендерятся на сервере так же, как в браузере.
692
730
 
693
731
  ## Правила API пакета
@@ -706,9 +744,9 @@ Inertia SSR), а серверная разметка гидратируется
706
744
  для него это и есть обычный `v-model`.
707
745
  - Булев проп называется `is*`, если описывает состояние компонента
708
746
  (`isRequired`, `isLoading`), и `with*`, если описывает необязательную
709
- возможность (`withEraser` у `PickDay`, `withPulse` у `Dot`).
747
+ возможность (`withPulse` у `Dot`).
710
748
  - Проп-перечисление — ограниченный набор строковых значений, как `color` у
711
- `Dot` или `align` у `Popup`, — обязательно имеет `validator`,
749
+ `Dot` или `variant` у `DataTable`, — обязательно имеет `validator`,
712
750
  отклоняющий значения вне списка.
713
751
  - Компонент не падает ни на каком значении пропа. `null` и `undefined` там,
714
752
  где ждут строку, объект или массив, он считает пустыми: пустой строкой,
@@ -727,8 +765,11 @@ Inertia SSR), а серверная разметка гидратируется
727
765
  `followLink($event, href)` миксина `withNavigation`, который отдаёт его
728
766
  `navigate` плагина. Ни Inertia, ни роутер пакет не импортирует. Исключение —
729
767
  `DownloadLink`: это обычная ссылка браузера, скачивающая файл, и через
730
- `navigate` она не идёт. Через `navigate` ходят `DataTable` (его пагинация) и
731
- `NavigationMenuElement`.
768
+ `navigate` она не идёт. Через `navigate` ходят `DataTable` (его пагинация),
769
+ `NavigationMenuElement`, основная кнопка-переход и пункты-переходы
770
+ `DropdownButtonWithAction` и пункты-переходы `HamburgerMenu`. Крестик
771
+ `PageCard` — кнопка, а не ссылка: адрес возврата он тоже отдаёт
772
+ `navigate`.
732
773
  - Компонент обращается к `window`, `document` и другим API браузера только
733
774
  в `mounted()`, `beforeUnmount()` и обработчиках событий; загрузка модуля,
734
775
  `data()`, `computed` и рендер работают без браузера. Так пакет
@@ -736,31 +777,25 @@ Inertia SSR), а серверная разметка гидратируется
736
777
  `tests/ssr.test.js` и `tests/hydration.test.js`: каждый экспортируемый
737
778
  компонент обязан иметь фикстуру в `tests/ssrFixtures.js`.
738
779
 
739
- ## Почему у Popup и Dot нет события changed
780
+ ## Почему у Dot нет события changed
740
781
 
741
782
  `changed` означает не «значение изменилось», а «пользователь закончил
742
783
  выбор, можно обновлять выдачу» — фильтры дашборда эмитят его как сигнал к
743
784
  перезапросу данных.
744
785
 
745
- У `Popup` такого момента нет: он не хранит значение фильтра, а лишь
746
- управляет видимостью меню, и открытие меню — не повод перезагружать
747
- данные. Момент, когда выбор действительно завершён, отслеживает
748
- компонент, который использует `Popup` — сам или через внутренние
749
- компоненты пакета, — `changed` эмитит он.
750
-
751
- У `Dot` события нет по другой причине: у него нет значения вообще, поэтому
752
- эмитить не о чем.
786
+ У `Dot` такого момента нет: у него нет значения вообще, поэтому эмитить
787
+ не о чем.
753
788
 
754
- Отсутствие `changed` у `Popup` и `Dot` — сознательное решение, а не
755
- пропуск.
789
+ Отсутствие `changed` у `Dot` — сознательное решение, а не пропуск.
756
790
 
757
791
  ## Почему у ConfirmationModal нет v-model
758
792
 
759
793
  Правило пакета — значение через `v-model` — относится к состоянию, которое
760
- компонент меняет сам: `Popup` закрывается по клику вне, `SelectSingle` меняет
761
- выбор. `ConfirmationModal` свою видимость не меняет никогда. Обе кнопки только
762
- сообщают родителю решение пользователя, а закрыть модалку, оставить её открытой
763
- на время запроса или открыть следующую — решает родитель.
794
+ компонент меняет сам: меню `DropdownButtonWithAction` закрывается по клику
795
+ вне, `SelectSingle` меняет выбор. `ConfirmationModal` свою видимость не меняет
796
+ никогда. Обе кнопки только сообщают родителю решение пользователя, а закрыть
797
+ модалку, оставить её открытой на время запроса или открыть следующую — решает
798
+ родитель.
764
799
 
765
800
  `v-model` обещал бы двустороннюю связь, которой нет, поэтому видимость
766
801
  передаётся пропом `isOpen`.
@@ -769,7 +804,7 @@ Inertia SSR), а серверная разметка гидратируется
769
804
 
770
805
  npm run playground
771
806
 
772
- Поднимает демо-страницы со всеми семнадцатью компонентами в двух окружениях:
807
+ Поднимает демо-страницы со всеми пятнадцатью компонентами в двух окружениях:
773
808
  голом (без Tailwind и стилей приложения) и в окружении приложения (с
774
809
  Tailwind, `@tailwindcss/forms` и типографикой, как у потребителя). Это
775
810
  основной способ посмотреть, как компоненты выглядят и ведут себя, а не
@@ -785,6 +820,147 @@ Tailwind, `@tailwindcss/forms` и типографикой, как у потре
785
820
  Правила номера телефона в `RussianMobileFilter` — российские; язык текстов
786
821
  задаётся, см. «Языки».
787
822
 
823
+ ## Обновление с 0.14
824
+
825
+ - Меню профиля в шапке переходит с `Popup` на `HamburgerMenu`: кнопку ☰,
826
+ панель и пункты рисует компонент, страница передаёт только пункты. Было:
827
+
828
+ <div class="relative">
829
+ <div>
830
+ <button
831
+ @click="menuDropdownOpen = !menuDropdownOpen"
832
+ :class="{ 'z-20': menuDropdownOpen }"
833
+ class="relative inline-flex items-center justify-center p-2 rounded-md text-gray-400 …"
834
+ aria-label="Main menu"
835
+ aria-expanded="false"
836
+ >
837
+ <svg class="h-6 w-6" …>…</svg>
838
+ </button>
839
+ </div>
840
+ <popup class="origin-top-right right-0" v-model="menuDropdownOpen" align="right">
841
+ <div class="mt-1 w-56 rounded-md shadow-lg bg-white ring-1 ring-black/5" role="menu" …>
842
+ <Link :href="route('users')" as="button" class="…" role="menuitem">Администраторы</Link>
843
+ <Link :href="route('logout')" method="delete" as="button" class="…" role="menuitem">Выйти</Link>
844
+ </div>
845
+ </popup>
846
+ </div>
847
+
848
+ Стало:
849
+
850
+ <hamburger-menu :actions="menuActions" />
851
+
852
+ import { router } from '@inertiajs/vue3';
853
+
854
+ computed: {
855
+ menuActions() {
856
+ return [
857
+ { label: 'Администраторы', href: this.route('users') },
858
+ { label: 'Выйти', onSelect: () => router.delete(this.route('logout')) },
859
+ ];
860
+ },
861
+ },
862
+
863
+ Пункты собираются в `computed`: в шаблоне импорт `router` недоступен.
864
+ Выход запросом DELETE — действие с `router.delete`, тот же вызов, что
865
+ делал `<Link method="delete">`. `menuDropdownOpen` и импорт `Popup`
866
+ больше не нужны.
867
+ - Пункты-переходы `HamburgerMenu` — ссылки `<a href>`, а не `<Link>`:
868
+ обычный клик уходит в `navigate` плагина `dashboardUi` (для Inertia —
869
+ `router.visit`), Cmd-клик и средняя кнопка открывают новую вкладку. Без
870
+ `navigate` переход идёт полной загрузкой страницы — подключите его
871
+ к плагину (см. «Подключение к приложению»).
872
+ - `Popup`, `PickDay` и `Closer` больше не экспортируются: это внутренние
873
+ части `SelectSingle`, `SelectDateInterval` и `PageCard`. Импорт любого
874
+ из них ломает сборку: «… is not exported by …».
875
+ - Список `SelectSingle` и календари `SelectDateInterval` открываются
876
+ в верхнем слое браузера, как меню. Клик вне закрывает их и доходит до
877
+ элемента под курсором, прокрутка страницы закрывает, длинный список
878
+ прокручивается внутри. Без Popover API (Safari до 17, Chrome до 114,
879
+ Firefox до 125) они видны всегда.
880
+ - Меню `DropdownButtonWithAction` и `HamburgerMenu` открывается под
881
+ кнопкой, а вверх — только если снизу не помещается и сверху места
882
+ больше. В 0.14 меню в нижней половине окна открывалось вверх всегда.
883
+ - `DropdownButtonWithAction` строится из списка действий: первое действие —
884
+ основная кнопка, остальные — меню. Слота `button` нет: переданный слот
885
+ не рисуется, а в режиме разработки в консоль уходит предупреждение.
886
+ Отступы и шрифт основной кнопки задаёт компонент (`px-4 py-2 text-sm
887
+ font-medium`). Было:
888
+
889
+ <dropdown-button-with-action
890
+ :actions="[
891
+ { label: 'Поменять пароль', href: route('user.password.edit', { user: row.getId() }) },
892
+ { label: 'Удалить', danger: true, onSelect: () => prepareUserDelete(row) },
893
+ ]"
894
+ >
895
+ <template v-slot:button>
896
+ <Link class="block px-4 py-2 text-sm font-medium text-gray-700" :href="route('user.edit', { user: row.getId() })">
897
+ Редактировать
898
+ </Link>
899
+ </template>
900
+ </dropdown-button-with-action>
901
+
902
+ Стало:
903
+
904
+ <dropdown-button-with-action
905
+ :actions="[
906
+ { label: 'Редактировать', href: route('user.edit', { user: row.getId() }) },
907
+ { label: 'Поменять пароль', href: route('user.password.edit', { user: row.getId() }) },
908
+ { label: 'Удалить', color: 'red', onSelect: () => prepareUserDelete(row) },
909
+ ]"
910
+ />
911
+
912
+ - Кнопка без меню — одно действие. Было:
913
+
914
+ <dropdown-button-with-action>
915
+ <template v-slot:button>
916
+ <Link class="block px-4 py-2 text-sm font-medium text-gray-700" :href="showGroupLink(row)">
917
+ Посмотреть
918
+ </Link>
919
+ </template>
920
+ </dropdown-button-with-action>
921
+
922
+ Стало:
923
+
924
+ <dropdown-button-with-action :actions="[{ label: 'Посмотреть', href: showGroupLink(row) }]" />
925
+
926
+ - Основное действие-функция больше не ловится на корне компонента:
927
+ `onSelect` первого действия вызывает сам компонент — по клику, Enter и
928
+ пробелу. Было:
929
+
930
+ <dropdown-button-with-action
931
+ :actions="[{ label: 'Зафиксировать как неотправленное', onSelect: () => askUnsent(row) }]"
932
+ v-on:click="retryFromDropdown($event, row)"
933
+ >
934
+ <template v-slot:button>
935
+ <span class="block px-2 py-1 text-sm text-gray-700">Отправить заново</span>
936
+ </template>
937
+ </dropdown-button-with-action>
938
+
939
+ retryFromDropdown(event, sms) {
940
+ if (event.currentTarget.firstElementChild.contains(event.target)) {
941
+ this.askRetry(sms);
942
+ }
943
+ },
944
+
945
+ Стало:
946
+
947
+ <dropdown-button-with-action
948
+ :actions="[
949
+ { label: 'Отправить заново', onSelect: () => askRetry(row) },
950
+ { label: 'Зафиксировать как неотправленное', onSelect: () => askUnsent(row) },
951
+ ]"
952
+ />
953
+
954
+ `retryFromDropdown` удаляется: он полагался на разметку пакета.
955
+ - Поле `danger` заменено цветом: `danger: true` → `color: 'red'`. Пункт
956
+ с `danger` рисуется обычным, а в режиме разработки в консоль уходит
957
+ предупреждение с заменой. Новый цвет — `color: 'yellow'`. Цвета есть
958
+ и у пунктов `HamburgerMenu`.
959
+ - Красный пункт меню — мягкий: красный текст, при подсветке светлый
960
+ красный фон, а не заливка `red-400` с белым текстом.
961
+ - `0.15.0` не подтянется по `^0.14.0`: для версий `0.x` знак `^` пропускает
962
+ только патчи. Обновление — `npm install @boobooking/dashboard-ui-components@^0.15.0`.
963
+
788
964
  ## Обновление с 0.13
789
965
 
790
966
  - `DropdownButtonWithAction`: пункты меню передаются пропом `actions`,
@@ -868,7 +1044,7 @@ Tailwind, `@tailwindcss/forms` и типографикой, как у потре
868
1044
  `ConfirmationModal`.
869
1045
 
870
1046
  Ресет пакета распространяется и на содержимое слотов — разметку приложения
871
- внутри `Popup` и `DropdownButtonWithAction`. Ресет лежит в слое
1047
+ внутри `DropdownButtonWithAction`. Ресет лежит в слое
872
1048
  `components`, поэтому внутри слота он сильнее всего, что приложение объявило
873
1049
  в слое `base`: preflight, `@tailwindcss/forms` и собственных умолчаний
874
1050
  приложения. В слоте он задаёт:
@@ -892,10 +1068,6 @@ Tailwind, `@tailwindcss/forms` и типографикой, как у потре
892
1068
  checked:bg-current checked:border-transparent focus:ring-2
893
1069
  focus:ring-offset-2`.
894
1070
 
895
- У `Closer` нет доступного имени, и он выключен из порядка табуляции
896
- (`tabindex="-1"`): скринридер его не назовёт, а с клавиатуры до него не
897
- добраться.
898
-
899
1071
  `ConfirmationModal` не переносит фокус внутрь при открытии, не удерживает его
900
1072
  внутри, не возвращает на прежнее место после закрытия и не закрывается по
901
1073
  Escape.