@olddevs/ui 4.0.3 → 4.1.1
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/CHANGELOG.md +26 -0
- package/dist/components/actions/Toggle.d.ts.map +1 -1
- package/dist/components/actions/Toggle.js +1 -1
- package/dist/components/actions/Toggle.js.map +1 -1
- package/dist/components/actions/Truncate.d.ts.map +1 -1
- package/dist/components/actions/Truncate.js +2 -2
- package/dist/components/actions/Truncate.js.map +1 -1
- package/dist/components/app-shell/MobileTabBar.d.ts.map +1 -1
- package/dist/components/app-shell/MobileTabBar.js +1 -1
- package/dist/components/app-shell/MobileTabBar.js.map +1 -1
- package/dist/components/app-shell/NavItem.d.ts.map +1 -1
- package/dist/components/app-shell/NavItem.js +3 -1
- package/dist/components/app-shell/NavItem.js.map +1 -1
- package/dist/components/app-shell/SideNav.d.ts +17 -0
- package/dist/components/app-shell/SideNav.d.ts.map +1 -1
- package/dist/components/app-shell/SideNav.js +2 -1
- package/dist/components/app-shell/SideNav.js.map +1 -1
- package/dist/components/content/AvatarGroup.d.ts.map +1 -1
- package/dist/components/content/AvatarGroup.js +2 -1
- package/dist/components/content/AvatarGroup.js.map +1 -1
- package/dist/components/content/Table.d.ts.map +1 -1
- package/dist/components/content/Table.js +2 -1
- package/dist/components/content/Table.js.map +1 -1
- package/dist/components/content/VirtualGrid.d.ts.map +1 -1
- package/dist/components/content/VirtualGrid.js +4 -1
- package/dist/components/content/VirtualGrid.js.map +1 -1
- package/dist/components/inputs/Checkbox.d.ts.map +1 -1
- package/dist/components/inputs/Checkbox.js +3 -0
- package/dist/components/inputs/Checkbox.js.map +1 -1
- package/dist/components/inputs/CheckboxCard.d.ts.map +1 -1
- package/dist/components/inputs/CheckboxCard.js +2 -2
- package/dist/components/inputs/CheckboxCard.js.map +1 -1
- package/dist/components/inputs/RadioGroup.d.ts.map +1 -1
- package/dist/components/inputs/RadioGroup.js +5 -3
- package/dist/components/inputs/RadioGroup.js.map +1 -1
- package/dist/components/inputs/Select.d.ts.map +1 -1
- package/dist/components/inputs/Select.js +6 -6
- package/dist/components/inputs/Select.js.map +1 -1
- package/dist/components/inputs/Slider.d.ts.map +1 -1
- package/dist/components/inputs/Slider.js +2 -2
- package/dist/components/inputs/Slider.js.map +1 -1
- package/dist/components/navigation/Accordion.d.ts.map +1 -1
- package/dist/components/navigation/Accordion.js +1 -1
- package/dist/components/navigation/Accordion.js.map +1 -1
- package/dist/components/navigation/TreeView.d.ts.map +1 -1
- package/dist/components/navigation/TreeView.js +6 -3
- package/dist/components/navigation/TreeView.js.map +1 -1
- package/dist/editor/RichTextEditor.d.ts.map +1 -1
- package/dist/editor/RichTextEditor.js +4 -2
- package/dist/editor/RichTextEditor.js.map +1 -1
- package/dist/editor/styles/prose.d.ts +7 -0
- package/dist/editor/styles/prose.d.ts.map +1 -1
- package/dist/editor/styles/prose.js +1 -1
- package/dist/editor/styles/prose.js.map +1 -1
- package/dist/editor/ui/FormatMenus.d.ts.map +1 -1
- package/dist/editor/ui/FormatMenus.js +2 -1
- package/dist/editor/ui/FormatMenus.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/choice-card.d.ts +13 -0
- package/dist/lib/choice-card.d.ts.map +1 -1
- package/dist/lib/choice-card.js +2 -0
- package/dist/lib/choice-card.js.map +1 -1
- package/docs/components/app-shell/mobile-tab-bar.en.md +3 -3
- package/docs/components/app-shell/mobile-tab-bar.md +2 -2
- package/docs/components/app-shell/side-nav.en.md +56 -6
- package/docs/components/app-shell/side-nav.md +55 -5
- package/docs/components/index.en.md +2 -2
- package/docs/components/index.md +1 -1
- package/docs/components/inputs/form.en.md +2 -2
- package/docs/components/inputs/form.md +1 -1
- package/docs/components/inputs/rich-text-editor.en.md +2 -2
- package/docs/components/inputs/rich-text-editor.md +1 -1
- package/docs/getting-started.en.md +3 -3
- package/docs/getting-started.md +2 -2
- package/package.json +1 -1
- package/src/components/actions/Toggle.tsx +10 -3
- package/src/components/actions/Truncate.tsx +5 -3
- package/src/components/app-shell/MobileTabBar.tsx +9 -1
- package/src/components/app-shell/NavItem.tsx +6 -1
- package/src/components/app-shell/SideNav.tsx +27 -2
- package/src/components/content/AvatarGroup.tsx +6 -2
- package/src/components/content/Table.tsx +15 -3
- package/src/components/content/VirtualGrid.tsx +11 -1
- package/src/components/inputs/Checkbox.tsx +5 -0
- package/src/components/inputs/CheckboxCard.tsx +2 -0
- package/src/components/inputs/RadioGroup.tsx +10 -1
- package/src/components/inputs/Select.tsx +113 -106
- package/src/components/inputs/Slider.tsx +6 -2
- package/src/components/navigation/Accordion.tsx +4 -1
- package/src/components/navigation/TreeView.tsx +70 -49
- package/src/editor/RichTextEditor.tsx +6 -3
- package/src/editor/styles/prose.ts +1 -1
- package/src/editor/ui/FormatMenus.tsx +16 -1
- package/src/index.ts +1 -1
- package/src/lib/choice-card.ts +17 -0
|
@@ -15,7 +15,7 @@ import { LISTBOX_ITEM_CLASS } from "../../lib/menu.js";
|
|
|
15
15
|
import { FIELD_CLEAR_CLASS } from "../../lib/field.js";
|
|
16
16
|
import { useMessages } from "../../i18n/useMessages.js";
|
|
17
17
|
import { CheckIcon, CloseIcon, SelectIcon } from "../../icons.js";
|
|
18
|
-
import { Popover, PopoverContent, PopoverTrigger } from "../overlays/Popover.js";
|
|
18
|
+
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../overlays/Popover.js";
|
|
19
19
|
import { VisuallyHidden } from "../content/VisuallyHidden.js";
|
|
20
20
|
|
|
21
21
|
/** Минимум, который список требует от элемента. Остальные поля — ваши. */
|
|
@@ -502,41 +502,47 @@ export function Select<T extends SelectItem>(props: SelectProps<T>) {
|
|
|
502
502
|
Кольцо фокуса рисуется на псевдоэлементе, а не на кнопке: обводить надо
|
|
503
503
|
пилюлю целиком, иначе она у`же собственного контура.
|
|
504
504
|
*/}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
505
|
+
{/*
|
|
506
|
+
Якорь панели — вся пилюля, а не кнопка-триггер внутри неё: кнопка отступает
|
|
507
|
+
от нижней кромки на рамку и `py-[7px]`, и панель с `sideOffset` в 4px
|
|
508
|
+
заходила на пилюлю вместо зазора.
|
|
509
|
+
*/}
|
|
510
|
+
<PopoverAnchor asChild>
|
|
511
|
+
<span
|
|
512
|
+
className={cn(
|
|
513
|
+
"relative inline-flex items-center gap-2 rounded-full border px-3 py-[7px]",
|
|
514
|
+
// `leading-5` задаёт высоту, а не наследует её: строчный бокс в 20px
|
|
515
|
+
// плюс отступы дают ровно 36 — рост `Button` размера `md`, рядом с
|
|
516
|
+
// которым пилюля и стоит в панели фильтров. С отступами формы
|
|
517
|
+
// (`py-2.5` от потребителя) те же 20 дают 42 — рост `TextControl`
|
|
518
|
+
// и `NumberInput` (см. `lib/field.ts`).
|
|
519
|
+
//
|
|
520
|
+
// Картинка выбранного значения тоже 20, и это условие, а не совпадение:
|
|
521
|
+
// содержимое выше строчного бокса растягивало бы триггер. Пока она
|
|
522
|
+
// была 22, пилюля с картинкой вырастала до 38, а триггер формы — до 44,
|
|
523
|
+
// и контрол расходился и с соседним полем, и сам с собой в пустом
|
|
524
|
+
// состоянии.
|
|
525
|
+
// `transition-colors`, а не `transition-all`: на этом боксе меняются
|
|
526
|
+
// только рамка, фон и цвет подписи — по ветке `hasSelection`. `all`
|
|
527
|
+
// обещал переход всему, чего на пилюле нет, и заодно тому, что
|
|
528
|
+
// потребитель пришлёт через `triggerClassName`. Заменено в волне 27
|
|
529
|
+
// при ревизии движения: класс выглядел перемещающим, а перемещать
|
|
530
|
+
// здесь нечего.
|
|
531
|
+
"text-[13px] leading-5 font-semibold transition-colors",
|
|
532
|
+
hasSelection && highlightSelected
|
|
533
|
+
? "border-accent-strong bg-accent-soft text-fg-0"
|
|
534
|
+
: "border-line-soft text-fg-1 hover:border-line hover:bg-bg-2 bg-transparent",
|
|
535
|
+
// Выбранное значение читается тёмным даже без акцентной подсветки:
|
|
536
|
+
// подпись — это содержимое контрола, а не подсказка.
|
|
537
|
+
hasSelection && !highlightSelected && "text-fg-0",
|
|
538
|
+
// Гаснет ПИЛЮЛЯ, а не кнопка внутри неё: рамка и фон нарисованы здесь,
|
|
539
|
+
// и приглушённая подпись рядом с яркой рамкой выглядела бы сбоем
|
|
540
|
+
// отрисовки, а не выключенным полем.
|
|
541
|
+
disabled && "cursor-not-allowed opacity-50",
|
|
542
|
+
triggerClassName
|
|
543
|
+
)}
|
|
544
|
+
>
|
|
545
|
+
{/*
|
|
540
546
|
Подпись видна только скринридеру и стоит ВНЕ кнопки: внутри неё она
|
|
541
547
|
попала бы в содержимое, то есть прочиталась бы дважды — один раз как
|
|
542
548
|
имя, второй как значение.
|
|
@@ -544,46 +550,46 @@ export function Select<T extends SelectItem>(props: SelectProps<T>) {
|
|
|
544
550
|
`elementType="span"`: по умолчанию react-aria рисует `<div>`, а здесь
|
|
545
551
|
он оказался бы внутри строчной пилюли.
|
|
546
552
|
*/}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
553
|
+
{ariaLabel && !ariaLabelledBy ? (
|
|
554
|
+
<VisuallyHidden elementType="span" id={labelId}>
|
|
555
|
+
{ariaLabel}
|
|
556
|
+
</VisuallyHidden>
|
|
557
|
+
) : null}
|
|
558
|
+
|
|
559
|
+
<PopoverTrigger asChild>
|
|
560
|
+
<button
|
|
561
|
+
type="button"
|
|
562
|
+
disabled={disabled}
|
|
563
|
+
aria-haspopup="listbox"
|
|
564
|
+
aria-expanded={open && !disabled}
|
|
565
|
+
aria-labelledby={labelledBy ? `${labelledBy} ${valueId}` : undefined}
|
|
566
|
+
aria-describedby={describedBy}
|
|
567
|
+
className={cn(
|
|
568
|
+
// `grow` + `justify-between`: когда потребитель растягивает пилюлю
|
|
569
|
+
// (`w-full`, `min-w`), кнопка занимает всю ширину, и его
|
|
570
|
+
// `justify-between` разводит иконку и подпись — как было до выноса.
|
|
571
|
+
// На пилюле по размеру содержимого свободного места нет, и оба
|
|
572
|
+
// класса ничего не меняют.
|
|
573
|
+
"inline-flex min-w-0 grow cursor-pointer items-center justify-between gap-2 text-left",
|
|
574
|
+
"disabled:cursor-not-allowed",
|
|
575
|
+
"after:absolute after:inset-0 after:rounded-[inherit]",
|
|
576
|
+
"focus-visible:after:outline-accent-strong focus-visible:after:outline-2 focus-visible:after:outline-offset-2",
|
|
577
|
+
// Место под крестик — на кнопке, а не на пилюле: пилюля уже несёт
|
|
578
|
+
// отступы потребителя, и добавка к ним сместила бы рамку.
|
|
579
|
+
hasSelection && clearable && !disabled && "pr-6"
|
|
580
|
+
)}
|
|
581
|
+
>
|
|
582
|
+
{/*
|
|
577
583
|
Содержимое собрано в одну группу, а не выложено соседями кнопки.
|
|
578
584
|
Иначе `justify-between` разводит по краям именно их: в пилюле по
|
|
579
585
|
ширине содержимого это незаметно, а в широком триггере (`w-full`
|
|
580
586
|
в форме) картинка прилипает к левому краю, подпись — к правому,
|
|
581
587
|
и между ними дыра во всю строку.
|
|
582
588
|
*/}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
589
|
+
{selected ? (
|
|
590
|
+
<span id={valueId} className="inline-flex min-w-0 items-center gap-2">
|
|
591
|
+
{renderMedia?.(selected, 20)}
|
|
592
|
+
{/*
|
|
587
593
|
Ширина подписи ограничена переменной, а не жёстким числом.
|
|
588
594
|
В пилюле фильтра предел нужен: она растёт по содержимому, и
|
|
589
595
|
«Абстрактные градиенты» растолкали бы соседние фильтры. В
|
|
@@ -592,10 +598,10 @@ export function Select<T extends SelectItem>(props: SelectProps<T>) {
|
|
|
592
598
|
Снимается это классом на триггере:
|
|
593
599
|
`triggerClassName="[--select-label-max:none]"`.
|
|
594
600
|
*/}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
601
|
+
<span className={LABEL_CLASS}>{selected.label}</span>
|
|
602
|
+
</span>
|
|
603
|
+
) : hasSelection && props.multiple ? (
|
|
604
|
+
/*
|
|
599
605
|
Счётчик — СТРОГО за множественным выбором.
|
|
600
606
|
|
|
601
607
|
Ветка ловила и одиночный `Select`, у которого значение есть, а
|
|
@@ -605,13 +611,13 @@ export function Select<T extends SelectItem>(props: SelectProps<T>) {
|
|
|
605
611
|
нечего. Одиночный случай уходит теперь в ветку ниже и
|
|
606
612
|
показывает сам ключ, как это делает `captionOf` в `TreeSelect`.
|
|
607
613
|
*/
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
614
|
+
<span id={valueId} className={LABEL_CLASS}>
|
|
615
|
+
{countLabel
|
|
616
|
+
? countLabel(selectedIds.length)
|
|
617
|
+
: `${placeholder}: ${selectedIds.length}`}
|
|
618
|
+
</span>
|
|
619
|
+
) : hasSelection ? (
|
|
620
|
+
/*
|
|
615
621
|
Одиночный выбор, которому не нашлось элемента.
|
|
616
622
|
|
|
617
623
|
Ключ ЧЕСТНЕЕ плейсхолдера, когда он человекочитаем
|
|
@@ -622,36 +628,37 @@ export function Select<T extends SelectItem>(props: SelectProps<T>) {
|
|
|
622
628
|
умолчанию показывается ключ, как это делает `captionOf` в
|
|
623
629
|
`TreeSelect`.
|
|
624
630
|
*/
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
631
|
+
<span id={valueId} className={LABEL_CLASS}>
|
|
632
|
+
{missingLabel ? missingLabel(selectedIds[0]!) : selectedIds[0]}
|
|
633
|
+
</span>
|
|
634
|
+
) : (
|
|
635
|
+
/*
|
|
630
636
|
`id` значения стоит на КОРНЕ КАЖДОЙ ветки, а не на общей обёртке
|
|
631
637
|
вокруг них: обёртка — лишний бокс во флексе триггера, и она
|
|
632
638
|
сместила бы содержимое там, где менять его никто не просил.
|
|
633
639
|
*/
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
640
|
+
<span id={valueId} className="inline-flex min-w-0 items-center gap-2">
|
|
641
|
+
<span aria-hidden className="inline-flex shrink-0 items-center opacity-60">
|
|
642
|
+
{icon ?? <SelectIcon size={14} />}
|
|
643
|
+
</span>
|
|
644
|
+
<span className="truncate">{placeholder}</span>
|
|
637
645
|
</span>
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
</span>
|
|
646
|
+
)}
|
|
647
|
+
</button>
|
|
648
|
+
</PopoverTrigger>
|
|
649
|
+
|
|
650
|
+
{hasSelection && clearable && !disabled ? (
|
|
651
|
+
<button
|
|
652
|
+
type="button"
|
|
653
|
+
aria-label={clearLabel ?? messages.common.clear}
|
|
654
|
+
onClick={clear}
|
|
655
|
+
className={FIELD_CLEAR_CLASS}
|
|
656
|
+
>
|
|
657
|
+
<CloseIcon size={10} aria-hidden />
|
|
658
|
+
</button>
|
|
659
|
+
) : null}
|
|
660
|
+
</span>
|
|
661
|
+
</PopoverAnchor>
|
|
655
662
|
|
|
656
663
|
<PopoverContent
|
|
657
664
|
className={cn("w-[260px]", contentClassName)}
|
|
@@ -64,15 +64,19 @@ export type RangeSliderProps = SliderLookProps &
|
|
|
64
64
|
`State=disabled` в Figma. `disabled:` здесь срабатывает на `data-disabled`,
|
|
65
65
|
который react-aria ставит на корень. Раньше вида не было вовсе —
|
|
66
66
|
отличался только курсор над бегунком.
|
|
67
|
+
|
|
68
|
+
`has-[input:disabled]:` — для ползунка внутри `<fieldset disabled>` (так `Form` с
|
|
69
|
+
`pending` гасит поля): react-aria о таком предке не знает и `data-disabled`
|
|
70
|
+
на корне не ставит, а нативное состояние видит только скрытый `input` бегунка.
|
|
67
71
|
*/
|
|
68
|
-
const DIMMED_WHEN_DISABLED = "disabled:opacity-50";
|
|
72
|
+
const DIMMED_WHEN_DISABLED = "disabled:opacity-50 has-[input:disabled]:opacity-50";
|
|
69
73
|
const ROOT_CLASS = `flex w-full flex-col gap-2 ${DIMMED_WHEN_DISABLED}`;
|
|
70
74
|
const TRACK_CLASS = "bg-bg-3 relative h-1.5 w-full rounded-full";
|
|
71
75
|
const FILL_CLASS = "bg-accent absolute h-full rounded-full";
|
|
72
76
|
const THUMB_BASE_CLASS =
|
|
73
77
|
"border-accent bg-bg-1 size-4 cursor-grab rounded-full border-2 shadow-[var(--shadow-1)] transition-shadow " +
|
|
74
78
|
"dragging:cursor-grabbing focus-visible:outline-accent-strong focus-visible:outline-2 focus-visible:outline-offset-2 " +
|
|
75
|
-
"disabled:cursor-not-allowed";
|
|
79
|
+
"disabled:cursor-not-allowed has-[input:disabled]:cursor-not-allowed";
|
|
76
80
|
// Вдоль оси бегунок ставит react-aria (`left` или `top`), поперёк — мы.
|
|
77
81
|
const THUMB_CLASS = `${THUMB_BASE_CLASS} top-1/2`;
|
|
78
82
|
|
|
@@ -91,7 +91,10 @@ export function AccordionItem({ id, title, disabled, className, children }: Acco
|
|
|
91
91
|
/>
|
|
92
92
|
</AriaButton>
|
|
93
93
|
</AriaHeading>
|
|
94
|
-
|
|
94
|
+
{/* Отступ только у раскрытой панели: свёрнутая остаётся в потоке, и её `pb-3` добавлял 12px. */}
|
|
95
|
+
<DisclosurePanel className={cn("text-fg-1 text-sm", isExpanded && "pb-3")}>
|
|
96
|
+
{children}
|
|
97
|
+
</DisclosurePanel>
|
|
95
98
|
</>
|
|
96
99
|
)}
|
|
97
100
|
</Disclosure>
|
|
@@ -165,57 +165,78 @@ export function TreeView({
|
|
|
165
165
|
className="outline-none [--tree-indent:1.25rem]"
|
|
166
166
|
>
|
|
167
167
|
<TreeItemContent>
|
|
168
|
-
{({ isExpanded, hasChildItems }) =>
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
168
|
+
{({ isExpanded, hasChildItems, isSelected, isDisabled }) => {
|
|
169
|
+
// Гасятся шеврон, иконка и подпись по отдельности, а не весь `div`:
|
|
170
|
+
// флажок выбора получает блокировку от строки сам и гасится собственным
|
|
171
|
+
// `disabled:opacity-50`, так что приглушение всего ряда дало бы ему
|
|
172
|
+
// двойное — 25% вместо 50%.
|
|
173
|
+
const dim = isDisabled && "opacity-50";
|
|
174
|
+
return (
|
|
175
|
+
<div
|
|
176
|
+
ref={(content) => markCurrent(content, current)}
|
|
177
|
+
className={cn(
|
|
178
|
+
"text-fg-1 flex items-center gap-2 rounded-xs py-1.5 pr-2 text-[13px] leading-5",
|
|
179
|
+
// Выбор и блокировка берутся из рендер-пропа, а не вариантами
|
|
180
|
+
// `selected:` и `disabled:`: `data-selected` и `data-disabled`
|
|
181
|
+
// react-aria ставит на строку (`row`), а красится её содержимое —
|
|
182
|
+
// вариант на нём не срабатывал, и ни выбранный узел не
|
|
183
|
+
// подсвечивался, ни заблокированный не гас. Тот же случай, что
|
|
184
|
+
// шеврон в `Accordion`.
|
|
185
|
+
//
|
|
186
|
+
// Приглушение — не на всём `div`, а на частях (см. `dim` ниже).
|
|
187
|
+
//
|
|
188
|
+
// Текущий узел — подложка и вес шрифта, как у `NavItem` с `active`.
|
|
189
|
+
// Стоит ДО выбора: `cn` оставляет последнюю подложку, и у узла,
|
|
190
|
+
// который и текущий, и выбран, видна подложка выбора — как было,
|
|
191
|
+
// пока порядок решал каскад вариантов.
|
|
192
|
+
current && "bg-bg-3 text-fg-0 font-semibold",
|
|
193
|
+
isSelected ? "bg-accent-soft text-fg-0" : !isDisabled && "hover:bg-bg-2",
|
|
194
|
+
"focus-visible:outline-accent-strong focus-visible:outline-2 focus-visible:-outline-offset-2"
|
|
195
|
+
)}
|
|
196
|
+
// Уровень вложенности берётся у react-aria: она кладёт его в
|
|
197
|
+
// `--tree-item-level` на строке, и считать глубину самим не нужно
|
|
198
|
+
// — иначе у нас была бы вторая копия того же числа.
|
|
199
|
+
style={{
|
|
200
|
+
paddingLeft: "calc((var(--tree-item-level) - 1) * var(--tree-indent) + 0.25rem)",
|
|
201
|
+
}}
|
|
202
|
+
>
|
|
203
|
+
{hasChildItems ? (
|
|
204
|
+
<Button
|
|
205
|
+
slot="chevron"
|
|
206
|
+
aria-label={isExpanded ? messages.treeView.collapse : messages.treeView.expand}
|
|
199
207
|
className={cn(
|
|
200
|
-
"
|
|
201
|
-
|
|
208
|
+
"text-fg-3 hover:text-fg-0 flex cursor-pointer items-center rounded-xs",
|
|
209
|
+
"transition-colors",
|
|
210
|
+
dim,
|
|
211
|
+
"focus-visible:outline-accent-strong focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
202
212
|
)}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
213
|
+
>
|
|
214
|
+
<ChevronRightIcon
|
|
215
|
+
size={14}
|
|
216
|
+
aria-hidden
|
|
217
|
+
className={cn(
|
|
218
|
+
"transition-transform motion-reduce:transition-none",
|
|
219
|
+
isExpanded && "rotate-90"
|
|
220
|
+
)}
|
|
221
|
+
/>
|
|
222
|
+
</Button>
|
|
223
|
+
) : (
|
|
224
|
+
// Место раскрывателя занято всегда: без него подписи листьев
|
|
225
|
+
// разъезжаются с подписями узлов на том же уровне.
|
|
226
|
+
<span aria-hidden className="w-[14px] shrink-0" />
|
|
227
|
+
)}
|
|
228
|
+
{selectionMode ? (
|
|
229
|
+
<Checkbox slot="selection" label={messages.treeView.select} />
|
|
230
|
+
) : null}
|
|
231
|
+
{node.icon != null && (
|
|
232
|
+
<span className={cn("text-fg-3 flex shrink-0 items-center", dim)}>
|
|
233
|
+
{node.icon}
|
|
234
|
+
</span>
|
|
235
|
+
)}
|
|
236
|
+
<span className={cn("min-w-0 truncate", dim)}>{node.label}</span>
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
}}
|
|
219
240
|
</TreeItemContent>
|
|
220
241
|
{node.children != null && node.children.length > 0 ? (
|
|
221
242
|
<Collection items={node.children}>{renderNode}</Collection>
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
} from "./core/jsonDocument.js";
|
|
68
68
|
import { markdownOf } from "./core/atomText.js";
|
|
69
69
|
import { countCharacters, EXTERNAL_CHANGE } from "./core/maxLength.js";
|
|
70
|
-
import { proseClass } from "./styles/prose.js";
|
|
70
|
+
import { PAPER_SHEET_CLASS, proseClass } from "./styles/prose.js";
|
|
71
71
|
import { SELECTION_BUBBLE_KEY, SelectionBubble } from "./ui/SelectionBubble.js";
|
|
72
72
|
import { useSlashMenu } from "./ui/SlashMenu.js";
|
|
73
73
|
import { useTriggerMenu } from "./ui/TriggerMenu.js";
|
|
@@ -1543,8 +1543,11 @@ export const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorPro
|
|
|
1543
1543
|
{...surfaceAttributes(surface)}
|
|
1544
1544
|
className={cn(
|
|
1545
1545
|
"grid",
|
|
1546
|
-
|
|
1547
|
-
|
|
1546
|
+
// На листе светлый лист остаётся под тёмным полем: смена режима
|
|
1547
|
+
// меняет содержимое листа, а не сам лист.
|
|
1548
|
+
surface === "paper"
|
|
1549
|
+
? cn(EDITOR_PAPER_TOKENS_CLASS, PAPER_SHEET_CLASS, "my-3")
|
|
1550
|
+
: cn(EDITOR_TOKENS_CLASS, "py-2.5")
|
|
1548
1551
|
)}
|
|
1549
1552
|
style={{ minHeight: source.minHeight }}
|
|
1550
1553
|
>
|
|
@@ -415,7 +415,7 @@ export const PROSE_CLASS = EDITOR_TOKENS_CLASS + " " + PROSE_RULES;
|
|
|
415
415
|
* видны на тёмной подложке так же, как на светлой. `scheme-light` — родной
|
|
416
416
|
* флажок чек-листа и полосы прокрутки рисуются светлыми.
|
|
417
417
|
*/
|
|
418
|
-
const PAPER_SHEET_CLASS =
|
|
418
|
+
export const PAPER_SHEET_CLASS =
|
|
419
419
|
"rounded-(--r-sm) border border-(--line) bg-(--rte-background) px-4 py-3 " +
|
|
420
420
|
"shadow-[var(--shadow-1)] scheme-light";
|
|
421
421
|
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "../../components/overlays/Menu.js";
|
|
14
14
|
import { Tooltip } from "../../components/overlays/Tooltip.js";
|
|
15
15
|
import type { UiMessages } from "../../i18n/messages.js";
|
|
16
|
+
import { cn } from "../../lib/cn.js";
|
|
16
17
|
import { useMessages, useUiLocale } from "../../i18n/useMessages.js";
|
|
17
18
|
import {
|
|
18
19
|
AlignCenterIcon,
|
|
@@ -227,7 +228,21 @@ export function formatMenuSections(
|
|
|
227
228
|
icon={ItemIcon ? <ItemIcon size={14} aria-hidden /> : undefined}
|
|
228
229
|
shortcut={item.keys ? shortcut(item.keys) : undefined}
|
|
229
230
|
>
|
|
230
|
-
{item.swatch ?
|
|
231
|
+
{item.swatch ? (
|
|
232
|
+
/*
|
|
233
|
+
Образец не должен брать вид выбранной строки: её голубой текст
|
|
234
|
+
и подложка перекрашивали цвета тона. Текст задан образцу
|
|
235
|
+
самому, а непрозрачная основа под ним не даёт полупрозрачной
|
|
236
|
+
подложке тона смешаться с подложкой строки. Основа — только в
|
|
237
|
+
отмеченной строке: у остальных образец лежит на панели меню,
|
|
238
|
+
как обёртка в документе, и выглядит так же.
|
|
239
|
+
*/
|
|
240
|
+
<span className="in-[[aria-checked=true]]:bg-surface-elevated rounded-xs">
|
|
241
|
+
<span className={cn(item.swatch, "text-fg-0")}>{item.label}</span>
|
|
242
|
+
</span>
|
|
243
|
+
) : (
|
|
244
|
+
item.label
|
|
245
|
+
)}
|
|
231
246
|
</MenuItem>
|
|
232
247
|
);
|
|
233
248
|
if (!item.submenu || item.disabled) return entry;
|
package/src/index.ts
CHANGED
|
@@ -208,7 +208,7 @@ export type {
|
|
|
208
208
|
MobileTabBarProps,
|
|
209
209
|
} from "./components/app-shell/MobileTabBar.js";
|
|
210
210
|
|
|
211
|
-
export { NavGroup, SideNav } from "./components/app-shell/SideNav.js";
|
|
211
|
+
export { NavGroup, SideNav, useNavCollapsed } from "./components/app-shell/SideNav.js";
|
|
212
212
|
export type { NavGroupProps, SideNavProps } from "./components/app-shell/SideNav.js";
|
|
213
213
|
|
|
214
214
|
export { DataTable } from "./components/table/DataTable.js";
|
package/src/lib/choice-card.ts
CHANGED
|
@@ -50,6 +50,23 @@ export const CHOICE_CARD_INVALID_BORDER = "border-danger-strong";
|
|
|
50
50
|
*/
|
|
51
51
|
export const CHOICE_CARD_BASE = "flex flex-col rounded-lg border bg-bg-2 transition-colors";
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Выключенность от `<fieldset disabled>` (так `Form` с `pending` гасит поля).
|
|
55
|
+
*
|
|
56
|
+
* React-aria о таком предке не знает: `isDisabled` ложен, `data-disabled` не
|
|
57
|
+
* ставится, и `disabled:` не срабатывает. Нативное состояние видит только
|
|
58
|
+
* скрытый `input` контрола — поэтому `has-[input:disabled]`, а не голый
|
|
59
|
+
* `has-disabled`: тот ловил бы любой выключенный узел внутри, например кнопку в
|
|
60
|
+
* описании карточки, и гасил бы рабочий контрол. Hover гасится вместе с видом:
|
|
61
|
+
* выключенный контрол не должен отзываться на указатель.
|
|
62
|
+
*/
|
|
63
|
+
export const DIMMED_IN_DISABLED_FIELDSET =
|
|
64
|
+
"has-[input:disabled]:cursor-default has-[input:disabled]:opacity-50 has-[input:disabled]:hover:bg-transparent";
|
|
65
|
+
|
|
66
|
+
/** То же для карточки: её подложка `bg-bg-2` при наведении не меняется. */
|
|
67
|
+
export const CHOICE_CARD_DIMMED_IN_DISABLED_FIELDSET =
|
|
68
|
+
"has-[input:disabled]:opacity-50 has-[input:disabled]:hover:bg-bg-2";
|
|
69
|
+
|
|
53
70
|
/** Раскладка карточки в строке с переносом: растёт, но не уезжает в полосу. */
|
|
54
71
|
export const CHOICE_CARD_ROW = "max-w-xs flex-1 basis-48";
|
|
55
72
|
|