@botiverse/raft-ui 0.64.0 → 0.65.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 (81) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/components/activity-inbox/activity-inbox-sidebar.recipe.mjs +1 -1
  3. package/dist/components/activity-inbox/activity-inbox-toolbar.recipe.mjs +6 -1
  4. package/dist/components/alert-dialog/alert-dialog.mjs +3 -3
  5. package/dist/components/announcement-dialog/announcement-dialog.mjs +2 -2
  6. package/dist/components/app-link-card/app-link-card.mjs +1 -1
  7. package/dist/components/app-rail/app-rail.recipe.mjs +12 -7
  8. package/dist/components/app-shell/app-shell.mjs +1 -1
  9. package/dist/components/avatar/avatar.mjs +1 -0
  10. package/dist/components/badge/badge.mjs +6 -1
  11. package/dist/components/banner/banner.mjs +1 -1
  12. package/dist/components/button/button-split.mjs +10 -3
  13. package/dist/components/button/button.mjs +13 -8
  14. package/dist/components/code-block/code-block.recipe.mjs +1 -1
  15. package/dist/components/combobox/combobox.mjs +4 -4
  16. package/dist/components/combobox-select/combobox-select.css +6 -3
  17. package/dist/components/composer/composer-suggestion-list.mjs +1 -1
  18. package/dist/components/composer/composer-toolbar-label.mjs +1 -1
  19. package/dist/components/composer/composer.mjs +1 -1
  20. package/dist/components/composer/composer.recipe.mjs +38 -26
  21. package/dist/components/conversation-panel/conversation-panel-empty-state.mjs +1 -1
  22. package/dist/components/conversation-panel/conversation-panel.recipe.mjs +3 -2
  23. package/dist/components/dialog/dialog-sheet-context.mjs +1 -1
  24. package/dist/components/dialog/dialog.mjs +7 -7
  25. package/dist/components/drawer/drawer.mjs +3 -2
  26. package/dist/components/drawer/drawer.recipe.mjs +5 -3
  27. package/dist/components/empty-state/empty-state.mjs +1 -1
  28. package/dist/components/field/field.mjs +1 -1
  29. package/dist/components/files/files.recipe.mjs +1 -1
  30. package/dist/components/filter-chip/filter-chip.mjs +1 -1
  31. package/dist/components/form-section/form-section.mjs +1 -1
  32. package/dist/components/icon-tile/icon-tile.mjs +3 -3
  33. package/dist/components/inbox-item/inbox-item.recipe.mjs +4 -2
  34. package/dist/components/input/input.recipe.mjs +1 -1
  35. package/dist/components/input-group/input-group.mjs +1 -1
  36. package/dist/components/lightbox/lightbox.mjs +1 -1
  37. package/dist/components/list-row/list-row.recipe.mjs +2 -2
  38. package/dist/components/message-action-card/message-action-card-content.recipe.mjs +1 -1
  39. package/dist/components/message-action-card/message-action-card.recipe.mjs +1 -1
  40. package/dist/components/message-item/index.d.mts +2 -2
  41. package/dist/components/message-item/message-item-content.d.mts +14 -0
  42. package/dist/components/message-item/message-item-content.mjs +146 -1
  43. package/dist/components/message-item/message-item.recipe.d.mts +3 -0
  44. package/dist/components/message-item/message-item.recipe.mjs +3 -2
  45. package/dist/components/message-item/message-reaction.recipe.mjs +4 -3
  46. package/dist/components/message-list/message-list.recipe.mjs +3 -1
  47. package/dist/components/mode-bar/mode-bar.mjs +1 -1
  48. package/dist/components/notification-center/notification-center.mjs +1 -1
  49. package/dist/components/panel/panel-action.recipe.mjs +5 -5
  50. package/dist/components/panel/panel-header.recipe.mjs +10 -9
  51. package/dist/components/panel/panel.recipe.mjs +2 -2
  52. package/dist/components/picker-trigger-button/picker-trigger-button.mjs +1 -1
  53. package/dist/components/profile-panel/profile-panel.recipe.mjs +1 -1
  54. package/dist/components/scroll-area/scroll-area.css +74 -33
  55. package/dist/components/search/search-entity.recipe.mjs +3 -2
  56. package/dist/components/search/search-message.recipe.mjs +1 -0
  57. package/dist/components/search/search-thread.recipe.mjs +1 -1
  58. package/dist/components/search/search-trigger.mjs +1 -1
  59. package/dist/components/search/search.recipe.mjs +7 -3
  60. package/dist/components/segmented-control/segmented-control.mjs +9 -8
  61. package/dist/components/select/select.mjs +2 -2
  62. package/dist/components/sidebar/sidebar-item.recipe.mjs +5 -4
  63. package/dist/components/sidebar/sidebar-section.recipe.mjs +5 -2
  64. package/dist/components/sidebar/sidebar.recipe.mjs +3 -3
  65. package/dist/components/switch/switch.mjs +3 -3
  66. package/dist/components/tabs/tabs.mjs +1 -1
  67. package/dist/components/task-board/task-board-column.recipe.mjs +1 -1
  68. package/dist/components/task-list/task-list-section.recipe.mjs +1 -1
  69. package/dist/components/tasks/tasks-panel.recipe.mjs +4 -3
  70. package/dist/components/text-selection-toolbar/text-selection-toolbar.recipe.mjs +1 -1
  71. package/dist/components/textarea/textarea.mjs +1 -1
  72. package/dist/components/thread-panel/thread-panel.recipe.mjs +3 -3
  73. package/dist/components/toggle-group/toggle-group.mjs +1 -1
  74. package/dist/foundation.css +100 -5
  75. package/dist/lib/head-edges.mjs +38 -8
  76. package/dist/lib/list-cell.mjs +1 -0
  77. package/dist/styles.css +54 -7
  78. package/dist/tokens.json +260 -4
  79. package/dist/wip.d.mts +2 -1
  80. package/dist/wip.mjs +2 -1
  81. package/package.json +1 -1
@@ -587,10 +587,7 @@
587
587
  block-size var(--r-scroll-area-duration) var(--r-scroll-area-easing);
588
588
  }
589
589
 
590
- /* Shown while hovered (trigger="hover"), always (trigger="none"), and while scrolling or dragging (every trigger). */
591
- .r-scroll-area.r-scroll-area--hover-trigger:hover
592
- > .r-scroll-area__gutters
593
- > .r-scroll-area__gutter,
590
+ /* Shown always (trigger="none"), and while scrolling or dragging (every trigger). */
594
591
  .r-scroll-area.r-scroll-area--none-trigger > .r-scroll-area__gutters > .r-scroll-area__gutter,
595
592
  .r-scroll-area
596
593
  > .r-scroll-area__gutters.r-scroll-area__gutters--scrolling
@@ -602,31 +599,7 @@
602
599
  transition-duration: var(--r-scroll-area-duration-fade-in);
603
600
  }
604
601
 
605
- /*
606
- * trigger="scroll": a hidden gutter doesn't catch presses meant for the
607
- * content under it. A gutter the pointer is already on (it was shown by
608
- * scrolling) stays visible and usable after `hideDelay`, until the pointer
609
- * leaves it; a hidden gutter can't become hovered, so it never shows on hover.
610
- */
611
- .r-scroll-area.r-scroll-area--scroll-trigger
612
- > .r-scroll-area__gutters
613
- > .r-scroll-area__gutter:hover {
614
- opacity: 1;
615
- transition-duration: var(--r-scroll-area-duration-fade-in);
616
- }
617
- .r-scroll-area.r-scroll-area--scroll-trigger
618
- > .r-scroll-area__gutters:not(.r-scroll-area__gutters--scrolling):not(
619
- .r-scroll-area__gutters--dragging
620
- )
621
- > .r-scroll-area__gutter:not(:hover) {
622
- pointer-events: none;
623
- }
624
-
625
- /* Pointed at, or while dragged: the thumb thickens and darkens. */
626
- .r-scroll-area
627
- > .r-scroll-area__gutters
628
- > .r-scroll-area__gutter--vertical:hover
629
- > .r-scroll-area__thumb,
602
+ /* While dragged: the thumb thickens and darkens. */
630
603
  .r-scroll-area
631
604
  > .r-scroll-area__gutters
632
605
  > .r-scroll-area__gutter--vertical.r-scroll-area__gutter--dragging
@@ -634,10 +607,6 @@
634
607
  background: var(--r-scroll-area-thumb-color-hover);
635
608
  inline-size: var(--r-scroll-area-thumb-size-hover);
636
609
  }
637
- .r-scroll-area
638
- > .r-scroll-area__gutters
639
- > .r-scroll-area__gutter--horizontal:hover
640
- > .r-scroll-area__thumb,
641
610
  .r-scroll-area
642
611
  > .r-scroll-area__gutters
643
612
  > .r-scroll-area__gutter--horizontal.r-scroll-area__gutter--dragging
@@ -646,6 +615,78 @@
646
615
  block-size: var(--r-scroll-area-thumb-size-hover);
647
616
  }
648
617
 
618
+ /*
619
+ * Hover only with a pointer that hovers (design/foundations/states.md): on a
620
+ * touch screen the last thing tapped stays `:hover`, which would leave the
621
+ * scrollbars shown, the thumb thick and a hidden gutter catching taps.
622
+ */
623
+ @media (hover: hover) {
624
+ /* trigger="hover": shown while the pointer is over the area. */
625
+ .r-scroll-area.r-scroll-area--hover-trigger:hover
626
+ > .r-scroll-area__gutters
627
+ > .r-scroll-area__gutter {
628
+ opacity: 1;
629
+ transition-duration: var(--r-scroll-area-duration-fade-in);
630
+ }
631
+
632
+ /*
633
+ * trigger="scroll": a hidden gutter doesn't catch presses meant for the
634
+ * content under it. A gutter the pointer is already on (it was shown by
635
+ * scrolling) stays visible and usable after `hideDelay`, until the pointer
636
+ * leaves it; a hidden gutter can't become hovered, so it never shows on hover.
637
+ */
638
+ .r-scroll-area.r-scroll-area--scroll-trigger
639
+ > .r-scroll-area__gutters
640
+ > .r-scroll-area__gutter:hover {
641
+ opacity: 1;
642
+ transition-duration: var(--r-scroll-area-duration-fade-in);
643
+ }
644
+ .r-scroll-area.r-scroll-area--scroll-trigger
645
+ > .r-scroll-area__gutters:not(.r-scroll-area__gutters--scrolling):not(
646
+ .r-scroll-area__gutters--dragging
647
+ )
648
+ > .r-scroll-area__gutter:not(:hover) {
649
+ pointer-events: none;
650
+ }
651
+
652
+ /* Pointed at: the thumb thickens and darkens, as while dragged. */
653
+ .r-scroll-area
654
+ > .r-scroll-area__gutters
655
+ > .r-scroll-area__gutter--vertical:hover
656
+ > .r-scroll-area__thumb {
657
+ background: var(--r-scroll-area-thumb-color-hover);
658
+ inline-size: var(--r-scroll-area-thumb-size-hover);
659
+ }
660
+ .r-scroll-area
661
+ > .r-scroll-area__gutters
662
+ > .r-scroll-area__gutter--horizontal:hover
663
+ > .r-scroll-area__thumb {
664
+ background: var(--r-scroll-area-thumb-color-hover);
665
+ block-size: var(--r-scroll-area-thumb-size-hover);
666
+ }
667
+ }
668
+
669
+ /*
670
+ * Where nothing hovers, trigger="hover" and "scroll" both show the scrollbars
671
+ * only while the area scrolls or a thumb is dragged, and a hidden gutter
672
+ * never catches a tap: it goes to the content under it. A scrollbar shown by
673
+ * scrolling takes the finger, so its thumb can be dragged; the drag keeps it.
674
+ */
675
+ @media (hover: none) {
676
+ .r-scroll-area.r-scroll-area--hover-trigger
677
+ > .r-scroll-area__gutters:not(.r-scroll-area__gutters--scrolling):not(
678
+ .r-scroll-area__gutters--dragging
679
+ )
680
+ > .r-scroll-area__gutter,
681
+ .r-scroll-area.r-scroll-area--scroll-trigger
682
+ > .r-scroll-area__gutters:not(.r-scroll-area__gutters--scrolling):not(
683
+ .r-scroll-area__gutters--dragging
684
+ )
685
+ > .r-scroll-area__gutter {
686
+ pointer-events: none;
687
+ }
688
+ }
689
+
649
690
  /*
650
691
  * Scroll-driven thumbs: the browser moves each thumb from the scroll itself,
651
692
  * on the compositor, with no script per scroll. The container names its
@@ -5,7 +5,8 @@ const searchEntity = tv({ slots: {
5
5
  entity: [
6
6
  "flex min-w-0 w-full items-center gap-3 text-left",
7
7
  "rounded-none border-0 py-3 text-foreground",
8
- "pointer-coarse:min-h-16",
8
+ "md:pointer-fine:py-2",
9
+ "pointer-coarse:min-h-14 pointer-coarse:py-2",
9
10
  listCell,
10
11
  listCellStates,
11
12
  "disabled:cursor-default",
@@ -15,7 +16,7 @@ const searchEntity = tv({ slots: {
15
16
  entityLeading: ["relative flex shrink-0"],
16
17
  entityIcon: [
17
18
  "flex items-center justify-center overflow-hidden",
18
- "size-[27px] rounded-sticker bg-fill-muted text-foreground-muted",
19
+ "size-[27px] rounded-chip bg-fill-muted text-foreground-muted",
19
20
  "[&_svg]:size-3.5"
20
21
  ],
21
22
  entityActivity: [
@@ -6,6 +6,7 @@ const searchMessage = tv({ slots: {
6
6
  message: [
7
7
  "w-full text-left",
8
8
  "block rounded-none border-0 py-3 text-foreground",
9
+ "md:pointer-fine:py-2",
9
10
  listCell,
10
11
  listCellStates,
11
12
  "disabled:cursor-default",
@@ -2,7 +2,7 @@ import { tv } from "../../lib/tv.mjs";
2
2
  //#region src/components/search/search-thread.recipe.ts
3
3
  const searchThread = tv({ slots: {
4
4
  thread: ["overflow-hidden", "relative rounded-none border-0 bg-transparent text-foreground shadow-none"],
5
- threadHeader: ["py-2", "px-5 pt-3 pb-0"],
5
+ threadHeader: ["py-2", "px-(--surface-inset) pt-3 pb-0"],
6
6
  threadMeta: [
7
7
  "flex flex-wrap items-center",
8
8
  "mb-0.5 gap-1.5",
@@ -6,7 +6,7 @@ import { useRender } from "@base-ui/react/use-render";
6
6
  //#region src/components/search/search-trigger.tsx
7
7
  const searchTrigger = tv({ slots: {
8
8
  root: [
9
- "relative inline-flex h-9 w-[clamp(360px,32vw,520px)] max-w-full min-w-0 shrink-0 items-center gap-2 px-3",
9
+ "relative inline-flex h-(--frame-item-height) w-[clamp(360px,32vw,520px)] max-w-full min-w-0 shrink-0 items-center gap-2 px-3",
10
10
  "rounded-control border-[1.5px] border-line-field bg-layer-card",
11
11
  "text-left font-sans text-body font-normal text-foreground-hint",
12
12
  "select-none outline-none transition-colors duration-100",
@@ -7,13 +7,17 @@ const search = tv({ slots: {
7
7
  "bg-layer-canvas text-foreground",
8
8
  listColumnMargin
9
9
  ],
10
- filtersRow: ["shrink-0", "border-b border-line-seam bg-layer-canvas px-(--column-margin) py-3"],
10
+ filtersRow: [
11
+ "shrink-0",
12
+ "border-b border-line-seam bg-layer-canvas px-(--column-margin) py-3",
13
+ "md:pointer-fine:py-[calc((var(--header-height)-1px-var(--control-height))/2)]"
14
+ ],
11
15
  viewport: ["min-h-0 flex-1 overflow-y-auto scrollbar-list p-4", "bg-layer-canvas px-0 pt-0 pb-4"],
12
- summary: ["block", "mb-0 px-5 pt-2 font-sans text-footnote text-foreground-hint"],
16
+ summary: ["block", "mb-0 px-(--surface-inset) pt-2 font-sans text-footnote text-foreground-hint"],
13
17
  section: ["mb-0"],
14
18
  sectionHeading: [
15
19
  "py-2",
16
- "flex items-center gap-2 px-5 pt-3 pb-0",
20
+ "flex items-center gap-2 px-(--surface-inset) pt-3 pb-0",
17
21
  "font-sans text-footnote font-bold text-foreground-hint"
18
22
  ],
19
23
  list: ["flex flex-col", "gap-0.5 py-2"]
@@ -12,13 +12,14 @@ const segmentedControl = tv({
12
12
  root: [
13
13
  "inline-flex min-w-0 items-center",
14
14
  "w-fit max-w-full flex-nowrap gap-[3px] rounded-[11px] bg-fill-muted p-[3px]",
15
+ "[--segment-size:2rem] md:pointer-fine:[--segment-size:var(--control-height)]",
15
16
  "data-overflowing:overflow-x-auto data-overflowing:overscroll-x-contain data-overflowing:scrollbar-none"
16
17
  ],
17
18
  item: [
18
19
  "inline-flex min-w-0 shrink-0 items-center gap-1.5",
19
20
  "outline-none disabled:pointer-events-none",
20
21
  "[&_svg]:pointer-events-none [&_svg]:shrink-0",
21
- "h-8 justify-center rounded-control border-[1.5px] border-transparent bg-transparent px-3",
22
+ "h-(--segment-size) justify-center rounded-control border-[1.5px] border-transparent bg-transparent px-3",
22
23
  "font-sans text-callout font-semibold text-foreground-muted",
23
24
  "[&_svg:not([class*='size-'])]:size-4",
24
25
  "hover:bg-fill-medium active:bg-fill-medium",
@@ -42,14 +43,14 @@ const segmentedControl = tv({
42
43
  ] }
43
44
  },
44
45
  iconsOnlyBelow: {
45
- xs: { root: ["@max-xs:**:data-[slot=segmented-control-item]:relative @max-xs:**:data-[slot=segmented-control-item]:w-8 @max-xs:**:data-[slot=segmented-control-item]:px-0", "@max-xs:**:data-[slot=segmented-control-label]:sr-only @max-xs:**:data-[slot=segmented-control-count]:sr-only"] },
46
- sm: { root: ["@max-sm:**:data-[slot=segmented-control-item]:relative @max-sm:**:data-[slot=segmented-control-item]:w-8 @max-sm:**:data-[slot=segmented-control-item]:px-0", "@max-sm:**:data-[slot=segmented-control-label]:sr-only @max-sm:**:data-[slot=segmented-control-count]:sr-only"] },
47
- md: { root: ["@max-md:**:data-[slot=segmented-control-item]:relative @max-md:**:data-[slot=segmented-control-item]:w-8 @max-md:**:data-[slot=segmented-control-item]:px-0", "@max-md:**:data-[slot=segmented-control-label]:sr-only @max-md:**:data-[slot=segmented-control-count]:sr-only"] },
48
- lg: { root: ["@max-lg:**:data-[slot=segmented-control-item]:relative @max-lg:**:data-[slot=segmented-control-item]:w-8 @max-lg:**:data-[slot=segmented-control-item]:px-0", "@max-lg:**:data-[slot=segmented-control-label]:sr-only @max-lg:**:data-[slot=segmented-control-count]:sr-only"] },
49
- xl: { root: ["@max-xl:**:data-[slot=segmented-control-item]:relative @max-xl:**:data-[slot=segmented-control-item]:w-8 @max-xl:**:data-[slot=segmented-control-item]:px-0", "@max-xl:**:data-[slot=segmented-control-label]:sr-only @max-xl:**:data-[slot=segmented-control-count]:sr-only"] },
50
- "2xl": { root: ["@max-2xl:**:data-[slot=segmented-control-item]:relative @max-2xl:**:data-[slot=segmented-control-item]:w-8 @max-2xl:**:data-[slot=segmented-control-item]:px-0", "@max-2xl:**:data-[slot=segmented-control-label]:sr-only @max-2xl:**:data-[slot=segmented-control-count]:sr-only"] }
46
+ xs: { root: ["@max-xs:**:data-[slot=segmented-control-item]:relative @max-xs:**:data-[slot=segmented-control-item]:w-(--segment-size) @max-xs:**:data-[slot=segmented-control-item]:px-0", "@max-xs:**:data-[slot=segmented-control-label]:sr-only @max-xs:**:data-[slot=segmented-control-count]:sr-only"] },
47
+ sm: { root: ["@max-sm:**:data-[slot=segmented-control-item]:relative @max-sm:**:data-[slot=segmented-control-item]:w-(--segment-size) @max-sm:**:data-[slot=segmented-control-item]:px-0", "@max-sm:**:data-[slot=segmented-control-label]:sr-only @max-sm:**:data-[slot=segmented-control-count]:sr-only"] },
48
+ md: { root: ["@max-md:**:data-[slot=segmented-control-item]:relative @max-md:**:data-[slot=segmented-control-item]:w-(--segment-size) @max-md:**:data-[slot=segmented-control-item]:px-0", "@max-md:**:data-[slot=segmented-control-label]:sr-only @max-md:**:data-[slot=segmented-control-count]:sr-only"] },
49
+ lg: { root: ["@max-lg:**:data-[slot=segmented-control-item]:relative @max-lg:**:data-[slot=segmented-control-item]:w-(--segment-size) @max-lg:**:data-[slot=segmented-control-item]:px-0", "@max-lg:**:data-[slot=segmented-control-label]:sr-only @max-lg:**:data-[slot=segmented-control-count]:sr-only"] },
50
+ xl: { root: ["@max-xl:**:data-[slot=segmented-control-item]:relative @max-xl:**:data-[slot=segmented-control-item]:w-(--segment-size) @max-xl:**:data-[slot=segmented-control-item]:px-0", "@max-xl:**:data-[slot=segmented-control-label]:sr-only @max-xl:**:data-[slot=segmented-control-count]:sr-only"] },
51
+ "2xl": { root: ["@max-2xl:**:data-[slot=segmented-control-item]:relative @max-2xl:**:data-[slot=segmented-control-item]:w-(--segment-size) @max-2xl:**:data-[slot=segmented-control-item]:px-0", "@max-2xl:**:data-[slot=segmented-control-label]:sr-only @max-2xl:**:data-[slot=segmented-control-count]:sr-only"] }
51
52
  },
52
- iconsOnlyMeasured: { true: { root: ["data-icons-only:**:data-[slot=segmented-control-item]:relative data-icons-only:**:data-[slot=segmented-control-item]:w-8 data-icons-only:**:data-[slot=segmented-control-item]:px-0", "data-icons-only:**:data-[slot=segmented-control-label]:sr-only data-icons-only:**:data-[slot=segmented-control-count]:sr-only"] } }
53
+ iconsOnlyMeasured: { true: { root: ["data-icons-only:**:data-[slot=segmented-control-item]:relative data-icons-only:**:data-[slot=segmented-control-item]:w-(--segment-size) data-icons-only:**:data-[slot=segmented-control-item]:px-0", "data-icons-only:**:data-[slot=segmented-control-label]:sr-only data-icons-only:**:data-[slot=segmented-control-count]:sr-only"] } }
53
54
  }
54
55
  });
55
56
  const ICONS_ONLY_PRESETS = /* @__PURE__ */ new Set([
@@ -17,7 +17,7 @@ const select = tv({
17
17
  "**:data-[slot='button-content']:w-full **:data-[slot='button-content']:justify-between",
18
18
  "[&>span[data-slot='select-trigger-content']]:min-w-0 [&>span[data-slot='select-trigger-content']]:flex-1",
19
19
  "[&_[data-slot='select-trigger-content']_svg]:shrink-0",
20
- "h-control w-full gap-2 pr-3 pl-3.5",
20
+ "h-control w-full gap-2 rounded-(--control-radius) pr-3 pl-3.5",
21
21
  "border-[1.5px] border-line-field",
22
22
  "hover:bg-none",
23
23
  "text-subhead font-semibold text-foreground",
@@ -102,7 +102,7 @@ const select = tv({
102
102
  trigger: [
103
103
  "px-3 text-field font-field",
104
104
  "**:data-[slot='button-content']:font-field **:data-[slot='select-value']:font-field",
105
- "h-control py-0",
105
+ "h-control rounded-(--control-radius) py-0",
106
106
  "data-placeholder:font-field"
107
107
  ],
108
108
  item: ["text-field font-field", "**:data-[slot='select-item-text']:font-field"],
@@ -6,7 +6,8 @@ const sidebarItemRecipe = tv({
6
6
  sidebarItem: [
7
7
  "group/sidebar-item flex w-full items-center text-left",
8
8
  listCell,
9
- "h-[38px] gap-2 rounded-none border-0 py-0",
9
+ "h-(--row-height) gap-2 rounded-none border-0 py-0",
10
+ "md:pointer-fine:*:data-[slot=avatar]:-mr-[3px]",
10
11
  "md:py-0 md:[@media(max-height:600px)]:py-0",
11
12
  "max-md:h-auto max-md:min-h-11 max-md:py-2.5",
12
13
  "font-sans text-subhead font-medium text-foreground",
@@ -18,13 +19,13 @@ const sidebarItemRecipe = tv({
18
19
  sidebarItemIcon: [
19
20
  "flex shrink-0 items-center justify-center",
20
21
  "[&>svg]:shrink-0",
21
- "size-[19px] text-foreground-hint [&>svg]:size-4 [&>svg]:stroke-2",
22
+ "size-[19px] md:pointer-fine:size-4 text-foreground-hint [&>svg]:size-4 [&>svg]:stroke-2",
22
23
  "group-data-active/sidebar-item:text-sticker-ink"
23
24
  ],
24
25
  sidebarItemChannelIcon: [
25
26
  "flex shrink-0 items-center justify-center",
26
27
  "[&_svg]:shrink-0",
27
- "size-[19px] text-foreground-hint [&_svg]:size-4 [&_svg]:stroke-2",
28
+ "size-[19px] md:pointer-fine:size-4 text-foreground-hint [&_svg]:size-4 [&_svg]:stroke-2",
28
29
  "group-data-active/sidebar-item:text-sticker-ink"
29
30
  ],
30
31
  sidebarItemContent: ["has-[>[data-slot=sidebar-item-meta-icon]]:items-center"],
@@ -79,7 +80,7 @@ const sidebarItemRecipe = tv({
79
80
  false: {},
80
81
  true: { sidebarItem: [
81
82
  "h-auto items-start whitespace-normal",
82
- "min-h-[38px] py-[9px] md:py-[9px] md:[@media(max-height:600px)]:py-[9px]",
83
+ "min-h-(--row-height) py-[calc((var(--row-height)-20px)/2)] md:py-[calc((var(--row-height)-20px)/2)] md:[@media(max-height:600px)]:py-[calc((var(--row-height)-20px)/2)]",
83
84
  "**:data-[slot=sidebar-item-channel-icon]:h-lh **:data-[slot=sidebar-item-icon]:h-lh",
84
85
  "**:data-[slot=sidebar-item-meta-icon]:h-lh",
85
86
  "*:data-[slot=avatar]:mt-[calc((1lh-19px)/2)]"
@@ -2,9 +2,9 @@ import { tv } from "../../lib/tv.mjs";
2
2
  //#region src/components/sidebar/sidebar-section.recipe.ts
3
3
  const sidebarSectionRecipe = tv({ slots: {
4
4
  sidebarSection: ["group/section", "px-0"],
5
- sidebarList: ["flex flex-col gap-0.5 py-2"],
5
+ sidebarList: ["flex flex-col gap-0.5 py-2 md:pointer-fine:pt-1"],
6
6
  sidebarSectionEmpty: [
7
- "px-5 py-2",
7
+ "px-(--surface-inset) md:pointer-fine:pl-(--sidebar-text-axis) py-2",
8
8
  "font-sans text-footnote",
9
9
  "text-foreground-hint"
10
10
  ],
@@ -13,6 +13,7 @@ const sidebarSectionRecipe = tv({ slots: {
13
13
  "group/section-header",
14
14
  "my-0 h-auto gap-2 pt-4 pb-0 pl-5",
15
15
  "pr-[calc(max(0px,calc(8px-var(--scrollbar-size,0px)))+12px)]",
16
+ "md:pointer-fine:min-h-6 md:pointer-fine:pt-2 md:pointer-fine:pl-(--sidebar-glyph-axis) md:pointer-fine:pr-[calc(max(0px,calc(8px-var(--scrollbar-size,0px)))+10px)]",
16
17
  "not-pointer-fine:pb-1.5"
17
18
  ],
18
19
  sidebarSectionDisclosure: [
@@ -31,12 +32,14 @@ const sidebarSectionRecipe = tv({ slots: {
31
32
  sidebarSectionControls: ["flex shrink-0 items-center gap-1", "[&>[data-icon-inset]:nth-last-child(1_of_:not([data-base-ui-focus-guard],[aria-owns]))]:-mr-icon-inset"],
32
33
  sidebarSectionControl: [
33
34
  "relative flex h-6 w-auto min-w-6 shrink-0 items-center justify-center p-0 -my-1",
35
+ "md:pointer-fine:size-auto md:pointer-fine:h-6 md:pointer-fine:min-w-6",
34
36
  "[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
35
37
  "[--icon-inset:calc((--spacing(6)-14px)/2)]",
36
38
  "text-foreground-hint",
37
39
  "hover:text-foreground active:text-foreground focus-visible:text-foreground",
38
40
  "data-popup-open:bg-fill-medium data-popup-open:text-foreground",
39
41
  "not-pointer-fine:after:absolute not-pointer-fine:after:inset-[-11px] not-pointer-fine:after:content-['']",
42
+ "pointer-fine:after:absolute pointer-fine:after:inset-[-3px] pointer-fine:after:content-['']",
40
43
  "not-pointer-fine:not-first:after:left-[-3px] not-pointer-fine:not-last:after:right-[-3px]"
41
44
  ]
42
45
  } });
@@ -10,16 +10,16 @@ const sidebarRecipe = tv({ slots: {
10
10
  ],
11
11
  sidebarHeader: [
12
12
  "flex items-center",
13
- "h-(--shell-header-height,56px) shrink-0 gap-2 border-b border-line-seam px-(--column-margin)",
13
+ "h-(--shell-header-height,var(--header-height)) shrink-0 gap-2 border-b border-line-seam px-(--column-margin)",
14
14
  "bg-layer-sidebar text-foreground",
15
15
  "max-md:bg-layer-frame max-md:text-foreground-on-frame",
16
16
  headEdgePull,
17
- "[@media(max-height:600px)]:h-12"
17
+ "[@media(max-height:600px)]:h-(--header-height-short)"
18
18
  ],
19
19
  sidebarHeaderMask: ["pointer-events-none absolute inset-x-0 top-0 -z-10 hidden"],
20
20
  sidebarHeaderMaskLayer: ["absolute inset-x-0 top-0"],
21
21
  sidebarHeaderTitle: ["truncate font-bold", "font-sans text-headline text-foreground max-md:text-foreground-on-frame"],
22
- sidebarGroupLabel: ["px-5 pt-4 pb-0", "font-sans text-footnote font-bold normal-case tracking-normal text-foreground-hint"],
22
+ sidebarGroupLabel: ["px-5 pt-4 pb-0 md:pointer-fine:px-(--sidebar-glyph-axis) md:pointer-fine:pt-2", "font-sans text-footnote font-bold normal-case tracking-normal text-foreground-hint"],
23
23
  sidebarBody: ["relative flex min-h-0 flex-1 overflow-hidden", "bg-layer-sidebar max-md:bg-layer-canvas"],
24
24
  sidebarContent: ["relative flex min-h-0 min-w-0 flex-1"],
25
25
  sidebarViewport: ["size-full min-h-0 flex-1 overflow-x-hidden overflow-y-auto"],
@@ -9,9 +9,9 @@ const switchRecipe = tv({ slots: {
9
9
  "motion-reduce:transition-none",
10
10
  "not-data-disabled:cursor-pointer",
11
11
  "data-disabled:cursor-not-allowed",
12
- "after:absolute after:-inset-x-3 after:-inset-y-2 after:content-['']",
12
+ "after:absolute after:-inset-x-3 after:-inset-y-[calc((44px-var(--switch-height))/2+1.5px)] after:content-['']",
13
13
  "focus-visible:ring-line-strong focus-visible:ring-offset-layer-sidebar",
14
- "h-[30px] w-[50px] rounded-full border-[1.5px] border-line-strong bg-fill-muted p-[2.5px]",
14
+ "h-(--switch-height) w-(--switch-width) rounded-full border-[1.5px] border-line-strong bg-fill-muted p-[2.5px]",
15
15
  "duration-150",
16
16
  "data-checked:border-sticker-ink data-checked:bg-selected data-checked:ring-[1.5px] data-checked:ring-sticker-edge",
17
17
  "focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:shadow-focus",
@@ -22,7 +22,7 @@ const switchRecipe = tv({ slots: {
22
22
  "pointer-events-none block",
23
23
  "transition-transform ease-out",
24
24
  "motion-reduce:transition-none",
25
- "size-[22px] rounded-full bg-layer-card shadow-[0_0_0_1.5px_var(--line-strong)] data-[checked]:translate-x-5",
25
+ "size-[calc(var(--switch-height)-8px)] rounded-full bg-layer-card shadow-[0_0_0_1.5px_var(--line-strong)] data-checked:translate-x-[calc(var(--switch-width)-var(--switch-height))]",
26
26
  "duration-220 ease-slide",
27
27
  "data-checked:bg-sticker-paper data-checked:shadow-[0_0_0_1.5px_var(--sticker-ink)]",
28
28
  "data-disabled:bg-fill-muted data-disabled:shadow-[0_0_0_1.5px_var(--foreground-disabled)]",
@@ -41,7 +41,7 @@ const tabs = tv({
41
41
  default: {
42
42
  list: ["gap-0.5"],
43
43
  tab: [
44
- "h-8 justify-center gap-1.5 rounded-control border-[1.5px] border-transparent px-3",
44
+ "h-8 md:pointer-fine:h-control justify-center gap-1.5 rounded-control border-[1.5px] border-transparent px-3",
45
45
  "after:inset-x-0 after:-inset-y-1.5",
46
46
  "hover:bg-fill-medium active:bg-fill-medium",
47
47
  "data-active:border-sticker-ink data-active:bg-selected data-active:font-bold data-active:text-sticker-ink data-active:ring-[1.5px] data-active:ring-sticker-edge",
@@ -8,7 +8,7 @@ const taskBoardColumn = tv({ slots: {
8
8
  ],
9
9
  trigger: [
10
10
  "group flex w-full touch-manipulation items-center justify-between text-left outline-none",
11
- "min-h-8 gap-2 rounded-control",
11
+ "min-h-8 md:pointer-fine:min-h-control gap-2 rounded-control",
12
12
  "font-sans text-footnote font-bold text-foreground-muted",
13
13
  "data-[panel-open]:mb-2",
14
14
  "focus-visible:shadow-focus"
@@ -5,7 +5,7 @@ const taskListSection = tv({ slots: {
5
5
  trigger: [
6
6
  "group flex w-full touch-manipulation items-center text-left outline-none",
7
7
  "disabled:cursor-default",
8
- "min-h-8 justify-start gap-2 rounded-control",
8
+ "min-h-8 md:pointer-fine:min-h-control justify-start gap-2 rounded-control",
9
9
  "font-sans text-footnote font-bold text-foreground-hint",
10
10
  "focus-visible:shadow-focus"
11
11
  ],
@@ -5,7 +5,8 @@ const tasksPanel = tv({ slots: {
5
5
  toolbar: [
6
6
  "grid shrink-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-2",
7
7
  "@min-md/tasks:flex @min-md/tasks:flex-wrap @min-md/tasks:items-center @min-md/tasks:justify-between",
8
- "min-h-14 border-b border-line-seam bg-layer-canvas px-5 py-3",
8
+ "min-h-(--header-height) border-b border-line-seam bg-layer-canvas px-(--surface-inset) py-3",
9
+ "md:pointer-fine:@min-[40rem]/tasks:py-0",
9
10
  "@max-md/tasks:px-4"
10
11
  ],
11
12
  toolbarGroup: ["flex flex-wrap items-center gap-2"],
@@ -20,14 +21,14 @@ const tasksPanel = tv({ slots: {
20
21
  toolbarLabel: [
21
22
  "inline-flex items-center",
22
23
  "[&_svg]:size-3.5 [&_svg]:shrink-0 [&>svg:last-child]:size-3",
23
- "h-8 gap-1.5 rounded-control px-2.5",
24
+ "h-8 md:pointer-fine:h-control gap-1.5 rounded-control px-2.5",
24
25
  "bg-transparent inset-ring inset-ring-line-seam",
25
26
  "font-sans text-footnote font-semibold text-foreground-muted whitespace-nowrap"
26
27
  ],
27
28
  viewport: [
28
29
  "min-h-0 flex-1 overflow-auto overscroll-contain p-4",
29
30
  "pb-[calc(1rem+env(safe-area-inset-bottom,0px))]",
30
- "bg-layer-canvas px-5",
31
+ "bg-layer-canvas px-(--surface-inset)",
31
32
  "@max-md/tasks:px-4"
32
33
  ],
33
34
  empty: ["flex h-full flex-col items-center justify-center"]
@@ -8,7 +8,7 @@ const textSelectionToolbar = tv({ slots: {
8
8
  ],
9
9
  button: [
10
10
  "flex shrink-0 items-center gap-1.5 outline-none transition-colors",
11
- "h-7 rounded-chip px-2.5 whitespace-nowrap text-foreground",
11
+ "h-7 rounded-chip md:pointer-fine:h-control md:pointer-fine:rounded-control px-2.5 whitespace-nowrap text-foreground",
12
12
  "hover:bg-fill-medium active:bg-fill-medium",
13
13
  "focus-visible:shadow-focus",
14
14
  "[&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-foreground-muted"
@@ -6,7 +6,7 @@ const textarea = tv({ slots: { root: [
6
6
  "scroll-pb-(--ta-scroll-pb,0)",
7
7
  "font-heading text-field font-field outline-none",
8
8
  "py-2.5",
9
- "rounded-control border-[1.5px] border-line-field bg-layer-card",
9
+ "rounded-(--control-radius) border-[1.5px] border-line-field bg-layer-card",
10
10
  "text-foreground placeholder:text-foreground-hint",
11
11
  "shadow-none transition-shadow duration-90 ease-out focus-visible:shadow-focus",
12
12
  "data-invalid:border-danger-strong",
@@ -4,7 +4,7 @@ import { panelHeadActions, panelHeadEdges } from "../panel/panel-header.recipe.m
4
4
  const threadPanel = tv({ slots: {
5
5
  root: ["isolate flex size-full min-h-0 flex-col", "relative bg-layer-canvas font-sans text-foreground"],
6
6
  header: [
7
- "flex h-(--shell-header-height,56px) shrink-0 items-center gap-3 max-md:gap-2",
7
+ "flex h-(--shell-header-height,var(--header-height)) shrink-0 items-center gap-3 max-md:gap-2",
8
8
  panelHeadEdges,
9
9
  "border-b border-line-seam bg-layer-canvas"
10
10
  ],
@@ -19,7 +19,7 @@ const threadPanel = tv({ slots: {
19
19
  search: [
20
20
  "relative z-20 flex shrink-0 items-center gap-1.5 py-2",
21
21
  "[&>[data-slot=input-group]]:min-w-0 *:data-[slot=input-group]:flex-1",
22
- "border-b border-line-hairline bg-layer-canvas px-5"
22
+ "border-b border-line-hairline bg-layer-canvas px-(--surface-inset)"
23
23
  ],
24
24
  searchTrailing: ["ml-auto flex shrink-0 items-center gap-1.5"],
25
25
  searchCount: ["shrink-0 whitespace-nowrap font-mono tabular-nums", "text-mono-meta text-foreground-hint"],
@@ -28,7 +28,7 @@ const threadPanel = tv({ slots: {
28
28
  "border-b border-line-hairline bg-transparent pb-timeline-end",
29
29
  "**:data-message-item:bg-transparent"
30
30
  ],
31
- replySummary: ["pt-timeline-start", "px-5"],
31
+ replySummary: ["pt-timeline-start", "px-(--surface-inset)"],
32
32
  replyCount: ["my-timeline-row border-b pb-2 text-center font-mono", "border-line-hairline text-mono-meta text-foreground-hint"],
33
33
  footer: ["pt-0", "text-caption text-foreground-hint"]
34
34
  } });
@@ -14,7 +14,7 @@ const toggleGroup = tv({ slots: {
14
14
  "gap-[3px] rounded-[11px] bg-fill-muted p-[3px]"
15
15
  ],
16
16
  item: [
17
- "inline-flex h-8 min-w-0 shrink-0 items-center justify-center gap-1.5",
17
+ "inline-flex h-8 md:pointer-fine:h-control min-w-0 shrink-0 items-center justify-center gap-1.5",
18
18
  "whitespace-nowrap",
19
19
  "outline-2 outline-offset-2 outline-transparent transition-[background-color,color,box-shadow,transform] duration-100",
20
20
  "disabled:pointer-events-none data-disabled:cursor-not-allowed",
@@ -338,11 +338,16 @@
338
338
  --field-font-size: 16px;
339
339
  --field-font-weight: 400;
340
340
  --field-line-height: 24px;
341
+ /* Between two fields of a form, and between the parts of a form section: two
342
+ airs. */
343
+ --field-gap: calc(2 * var(--control-air));
341
344
  --card-title-font-size: 16px;
342
345
  --card-title-font-weight: 600;
343
346
  --card-title-line-height: 24px;
344
- /* A default control: a field, a Button at lg, an icon button at icon-lg. A
345
- finger's 44px on a phone, Feishu's 32px on a desktop. */
347
+ /* A default control: a field, a select, a Button, a tab, a selected segment, a
348
+ menu option, a sidebar row. A finger's 44px on a phone; 34px on a desktop,
349
+ the one desktop size for all of them (stdrc, 2026-11-04; Feishu's 32px
350
+ before). */
346
351
  --control-height: 44px;
347
352
  /* The full-width call to action across a sign-in, sign-up or setup form
348
353
  (Button xl). */
@@ -358,6 +363,81 @@
358
363
  /* A chip or a button embedded in a field: the control height less the embedded
359
364
  inset above and below, so the field keeps its height. */
360
365
  --control-embedded-size: 32px;
366
+ /* The air of the size chain: between a label, its control and its help, around
367
+ a control in a bar, and half of the gap between two fields. Every step below
368
+ is the control height plus or minus a number of airs. */
369
+ --control-air: 6px;
370
+ /* The corner of a control at the control height (a field, a Select, a Button
371
+ at lg and icon-lg), about a quarter of its height: 10px at 44 on a phone and
372
+ a touch screen, 8px at 32 on a desktop. */
373
+ --control-radius: 10px;
374
+ /* Every header: a panel, a sidebar or a thread head, a dialog's bar, a
375
+ drawer's head, a rail head. The control height with the header air above and
376
+ below, so the seams of a screen line up. */
377
+ --header-height: calc(var(--control-height) + 2 * var(--header-air));
378
+ /* The air above and below the control in a header: one control air on a phone
379
+ and a touch screen (44 + 12 = 56); 5px on a desktop, so a 34px control and
380
+ its head make 44 (34 + 10). */
381
+ --header-air: var(--control-air);
382
+ /* Every header in a window shorter than 600px. On a desktop, a bar button with
383
+ one air above and below (28 + 12 = 40). */
384
+ --header-height-short: 48px;
385
+ /* An icon button in a bar: a head's actions and its x, the composer's icons, a
386
+ row's actions. The control height on a phone and a touch screen; 28px on a
387
+ desktop, the size of the header's icon tile, so a head's tile and buttons
388
+ share one size and one centre line. Its hit area still reaches 44px. */
389
+ --bar-button-size: var(--control-height);
390
+ /* The corner of a bar button, about a quarter of its height: 10px at 44 on a
391
+ phone and a touch screen, 6px at the 28px bar button with a mouse on a
392
+ desktop. */
393
+ --bar-button-radius: 10px;
394
+ /* An item on the frame: the top bar's and the title bar's buttons, the
395
+ workspace button and the search box. 36px on a phone and a touch screen; the
396
+ control height on a desktop, so the top bar is a header (34 + 2 x 5 = 44
397
+ with a mouse). */
398
+ --frame-item-height: 36px;
399
+ /* The switch's track: 50 x 30 on a phone and a touch screen, macOS's regular
400
+ 38 x 22 on a desktop. Its knob is the track height less 8px (a 1.5px border
401
+ and 2.5px of padding each side, so the 1px of track between the knob's ring
402
+ and the border stays) and travels the width less the height. */
403
+ --switch-width: 50px;
404
+ /* The switch's track height. */
405
+ --switch-height: 30px;
406
+ /* A sidebar row from 768px, 2px apart; its highlight fills the row. On a
407
+ desktop the control height (34px): one size for a sidebar item, a menu
408
+ option, a compact list row, a field, a select, a Button, a tab and a
409
+ selected segment (stdrc, 2026-11-04). A phone list of places keeps 44px. */
410
+ --row-height: 38px;
411
+ /* An option of a popup list or a menu item with one line: 36px on a phone and
412
+ a touch screen, the row height (34px, the control height) on a desktop. */
413
+ --option-height: 2.25rem;
414
+ /* Above and below the tallest part of a settings row (ListRow plain): two
415
+ airs. */
416
+ --settings-row-inset: calc(2 * var(--control-air));
417
+ /* Between two sections of a form (FormSection), half above and half below the
418
+ hairline between them: 32px on a phone and a touch screen, two field gaps
419
+ (24px) on a desktop. */
420
+ --section-gap: 32px;
421
+ /* From a head's seam to the first thing of the content under it: the page
422
+ inset on a phone and a touch screen; 12px on a desktop, so the content sits
423
+ close under its compact head (stdrc, 2026-11-05). */
424
+ --page-top: var(--page-inset);
425
+ /* The inset of a column's content, a settings page, a drawer's body and a
426
+ dialog's form: 16px on a phone and a touch screen; 16px with a mouse on a
427
+ desktop too (20 before 2026-11-05), where the column margin, the list cells'
428
+ content and the heads follow it, one edge. */
429
+ --page-inset: 16px;
430
+ /* The rail, its 1px seam included: 57px on a touch screen, 49px on a desktop
431
+ (48 + 1, the short window's rail). Its own size, not the header's. */
432
+ --rail-width: 57px;
433
+ /* A rail item: 40px on a touch screen, 36px on a desktop. */
434
+ --rail-item: 40px;
435
+ /* The rail in a window shorter than 600px, its 1px seam included; with a mouse
436
+ the rail is always this width (48 + 1). */
437
+ --rail-width-short: 49px;
438
+ /* A rail item in a window shorter than 600px; with a mouse rail items are
439
+ always this size. */
440
+ --rail-item-short: 36px;
361
441
  /* The message step of a timeline: from the last line of one turn to the first
362
442
  line of the next (a sender line, an event line). Every row keeps the row
363
443
  inset above and below its text; the first row of a turn adds the rest above
@@ -605,10 +685,25 @@
605
685
  --field-line-height: 22px;
606
686
  --card-title-font-size: 14px;
607
687
  --card-title-line-height: 22px;
608
- --control-height: 32px;
688
+ --control-height: 34px;
609
689
  --control-height-cta: 40px;
610
690
  --control-icon-size: 18px;
611
- --control-hit-inset: 6px;
612
- --control-embedded-size: 20px;
691
+ --control-hit-inset: 5px;
692
+ --control-embedded-size: 22px;
693
+ --control-radius: 8px;
694
+ --header-air: 5px;
695
+ --header-height-short: calc(var(--bar-button-size) + 2 * var(--control-air));
696
+ --bar-button-size: 28px;
697
+ --bar-button-radius: 6px;
698
+ --frame-item-height: var(--control-height);
699
+ --switch-width: 38px;
700
+ --switch-height: 22px;
701
+ --row-height: var(--control-height);
702
+ --option-height: var(--row-height);
703
+ --section-gap: calc(2 * var(--field-gap));
704
+ --page-top: 12px;
705
+ --page-inset: 16px;
706
+ --rail-width: var(--rail-width-short);
707
+ --rail-item: var(--rail-item-short);
613
708
  }
614
709
  }