@botiverse/raft-ui 0.18.0 → 0.20.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/CHANGELOG.md CHANGED
@@ -1,3 +1,129 @@
1
+ ## [0.20.0](https://github.com/botiverse/raft-design/compare/v0.19.0...v0.20.0) (2026-10-22)
2
+
3
+ ### Design System
4
+
5
+ - **Hover and press**: hover and press are one translucent ink layer over the resting colour, never a fill swap, in two tiers sized by what is under the pointer. Subtle for rows, messages, readable cards and a row whose menu is open; medium for small controls and for pressing anything clickable. A colour sticker and the frame get their own medium alphas. Selected items and fields don't hover, a message has no press, and every hover sits under `(hover: hover)`. It applies to every row, card, button, badge, tab, segment, sidebar and rail item, table row, inbox item and menu. ([#681](https://github.com/botiverse/raft-design/pull/681))
6
+ - Why: the same hover looked different on paper, the sidebar and a card, rows and messages used three different steps, and some clickable parts gave no feedback on press; one layer in two tiers reads the same everywhere.
7
+ - Affects: subtle `fill-subtle` 4% light / 3% dark; medium `fill-medium` 7.8% light / 6.5% dark (was 7.8%); `sticker-medium` 10% (was 12.2%); `fill-frame-medium` 10% light (was 12.2%) / 6% dark (was 7.8%); dark `brand-soft` 16% → 20%; colour `-hover` / `-active` pairs mix 10% ink (was 12%).
8
+ - Docs: https://raft-design.botiverse.build/foundations/states/#hover-and-press
9
+ - **Inside a frame**: a control embedded in another component's frame (a field, the composer, a toolbar, a card or media corner) wears `radius-chip` (6px) and sits the same distance from every side of the frame and from its siblings; frames themselves don't change. It applies to ComboboxSelect, Combobox, InputGroup, the composer, the message hover and text-selection toolbars, the lightbox and multi-select bars, CodeBlock, media and file card actions, popover close, menus and SegmentedControl / ToggleGroup. ([#682](https://github.com/botiverse/raft-design/pull/682))
10
+ - Why: embedded buttons at the free-standing 8px corner read nearly round at 20px, and their offsets differed per component; one corner and one even inset make them read as one kind of thing.
11
+ - Affects: new tokens `control-embedded-inset` 6px and `control-embedded-size` 20px desktop / 32px phone (utilities `embedded-inset`, `control-embedded`); insets 6px in a field, the composer and a corner, 4px in a floating toolbar and for a popup's highlight, 3px between segments.
12
+ - Docs: https://raft-design.botiverse.build/foundations/shape/#inside-a-frame
13
+
14
+ ### Breaking Changes
15
+
16
+ - **Dialog** `size`: `sm` is 400px (was 384) and `lg` is 640px (was 512); `md` 448 and `wide` 896 are unchanged. Sizes are chosen by content: `sm` for a decision, `md` for a small task, `lg` for content that needs width. ([#676](https://github.com/botiverse/raft-design/pull/676))
17
+ - Why: the steps between sizes were too small to mean anything, and a confirmation could be 384 or 400 wide depending on the component; now every confirmation is 400, the same as AlertDialog.
18
+ - Affects: `sm` `max-w-sm` (384px) → 400px; `lg` `max-w-lg` (512px) → 640px.
19
+ - Migrate: dialogs with `size="sm"` grow 16px and `size="lg"` 128px; check their content and pick the size by content. Set width with `size` only, not your own `max-w-*`.
20
+ - Docs: https://raft-design.botiverse.build/components/dialog/#variants
21
+ - **Hover tokens**: renamed to their tier. The old names stay as deprecated aliases until 1.0, but they now resolve to the tier values, so some hovers change. ([#681](https://github.com/botiverse/raft-design/pull/681))
22
+ - Why: names that said where a fill was used (row hover, message hover, row open, pressed) could not describe two tiers that apply everywhere.
23
+ - Affects: `fill-row-hover`, `fill-row-open`, `fill-message-hover` → `fill-subtle` (row open 5% → 4%, message hover 3% → 4%); `fill-pressed` → `fill-medium`; `fill-frame-pressed` → `fill-frame-medium`; `sticker-pressed` → `sticker-medium`; utilities `row-hover-overlay`, `row-open-overlay` → `overlay-subtle`, `press-overlay` → `overlay-medium`, `press-overlay-sticker` → `overlay-sticker-medium`. Overlays are registered with the `cn` helper as background images, so a later `hover:bg-none` or another tier replaces them.
24
+ - Migrate: run `node node_modules/@botiverse/raft-ui/codemods/token-names.mjs <paths>`; it renames classes, `var(--…)` and `var(--color-…)` forms. Then move readable cards that used `press-overlay` for hover to `overlay-subtle`. Tests that assert a token name in a class (for example `var(--fill-row-open)` on a message) look for the new name.
25
+ - Docs: https://raft-design.botiverse.build/foundations/color/#hover-overlays
26
+ - **MessageItemToolbar** and **MessageTextSelectionToolbar**: 36px tall (was 32), buttons 4px from the frame and 4px apart (was 2px), at `radius-chip`. The hover toolbar rides 18px above the row (was 16). ([#682](https://github.com/botiverse/raft-design/pull/682))
27
+ - Why: at a 2px inset the buttons' hover fill nearly touched the frame; see Inside a frame above.
28
+ - Affects: toolbar height 32 → 36px; inset and gap 2 → 4px; the message header's default reserve `--mi-toolbar-reserve` 6.5rem → 7rem (three buttons).
29
+ - Migrate: if you set `--mi-toolbar-reserve` yourself, compute it as `1rem + 2rem × buttons` (was `1.25rem + 1.75rem × buttons`). Leave at least 18px above the first message of a scroll area and above a thread's root message.
30
+ - Docs: https://raft-design.botiverse.build/components/message-item/
31
+ - **MessageMultiSelectToolbar**: 36px tall (was 48) with 28px buttons (was 32) 4px from the frame; on a touch screen each button takes a 44px tap. The buttons go in the new `MessageMultiSelectToolbarActions`. ([#682](https://github.com/botiverse/raft-design/pull/682))
32
+ - Why: every floating toolbar is now 36px with 28px buttons; the touch hit area keeps the smaller buttons easy to tap.
33
+ - Affects: height 48 → 36px; buttons 32 → 28px; inset 8 / 10 → 4px; gap 6 → 4px.
34
+ - Migrate: wrap the toolbar's buttons in `MessageMultiSelectToolbarActions`, which owns their 4px gap and the touch hit areas; remove your own gap or padding there.
35
+ - Docs: https://raft-design.botiverse.build/components/message-item/
36
+ - **ComboboxSelect**, **Combobox** and **InputGroup** chips and end buttons: chips and embedded buttons in a field are 20px on a desktop and 32px on a phone (chips were 28px, end buttons 32px), 6px from the field's edge and from each other, at `radius-chip`, so a field with chips stays 32 / 44px tall (it grew to 42). ([#682](https://github.com/botiverse/raft-design/pull/682))
37
+ - Why: 28px chips made a field with chips taller than one without, and the embedded buttons sat at different offsets per component.
38
+ - Affects: chips 28 → 20px desktop / 32px phone; chip remove 16 / 28px at `radius-sticker`; InputGroup end-addon Button 32 → 20 / 32px with a 16px icon; ComboboxSelect `sm` keeps 22px chips at 3px, `lg` uses 40px chips in a 52px field.
39
+ - Migrate: remove size, offset or radius overrides on chips and end-addon buttons in fields. In an InputGroup end addon, put a Kbd before the Button, so on a phone (Kbd hidden) the button is the last child and ends 6px from the edge.
40
+ - Docs: https://raft-design.botiverse.build/components/combobox-select/
41
+
42
+ ### Added
43
+
44
+ - **CodeBlockPre**: renders the code's `pre`. While the code overflows it is a keyboard-reachable named region (`tabindex=0`, `role=region`, `aria-label` "Code" by default), so it can be scrolled from the keyboard; code that fits adds no Tab stop. ([#682](https://github.com/botiverse/raft-design/pull/682))
45
+ - Why: a scrolling code block could not be reached from the keyboard in Safari.
46
+ - Docs: https://raft-design.botiverse.build/components/code-block/
47
+ - **MessageMultiSelectToolbarActions**: the multi-select toolbar's actions at its end, 4px apart, with touch hit areas. ([#682](https://github.com/botiverse/raft-design/pull/682))
48
+ - Why: see MessageMultiSelectToolbar above.
49
+ - Docs: https://raft-design.botiverse.build/components/message-item/
50
+ - **Press feedback**: Badge, the mention sticker, InboxItem, Table rows, the system-message disclosure and the selected SidebarItem now press with the medium tier. ([#681](https://github.com/botiverse/raft-design/pull/681))
51
+ - Why: anything clickable presses with the medium tier; these gave no feedback on press.
52
+ - Affects: solid Badge `sticker-medium`; soft and outline Badge, InboxItem, Table row, system-message disclosure `fill-medium`; selected SidebarItem `sticker-medium`.
53
+ - Docs: https://raft-design.botiverse.build/foundations/states/#hover-and-press
54
+
55
+ ### Changed
56
+
57
+ - **MessageItem**, **MessageAttachmentCard**, **MessageReplies**, **TaskCard** and menu-open rows: hover with the subtle tier. ([#681](https://github.com/botiverse/raft-design/pull/681))
58
+ - Why: a message, a readable card and a row whose menu is open are large things people read; one subtle step keeps text contrast and never stacks with the menu-open fill.
59
+ - Affects: message hover 3% / 2.2% → 4% / 3%; menu-open row 5% / 4% → 4% / 3%; attachment card, replies bar and task card 7.8% → 4% / 3%. Press stays medium.
60
+ - Docs: https://raft-design.botiverse.build/foundations/states/#hover-and-press
61
+ - **SegmentedControl**, **ToggleGroup** and **Select**: a selected segment or toggle no longer darkens on hover and presses with `sticker-medium`; the Select trigger and the top-bar SearchTrigger don't hover, only press. SegmentedControl and ToggleGroup items are 3px apart (was 2). ([#681](https://github.com/botiverse/raft-design/pull/681), [#682](https://github.com/botiverse/raft-design/pull/682))
62
+ - Why: selected items and fields don't hover, like selected tabs and the active sidebar item.
63
+ - Affects: segment gap 2 → 3px.
64
+ - Docs: https://raft-design.botiverse.build/components/segmented-control/
65
+ - **DropdownMenu**, **ContextMenu**, **Select**, **Combobox**, **ComboboxSelect**, the suggestion list and quick reactions: the highlighted item sits 4px inside the popup at `radius-chip`, and separators take 4px. ([#682](https://github.com/botiverse/raft-design/pull/682))
66
+ - Why: see Inside a frame above.
67
+ - Affects: highlight inset 6px (quick reactions 8px) → 4px; highlight radius 8 → 6px; separator margins 6 → 4px.
68
+ - Docs: https://raft-design.botiverse.build/components/dropdown-menu/
69
+ - **CodeBlock**: the copy and other actions sit 6px from the corner at `radius-chip`, and without a header they take a column of their own instead of covering the first line; the code scrolls beside them. With a header, the actions sit 6px from its end, centred in it, 4px apart. ([#682](https://github.com/botiverse/raft-design/pull/682))
70
+ - Why: on a phone the copy button covered the first line of code.
71
+ - Affects: action offset 9px (no header) / 5px (header) → 6px; radius 8 → 6px; a one-line block keeps 6px under the button.
72
+ - Docs: https://raft-design.botiverse.build/components/code-block/
73
+ - **Composer**, **MessageActionCard**, **Popover** header close, **Lightbox** info bar, video preview, gallery and file card actions: embedded buttons at `radius-chip`, 6px from the frame's sides (4px in the lightbox bar, whose buttons are 28px with a 44px touch target). ([#682](https://github.com/botiverse/raft-design/pull/682))
74
+ - Why: see Inside a frame above.
75
+ - Affects: composer buttons 8 / 13px from the sides and 9px from the bottom → 6px; media and file card actions 9 / 7 / 5px → 6px; lightbox buttons 32 → 28px.
76
+ - Docs: https://raft-design.botiverse.build/foundations/shape/#inside-a-frame
77
+
78
+ ### Fixed
79
+
80
+ - **ComboboxSelect** and **FloatButton**: hover no longer sticks after a tap on a touch screen; their `:hover` rules are under `(hover: hover)`, and the ghost FloatButton hovers and presses with the overlay instead of a fill swap. ([#681](https://github.com/botiverse/raft-design/pull/681))
81
+ - Why: ungated `:hover` rules stayed applied after a tap.
82
+ - Docs: https://raft-design.botiverse.build/foundations/states/#hover-and-press
83
+
84
+ ## [0.19.0](https://github.com/botiverse/raft-design/compare/v0.18.0...v0.19.0) (2026-10-21)
85
+
86
+ ### Design System
87
+
88
+ - **Collapsing the sidebar**: on desktop the sidebar toggle is the first item of the rail's footer, above Help and Settings. Its icon shows what pressing it does (`panel-left-close` while the sidebar is shown, `panel-left-open` while it is hidden), its tooltip says Collapse Sidebar or Expand Sidebar with the shortcut (⌘\ on macOS, Ctrl+\ elsewhere), and the state is remembered on the device. It applies to AppShellSidebarTrigger. ([#674](https://github.com/botiverse/raft-design/pull/674))
89
+ - Why: the toggle belongs to the frame rather than to a page, so it needs one fixed place and one behavior whatever the main area shows.
90
+ - Docs: https://raft-design.botiverse.build/patterns/navigation/#collapsing-the-sidebar
91
+
92
+ ### Breaking Changes
93
+
94
+ - **BannerAction**: the buttons sit at the bottom right, at the end of the last line of text and centered on it, while the text fits beside them on one line. When the text would wrap beside them, or the banner is narrower than 480px, they take a line of their own under the text, aligned to the end. `BannerAction` places itself; it sets `data-wrap` when it takes its own line. ([#672](https://github.com/botiverse/raft-design/pull/672))
95
+ - Why: the button sat in a start-aligned row under the text, away from where the eye ends; it now comes after what it acts on and ends where reading ends, as a dialog's commit button does.
96
+ - Affects: action placement from a row under the text (start-aligned, `mt-2`) → the end of the last line from 452px of banner content (a 480px banner at `md`), otherwise its own line under the text, end-aligned; `size="sm"` banners with an action get vertical padding that centers a 32px button on the line.
97
+ - Migrate: remove layout overrides on `BannerAction`, such as `col-span-full`, `col-start-*`, `row-start-*` or `mt-*`, which now fight its placement. Give `Banner` a width (or let it fill its parent); never `w-fit`. Use `variant="default" size="sm"` for the button, and put a ghost Dismiss (`variant="ghost" size="sm"`) before it when the banner can be dismissed; move a × out of `BannerAction` into `BannerClose`, on a banner with no button.
98
+ - Docs: https://raft-design.botiverse.build/components/banner/
99
+ - **AppShellSidebarTrigger**: the trigger is a disclosure button. It has `aria-expanded` and `aria-controls` naming the sidebar slot, instead of `aria-pressed`, and its labels are Title Case: "Collapse Sidebar" and "Expand Sidebar". ([#674](https://github.com/botiverse/raft-design/pull/674))
100
+ - Why: the trigger shows and hides a region, which assistive technology announces as expanded or collapsed, not as a pressed toggle.
101
+ - Affects: `aria-pressed` → `aria-expanded`; new `aria-controls`; `AppShellSidebarSlot` gets a generated `id` when the root is `sidebarCollapsible` and none is passed; label and `title` "Collapse sidebar" / "Expand sidebar" → "Collapse Sidebar" / "Expand Sidebar".
102
+ - Migrate: update tests and selectors that read `aria-pressed` or the old labels to `aria-expanded` and the Title Case labels. When you wrap the trigger in a Tooltip, pass `title={undefined}` and repeat the label in the tooltip; to translate it, pass your own `aria-label`.
103
+ - Docs: https://raft-design.botiverse.build/components/app-shell/
104
+ - **AppShellMainSlot** and **AppShellSidebarSlot**: in a collapsible shell, the main area has a z-index only while it slides over the sidebar, and the collapsed sidebar is `inert`. ([#674](https://github.com/botiverse/raft-design/pull/674))
105
+ - Why: the main area sat at z-index 45 at rest and covered the app's own fixed layers, such as a bar at z-40, and a collapsed sidebar's controls stayed in the tab order while hidden.
106
+ - Affects: main slot `z-45` always → `z-2` with `data-sliding` only during the slide, none at rest; the expanded sidebar slot is `relative z-1`, so a resize handle past its edge stays grabbable; the collapsed sidebar slot adds `inert` to `aria-hidden`.
107
+ - Migrate: remove workarounds that lowered the app's own layers below z-45 or raised them over the main slot, and any code or test that reads an inline or computed z-index on the main slot at rest. Don't make the collapsed sidebar focusable yourself; a shortcut that collapses it while focus is inside should move focus to the trigger first.
108
+ - Docs: https://raft-design.botiverse.build/components/app-shell/
109
+
110
+ ### Added
111
+
112
+ - **BannerClose**: the × of a banner that has no button, a frameless icon button at the end of the first line whose icon box ends on the banner's padding. Its label defaults to "Dismiss". ([#672](https://github.com/botiverse/raft-design/pull/672))
113
+ - Why: a × put in `BannerAction` read as one of the actions and sat on the wrong line; the close control now has a place of its own.
114
+ - Affects: `variant="ghost"`, `size="icon-sm"` (32px), centered on the first line. With `BannerAction` in the same banner, raft-ui warns in development and moves the actions to a line of their own.
115
+ - Docs: https://raft-design.botiverse.build/components/banner/
116
+
117
+ ### Fixed
118
+
119
+ - **BannerTitle** and **BannerDescription**: a long word or URL breaks onto the next line instead of pushing the banner wider. ([#672](https://github.com/botiverse/raft-design/pull/672))
120
+ - Why: an unbroken string, such as a URL, ran out of the text column and under the actions; the text now wraps inside its column.
121
+ - Docs: https://raft-design.botiverse.build/components/banner/
122
+ - **ResizableHandle**: under a coarse pointer, such as a finger on a tablet, the handle is easier to grab. ([#667](https://github.com/botiverse/raft-design/pull/667))
123
+ - Why: a 12px handle was too narrow to hit with a finger.
124
+ - Affects: handle hit area 12px → 24px under `pointer: coarse`, still centered on the boundary; mouse and pen keep 12px.
125
+ - Docs: https://raft-design.botiverse.build/components/resizable/
126
+
1
127
  ## [0.18.0](https://github.com/botiverse/raft-design/compare/v0.17.0...v0.18.0) (2026-10-21)
2
128
 
3
129
  ### Design System
@@ -122,7 +248,7 @@
122
248
  - **Row hover**: rows hover with their own lightest visible step, not the press overlay; a row whose menu is open is marked with one slightly stronger step. ([#641](https://github.com/botiverse/raft-design/pull/641), [#643](https://github.com/botiverse/raft-design/pull/643))
123
249
  - Why: the press fill made hovered messages and list rows read as grey and heavy, and on a phone a long-press menu left no mark of which row it belonged to.
124
250
  - Affects: hover `fill-pressed` 7.8% → `fill-row-hover` 4% (dark 3%), messages `fill-message-hover` 3% (dark 2.2%), menu-open `fill-row-open` 5% (dark 4%). Press and selection are unchanged; hover stays lighter than selection.
125
- - Docs: https://raft-design.botiverse.build/foundations/states/#row-hover
251
+ - Docs: https://raft-design.botiverse.build/foundations/states/#hover-and-press
126
252
 
127
253
  ### Added
128
254
 
@@ -136,11 +262,11 @@
136
262
  - **Message, SidebarItem, List, Inbox, Search, Table and File rows**: hover with `fill-row-hover` (messages with `fill-message-hover`) instead of `fill-pressed`. ([#641](https://github.com/botiverse/raft-design/pull/641))
137
263
  - Why: see Row hover above.
138
264
  - Affects: hover background 7.8% → 4% (messages 3%).
139
- - Docs: https://raft-design.botiverse.build/foundations/states/#row-hover
265
+ - Docs: https://raft-design.botiverse.build/foundations/states/#hover-and-press
140
266
  - **Rows with a menu open** (right-click, ⋯ menu, phone long-press): take `fill-row-open` while the menu is open; selection still wins. **Comment cards** hover with `fill-row-hover`. ([#643](https://github.com/botiverse/raft-design/pull/643))
141
267
  - Why: the open menu needs one marker of which row it belongs to, including on a phone where there is no hover.
142
268
  - Affects: menu-open background `fill-row-open` 5% / 4%.
143
- - Docs: https://raft-design.botiverse.build/foundations/states/#row-hover
269
+ - Docs: https://raft-design.botiverse.build/foundations/states/#hover-and-press
144
270
  - **Select, Combobox**: `collisionPadding` defaults to 8px. ([#642](https://github.com/botiverse/raft-design/pull/642))
145
271
  - Why: popups touched the screen edge on phones.
146
272
  - Affects: popup inset from the screen edge, 8px.
@@ -32,6 +32,12 @@ export const TOKEN_RENAMES = {
32
32
  "theme-shadow-sticker": "elevation-sticker",
33
33
  "theme-focus-ring": "focus-ring",
34
34
  "soft-signal": "palette-yellow",
35
+ "fill-pressed": "fill-medium",
36
+ "fill-frame-pressed": "fill-frame-medium",
37
+ "fill-row-hover": "fill-subtle",
38
+ "fill-row-open": "fill-subtle",
39
+ "fill-message-hover": "fill-subtle",
40
+ "sticker-pressed": "sticker-medium",
35
41
  };
36
42
 
37
43
  /** Families renamed with every step: `primary-soft` -> `brand-soft`. */
@@ -61,6 +67,10 @@ export const UTILITY_RENAMES = {
61
67
  "shadow-raft-xl": "shadow-float",
62
68
  "shadow-raft-sticker": "shadow-sticker",
63
69
  "shadow-raft-focus": "shadow-focus",
70
+ "press-overlay": "overlay-medium",
71
+ "press-overlay-sticker": "overlay-sticker-medium",
72
+ "row-hover-overlay": "overlay-subtle",
73
+ "row-open-overlay": "overlay-subtle",
64
74
  };
65
75
 
66
76
  const COLOR_UTILITY =
@@ -83,6 +93,8 @@ function rules() {
83
93
  for (const [from, to] of tokens) {
84
94
  list.push([new RegExp(`(?<![\\w-])(${COLOR_UTILITY})-${escape(from)}${END}`, "g"), `$1-${to}`]);
85
95
  list.push([new RegExp(`${VAR}${escape(from)}${REF_END}`, "g"), `--${to}`]);
96
+ // The Tailwind theme variable of the same token: `var(--color-fill-pressed)`.
97
+ list.push([new RegExp(`${VAR}color-${escape(from)}${REF_END}`, "g"), `--color-${to}`]);
86
98
  }
87
99
  for (const [from, to] of Object.entries(FAMILY_RENAMES)) {
88
100
  list.push([
@@ -90,6 +102,10 @@ function rules() {
90
102
  `$1-${to}$2`,
91
103
  ]);
92
104
  list.push([new RegExp(`${VAR}${from}((?:-[a-z0-9]+)*)${REF_END}`, "g"), `--${to}$1`]);
105
+ list.push([
106
+ new RegExp(`${VAR}color-${from}((?:-[a-z0-9]+)*)${REF_END}`, "g"),
107
+ `--color-${to}$1`,
108
+ ]);
93
109
  }
94
110
  for (const [from, to] of Object.entries(PALETTE_COLORS)) {
95
111
  list.push([
package/dist/cn.mjs CHANGED
@@ -7,6 +7,8 @@ const twMerge = extendTailwindMerge({ extend: {
7
7
  "control-cta",
8
8
  "control-icon",
9
9
  "control-hit",
10
+ "control-embedded",
11
+ "embedded-inset",
10
12
  "option",
11
13
  "option-inset",
12
14
  "icon-inset"
@@ -50,7 +52,19 @@ const twMerge = extendTailwindMerge({ extend: {
50
52
  "mono-code",
51
53
  "control"
52
54
  ] }],
53
- "font-weight": [{ font: ["field", "card-title"] }]
55
+ "font-weight": [{ font: ["field", "card-title"] }],
56
+ "bg-image": [
57
+ { overlay: [
58
+ "subtle",
59
+ "medium",
60
+ "sticker-medium",
61
+ "frame-medium"
62
+ ] },
63
+ "press-overlay",
64
+ "press-overlay-sticker",
65
+ "row-hover-overlay",
66
+ "row-open-overlay"
67
+ ]
54
68
  }
55
69
  } });
56
70
  function cn(...inputs) {
@@ -3,16 +3,24 @@
3
3
  min-width: 0;
4
4
  position: relative;
5
5
  }
6
+ /* The chips, the clear button and the toggle are embedded in the field: they
7
+ sit --control-embedded-inset inside its outer edge and as far apart, and
8
+ are as tall as the field less that inset on both sides, so the field keeps
9
+ its height with chips in it. They wear the chip radius
10
+ (design/foundations/shape.md#inside-a-frame). Across the field the inset is
11
+ counted over the 1px Chromium draws the 1.5px border at (WebKit draws
12
+ 1.5px: half a pixel more); down it the chips are centred in the field's
13
+ height, which holds on both. */
6
14
  .r-combobox-select__control {
7
15
  box-sizing: border-box;
8
16
  min-width: 0;
9
17
  min-height: var(--control-height);
10
18
  display: flex;
11
19
  align-items: center;
12
- gap: 0.5rem;
13
- padding: 0 6px 0 12px;
20
+ gap: var(--control-embedded-inset);
21
+ padding: 0 calc(var(--control-embedded-inset) - 1px) 0 12px;
14
22
  border: 1.5px solid var(--line-field);
15
- border-radius: 8px;
23
+ border-radius: var(--radius-control);
16
24
  outline: 0;
17
25
  background: var(--layer-card);
18
26
  color: var(--foreground);
@@ -38,30 +46,30 @@
38
46
  color: var(--foreground-hint);
39
47
  }
40
48
  .r-combobox-select__chips {
41
- min-height: 28px;
49
+ min-height: var(--control-embedded-size);
42
50
  min-width: 0;
43
- margin-inline-start: -6px;
44
- padding: 6px 0;
51
+ margin-inline-start: calc(var(--control-embedded-inset) - 13px);
52
+ padding: calc(var(--control-embedded-inset) - 1.5px) 0;
45
53
  display: flex;
46
54
  flex: 1;
47
55
  flex-wrap: wrap;
48
56
  align-items: center;
49
- gap: 6px;
57
+ gap: var(--control-embedded-inset);
50
58
  border: 0;
51
59
  }
52
60
  .r-combobox-select__chips .r-combobox-select__input {
53
- min-height: 28px;
61
+ height: var(--control-embedded-size);
54
62
  padding: 0 4px;
55
63
  }
56
64
  .r-combobox-select__chip {
57
65
  max-width: 100%;
58
- min-height: 28px;
66
+ height: var(--control-embedded-size);
59
67
  padding: 0 2px 0 8px;
60
68
  display: inline-flex;
61
69
  align-items: center;
62
70
  gap: 6px;
63
71
  overflow: hidden;
64
- border-radius: 6px;
72
+ border-radius: var(--radius-chip);
65
73
  background: var(--fill-muted);
66
74
  color: var(--foreground);
67
75
  font-size: var(--text-footnote);
@@ -89,7 +97,7 @@
89
97
  .r-combobox-select__actions {
90
98
  display: inline-flex;
91
99
  align-items: center;
92
- gap: 0.125rem;
100
+ gap: var(--control-embedded-inset);
93
101
  flex: 0 0 auto;
94
102
  }
95
103
  .r-combobox-select__action,
@@ -107,15 +115,15 @@
107
115
  background-color 150ms ease;
108
116
  }
109
117
  .r-combobox-select__remove {
110
- width: 22px;
111
- height: 22px;
112
- border-radius: 4px;
118
+ width: calc(var(--control-embedded-size) - 4px);
119
+ height: calc(var(--control-embedded-size) - 4px);
120
+ border-radius: var(--radius-sticker);
113
121
  color: var(--foreground-hint);
114
122
  }
115
123
  .r-combobox-select__action {
116
- width: 32px;
117
- height: 32px;
118
- border-radius: 8px;
124
+ width: var(--control-embedded-size);
125
+ height: var(--control-embedded-size);
126
+ border-radius: var(--radius-chip);
119
127
  color: var(--foreground-hint);
120
128
  }
121
129
  .r-combobox-select__action > svg,
@@ -130,15 +138,28 @@
130
138
  width: 0.875rem;
131
139
  height: 0.875rem;
132
140
  }
133
- .r-combobox-select__action:hover,
134
- .r-combobox-select__remove:hover {
135
- background: var(--fill-pressed);
141
+ /* Hover only where there is a pointer that hovers: on a touch screen a
142
+ :hover would stay on the last button tapped. */
143
+ @media (hover: hover) {
144
+ .r-combobox-select__action:hover,
145
+ .r-combobox-select__remove:hover {
146
+ background: var(--fill-medium);
147
+ color: var(--foreground);
148
+ }
149
+ }
150
+ .r-combobox-select__action:active,
151
+ .r-combobox-select__remove:active {
152
+ background: var(--fill-medium);
136
153
  color: var(--foreground);
137
154
  }
138
- .r-combobox-select__action:focus-visible,
139
- .r-combobox-select__remove:focus-visible {
155
+ .r-combobox-select__action:focus-visible {
140
156
  box-shadow: var(--focus-ring);
141
157
  }
158
+ /* The chip clips anything outside it, so the remove button's ring is drawn
159
+ inside, after keyboard navigation only (styles.css, the keyboard variant). */
160
+ .r-combobox-select__remove:focus-visible:not(:where(:root[data-input-modality="pointer"] *)) {
161
+ box-shadow: inset 0 0 0 2px var(--foreground);
162
+ }
142
163
  .r-combobox-select__positioner {
143
164
  z-index: 50;
144
165
  isolation: isolate;
@@ -152,7 +173,7 @@
152
173
  max-height: min(var(--available-height), max(24rem, var(--combobox-select-list-max-height, 0px)));
153
174
  overflow: hidden;
154
175
  border: 0;
155
- border-radius: 10px;
176
+ border-radius: var(--radius-container);
156
177
  background: var(--layer-popover);
157
178
  color: var(--foreground);
158
179
  box-shadow:
@@ -165,8 +186,8 @@
165
186
  max-height: var(--combobox-select-list-max-height, 16rem);
166
187
  overflow-y: auto;
167
188
  overscroll-behavior: contain;
168
- scroll-padding-block: 6px;
169
- padding: 6px;
189
+ scroll-padding-block: 4px;
190
+ padding: 4px;
170
191
  }
171
192
  .r-combobox-select__option {
172
193
  box-sizing: border-box;
@@ -180,7 +201,7 @@
180
201
  justify-content: space-between;
181
202
  gap: 10px;
182
203
  overflow: hidden;
183
- border-radius: 8px;
204
+ border-radius: var(--radius-chip);
184
205
  cursor: default;
185
206
  user-select: none;
186
207
  color: var(--foreground);
@@ -192,7 +213,12 @@
192
213
  background-color 120ms ease,
193
214
  color 120ms ease;
194
215
  }
195
- .r-combobox-select__option:hover,
216
+ @media (hover: hover) {
217
+ .r-combobox-select__option:hover {
218
+ background: var(--brand-soft);
219
+ color: var(--foreground);
220
+ }
221
+ }
196
222
  .r-combobox-select__option[data-highlighted] {
197
223
  background: var(--brand-soft);
198
224
  color: var(--foreground);
@@ -250,23 +276,15 @@
250
276
  animation: r-combobox-select-spin 700ms linear infinite;
251
277
  }
252
278
  .r-combobox-select--sm.r-combobox-select__control {
253
- min-height: 1.75rem;
279
+ --control-height: 1.75rem;
280
+ --control-embedded-inset: 3px;
281
+ --control-embedded-size: 22px;
254
282
  font-size: var(--text-control);
255
283
  line-height: var(--text-control--line-height);
256
284
  }
257
- .r-combobox-select--sm .r-combobox-select__chips {
258
- min-height: 1.5rem;
259
- padding: 1px 0;
260
- }
261
- .r-combobox-select--sm .r-combobox-select__chip {
262
- min-height: 1.375rem;
263
- }
264
- .r-combobox-select--sm .r-combobox-select__action {
265
- width: 1.5rem;
266
- height: 1.5rem;
267
- }
268
285
  .r-combobox-select--lg.r-combobox-select__control {
269
- min-height: 52px;
286
+ --control-height: 52px;
287
+ --control-embedded-size: 40px;
270
288
  }
271
289
  .r-combobox-select__control[data-status="error"] {
272
290
  border-color: var(--danger-strong);
@@ -20,7 +20,7 @@
20
20
  --r-float-button-border-color: var(--line-strong);
21
21
  --r-float-button-bg: var(--layer-card);
22
22
  --r-float-button-bg-hover: var(--layer-card);
23
- --r-float-button-press: var(--fill-pressed);
23
+ --r-float-button-press: var(--fill-medium);
24
24
  --r-float-button-text-color: var(--foreground);
25
25
  --r-float-button-text-hover-color: var(--foreground);
26
26
  --r-float-button-text-disabled-color: var(--foreground-disabled);
@@ -87,7 +87,7 @@
87
87
  --r-float-button-border-color: var(--sticker-ink);
88
88
  --r-float-button-bg: var(--attention-400);
89
89
  --r-float-button-bg-hover: var(--attention-400);
90
- --r-float-button-press: var(--sticker-pressed);
90
+ --r-float-button-press: var(--sticker-medium);
91
91
  --r-float-button-text-color: var(--sticker-ink);
92
92
  --r-float-button-text-hover-color: var(--sticker-ink);
93
93
  }
@@ -95,7 +95,7 @@
95
95
  --r-float-button-border-color: var(--sticker-ink);
96
96
  --r-float-button-bg: var(--warning);
97
97
  --r-float-button-bg-hover: var(--warning);
98
- --r-float-button-press: var(--sticker-pressed);
98
+ --r-float-button-press: var(--sticker-medium);
99
99
  --r-float-button-text-color: var(--sticker-ink);
100
100
  --r-float-button-text-hover-color: var(--sticker-ink);
101
101
  }
@@ -103,15 +103,15 @@
103
103
  --r-float-button-border-color: var(--sticker-ink);
104
104
  --r-float-button-bg: var(--danger);
105
105
  --r-float-button-bg-hover: var(--danger);
106
- --r-float-button-press: var(--sticker-pressed);
106
+ --r-float-button-press: var(--sticker-medium);
107
107
  --r-float-button-text-color: var(--sticker-ink);
108
108
  --r-float-button-text-hover-color: var(--sticker-ink);
109
109
  }
110
110
  &.r-float-button--ghost {
111
111
  --r-float-button-border-color: transparent;
112
112
  --r-float-button-bg: transparent;
113
- --r-float-button-bg-hover: var(--fill-pressed);
114
- --r-float-button-press: transparent;
113
+ --r-float-button-bg-hover: transparent;
114
+ --r-float-button-press: var(--fill-medium);
115
115
  }
116
116
  }
117
117
 
@@ -159,11 +159,15 @@
159
159
  padding-right: var(--r-float-button-padding-x);
160
160
  }
161
161
 
162
- &:hover:not(:disabled) {
163
- background-color: var(--r-float-button-bg-hover);
164
- color: var(--r-float-button-text-hover-color);
165
- box-shadow: var(--r-float-button-shadow-hover);
166
- transform: var(--r-float-button-transform-hover);
162
+ /* Hover only where there is a pointer that hovers: on a touch screen a
163
+ :hover would stay on the button after the tap. */
164
+ @media (hover: hover) {
165
+ &:hover:not(:disabled) {
166
+ background-color: var(--r-float-button-bg-hover);
167
+ color: var(--r-float-button-text-hover-color);
168
+ box-shadow: var(--r-float-button-shadow-hover);
169
+ transform: var(--r-float-button-transform-hover);
170
+ }
167
171
  }
168
172
 
169
173
  &:active:not(:disabled) {
@@ -179,7 +183,12 @@
179
183
 
180
184
  /* Press feedback lays one overlay over the resting fill instead of
181
185
  swapping it. */
182
- &:is(:hover, :active):not(:disabled) {
186
+ @media (hover: hover) {
187
+ &:hover:not(:disabled) {
188
+ background-image: linear-gradient(var(--r-float-button-press), var(--r-float-button-press));
189
+ }
190
+ }
191
+ &:active:not(:disabled) {
183
192
  background-image: linear-gradient(var(--r-float-button-press), var(--r-float-button-press));
184
193
  }
185
194