@botiverse/raft-ui 0.83.0 → 0.85.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 +125 -0
- package/dist/cn.d.mts +2 -0
- package/dist/cn.mjs +21 -1
- package/dist/components/alert-dialog/alert-dialog.mjs +2 -2
- package/dist/components/app-link-card/app-link-card.mjs +19 -37
- package/dist/components/app-shell/app-shell.mjs +2 -2
- package/dist/components/avatar/avatar.mjs +1 -1
- package/dist/components/banner/banner.mjs +15 -2
- package/dist/components/button/button-split.mjs +2 -2
- package/dist/components/button/button.mjs +2 -2
- package/dist/components/choice-list/choice-list.recipe.mjs +1 -1
- package/dist/components/code-block/code-block.recipe.mjs +2 -2
- package/dist/components/combobox/combobox.mjs +1 -1
- package/dist/components/combobox-select/combobox-select.css +1 -1
- package/dist/components/comment/comment.recipe.mjs +2 -2
- package/dist/components/composer/composer-attachment.recipe.mjs +5 -5
- package/dist/components/composer/composer-drop-overlay.mjs +2 -2
- package/dist/components/composer/composer-drop-overlay.recipe.mjs +1 -1
- package/dist/components/composer/sortable-composer-attachments.mjs +1 -1
- package/dist/components/context-menu/context-menu.mjs +1 -1
- package/dist/components/conversation-panel/conversation-panel-empty-state.mjs +1 -1
- package/dist/components/dialog/dialog.mjs +4 -4
- package/dist/components/drag/drag-lift.d.mts +3 -2
- package/dist/components/drag/drag-lift.mjs +3 -2
- package/dist/components/drag/drag.recipe.mjs +1 -1
- package/dist/components/drawer/drawer.recipe.mjs +4 -4
- package/dist/components/dropdown-menu/dropdown-menu.mjs +1 -1
- package/dist/components/file-preview/file-preview.recipe.mjs +1 -1
- package/dist/components/fit-toolbar/fit-toolbar.d.mts +63 -8
- package/dist/components/fit-toolbar/fit-toolbar.mjs +310 -43
- package/dist/components/fit-toolbar/fit-toolbar.recipe.mjs +21 -7
- package/dist/components/fit-toolbar/index.d.mts +2 -2
- package/dist/components/inbox-item/inbox-item.recipe.mjs +7 -7
- package/dist/components/lightbox/index.d.mts +2 -2
- package/dist/components/lightbox/lightbox.d.mts +70 -4
- package/dist/components/lightbox/lightbox.mjs +285 -91
- package/dist/components/list-row/list-row.d.mts +14 -1
- package/dist/components/list-row/list-row.mjs +4 -2
- package/dist/components/list-row/list-row.recipe.d.mts +12 -0
- package/dist/components/list-row/list-row.recipe.mjs +19 -7
- package/dist/components/message-action-card/message-action-card-content.recipe.mjs +3 -3
- package/dist/components/message-action-card/message-action-card.mjs +7 -3
- package/dist/components/message-action-card/message-action-card.recipe.mjs +2 -6
- package/dist/components/message-attachment/message-image-gallery.recipe.mjs +1 -1
- package/dist/components/message-embed/index.d.mts +2 -1
- package/dist/components/message-embed/message-embed.d.mts +8 -1
- package/dist/components/message-embed/message-embed.mjs +2 -2
- package/dist/components/message-embed/message-embed.recipe.d.mts +160 -0
- package/dist/components/message-embed/message-embed.recipe.mjs +49 -10
- package/dist/components/message-forwarded-bundle/message-forwarded-bundle.mjs +1 -0
- package/dist/components/message-forwarded-bundle/message-forwarded-bundle.recipe.mjs +1 -1
- package/dist/components/message-item/message-item.recipe.mjs +2 -2
- package/dist/components/message-list/message-list.recipe.mjs +2 -2
- package/dist/components/message-quoted-preview/message-quoted-preview.mjs +2 -0
- package/dist/components/message-replies/message-replies.mjs +1 -1
- package/dist/components/message-replies/message-replies.recipe.mjs +1 -1
- package/dist/components/mobile-nav/mobile-nav.mjs +2 -2
- package/dist/components/notification-center/notification-center.mjs +1 -1
- package/dist/components/panel/panel-header.recipe.mjs +1 -1
- package/dist/components/panel/panel-split-action.recipe.mjs +2 -2
- package/dist/components/popover/popover.d.mts +11 -2
- package/dist/components/popover/popover.mjs +51 -38
- package/dist/components/preview-card/preview-card.d.mts +5 -1
- package/dist/components/preview-card/preview-card.mjs +25 -28
- package/dist/components/pull-to-refresh/pull-to-refresh-indicator.mjs +1 -1
- package/dist/components/resizable/resizable.recipe.mjs +1 -1
- package/dist/components/save-status/save-status.d.mts +16 -9
- package/dist/components/save-status/save-status.mjs +19 -30
- package/dist/components/scroll-area/scroll-area.css +1 -0
- package/dist/components/segmented-control/segmented-control.mjs +45 -8
- package/dist/components/select/select.mjs +2 -2
- package/dist/components/selection-toolbar/selection-toolbar.recipe.mjs +1 -1
- package/dist/components/sidebar/sidebar.recipe.mjs +1 -1
- package/dist/components/tabs/tabs.mjs +4 -4
- package/dist/components/task-board/task-board.d.mts +5 -0
- package/dist/components/task-board/task-board.mjs +8 -1
- package/dist/components/task-dnd/task-dnd.d.mts +10 -1
- package/dist/components/task-dnd/task-dnd.mjs +2 -1
- package/dist/components/task-dnd/task-dnd.recipe.mjs +2 -2
- package/dist/components/thread-panel/thread-panel.recipe.mjs +1 -1
- package/dist/components/toast/toast.mjs +5 -5
- package/dist/components/tooltip/tooltip.mjs +2 -2
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/lib/laid-out-children.mjs +32 -0
- package/dist/lib/list-cell.mjs +1 -1
- package/dist/lib/stacked-actions.mjs +7 -4
- package/dist/lint/no-raw-z-index.mjs +142 -0
- package/dist/lint/prefer-scroll-area.mjs +1 -1
- package/dist/lint/recommended.json +2 -1
- package/dist/lint.d.mts +8 -2
- package/dist/lint.mjs +16 -3
- package/dist/styles.css +28 -0
- package/dist/wip.d.mts +4 -2
- package/dist/wip.mjs +3 -2
- package/package.json +1 -1
- package/skills/raft-ui-guide/SKILL.md +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,128 @@
|
|
|
1
|
+
## [0.85.0](https://github.com/botiverse/raft-design/compare/v0.84.0...v0.85.0) (2026-11-10)
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **Named layers, `z-below` to `z-drag`, and the lint rule `raft-ui/no-raw-z-index`**: every z-index has a name and a place in one order ([Layer order](https://raft-design.botiverse.build/foundations/elevation/#layer-order)). The names are ordinary Tailwind `z-*` utilities from `--z-index-*` theme variables in `styles.css` (`@theme static`, so CSS can always read `var(--z-index-sticky)`): in the page `z-below` -1, `z-base` 0, `z-raised` 1, `z-control` 10, `z-sticky` 20, `z-float` 30, `z-chrome` 35, `z-cover` 40, `z-app` 45, `z-scrollbar` 49; overlays `z-popup` and `z-dialog` 50, `z-dialog-layer` 60, `z-lightbox` 70, `z-toast` 80, `z-drag` 999. They take every variant (`max-md:z-chrome`, `before:z-below`), and `cn` and the recipes merge them with a number an app passes (`z-popup` and `z-40` keep `z-40`). raft-ui's own components use them. The new rule reports a z class with a number or an arbitrary value (`z-10`, `-z-1`, `z-[60]`), a numeric `zIndex` in a style object, a `zIndex={n}` prop and `el.style.zIndex = n`; a number kept on purpose takes a `// raft-ui: z-index <reason>` comment on the line above. `z-index-named.test.ts` holds raft-ui's own code to the same rule, CSS included, with its kept exceptions listed.
|
|
6
|
+
- Why: slock alone writes 186 z-index values, and the same role has up to five numbers (sticky headers at 2, 10, 15, 20 and 30) while one number has up to seven roles; a share lightbox at 100 hides its own toast, and a badge menu at 1000 sits above the drag lift (#proj-raft-ui:#218, step 1, approved by Artea).
|
|
7
|
+
- Affects: nothing on the upgrade for an app that extends `recommended.json`: the rule is `"off"` in it for now, so an app that lints with `--max-warnings=0` (slock) stays green. It turns on there as `"error"` once slock has moved to the names (#218 step 2). An app can opt in early with `"raft-ui/no-raw-z-index": "warn"` or `"error"` in its own `rules`. Map each value with the table in Elevation: a local lift is `z-raised` inside an `isolate` root (there is no third local level: order a third part by DOM order); a popup, menu, dialog, lightbox, toast or drag copy takes raft-ui's component and no z-index of its own. Nothing may sit above the toasts (80) or the drag lift (999). slock's InlineBadgeEditor (1000) and SelectShareLightbox (100) are not exceptions: they move onto `Popover` and `z-lightbox` in step 2.
|
|
8
|
+
- Docs: https://raft-design.botiverse.build/foundations/elevation/#layer-order, https://raft-design.botiverse.build/about/use-raft-ui/#lint-rules
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **raft-ui's in-page layers take the scale's values**: where a component's z-index moved to a name with another number, it renders the same inside the component, but its number against an app's own layers changes. Overlays keep theirs (50, 60, 70, 80, 999).
|
|
13
|
+
- Raised parts 10 → 1 (`z-raised`): Button's and Toast's label and loading indicator, Avatar's badge, InboxItem's lead, badge, selection, text, trail and corner (its trigger is `z-base`, 0), ChoiceList's control column, Comment's cover and active comment, CodeBlock's actions, ComposerAttachment's veils, FilePreview's corner badge, MessageImageGallery's "+N" veil, MessageItem's trailing meta, MobileNav's icons, Select's scroll arrows, Tabs' tab content and drop placeholder, Tooltip's kbd, Lightbox's arrows, info dock and counter, ConversationPanelEmptyState's illustration. 20 → 1: CodeBlock's corners, ComposerAttachment's progress bar (over the veil by DOM order), ComposerDropOverlay's second icon (and its first, 10 → 1), Lightbox's close, a dragged TaskDraggable. MessageList's end fade 5 → 1.
|
|
14
|
+
- Up: MessageList's jump controls and SelectionToolbar 10 → 30 (`z-float`), the pull-to-refresh indicator 10 → 20 (`z-sticky`), PanelHeader's sticky phone header 30 → 35 (`z-chrome`), ThreadPanel's header 20 → 35 (inside the panel's `isolate`), AppShellOverlaySlot 20 → 40 (`z-cover`).
|
|
15
|
+
- Down: MessageItem's hover toolbar 20 → 10 and ComposerAttachment's remove button 20 → 10 (`z-control`), the composer attachment row's arrows 30 → 10 (outside the row's ScrollArea, so still over it). The sidebar header mask -10 → -1 (`z-below`).
|
|
16
|
+
- Isolation: a tab is now `isolate` instead of `z-10`, and a dragged tab is lifted by `var(--z-index-raised)` instead of 20, all inside the tab list's `isolate`. TaskDraggable is `isolate`, so a card's own parts rank only inside the card, and a dragged card at `z-raised` is above every other card.
|
|
17
|
+
- Why: the scale has one number per role; the old numbers made the same role differ between components and forced slock to workarounds (a day heading at `z-15`, an `isolate` around the artifacts dashboard, `max-md:z-40` beside the phone header) (#proj-raft-ui:#218).
|
|
18
|
+
- Affects: an app layer that sat between the old and the new number of a raft-ui layer it overlaps. In slock, move these in the same upgrade: a cover at `z-30` beside a PanelHeader (MainLayout, ProfilePanel, ChannelSideArea, LegacyTaskPanel, TaskConversationDialog, `index.css`) to `z-cover`, or the header, now 35, is drawn over it; the typing line (`z-6`) to `z-float`, so it stays over the end fade and beside the jump pills; a hit target at `z-10` in a message (MessageItem) to `z-raised`, so the hover toolbar, now 10, stays above it; ThreadsInbox's day heading (`z-15`) and "new" pill (`z-20`) to `z-sticky` and `z-float`, which InboxItem's parts at 1 now allow.
|
|
19
|
+
- Docs: https://raft-design.botiverse.build/foundations/elevation/#in-page-layers
|
|
20
|
+
|
|
21
|
+
## [0.84.0](https://github.com/botiverse/raft-design/compare/v0.83.0...v0.84.0) (2026-11-10)
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- **LightboxBody, the scrolling body of a document-mode lightbox** (from `@botiverse/raft-ui/wip`): a ScrollArea under `LightboxToolbar` that fills the rest of the content (`min-h-0 flex-1`), takes the pointer back from `LightboxContent` (`pointer-events-auto`), and never passes a scroll on to the page under the scrim (`overscroll-contain`), on `layer-canvas`, the toolbar's surface. `className` sets another surface (`bg-layer-sidebar`); every other ScrollArea prop, `render` included, is the area's, except `overscroll`. It carries `data-slot="lightbox-body"`. It is a tab stop even when it has nothing to scroll (`tabIndex={0}`), with the inset focus ring (a 2px `foreground` outline inside its edge, after keyboard input only) instead of ScrollArea's ring outside it, which the window's edges hid. Its content is padded by `env(safe-area-inset-left|right|bottom)`, and `contentClassName` and `contentStyle` style a box of the caller's inside that (`data-slot="lightbox-body-content"`), so a phone held sideways doesn't put a wide file under the notch or its end under the home bar, and the scrollbar stays at the window's edge.
|
|
26
|
+
- Why: slock's FileViewerShell and SelectShareLightbox each write a lightbox's scrolling body of their own (Kirby's review of #1130, #proj-raft-ui:#226). A ScrollArea stops a scroll only while it has something to scroll, so a body too short to scroll passed the wheel on to the page under the scrim, which document mode doesn't lock.
|
|
27
|
+
- Affects: nothing until a lightbox uses it. The document-mode example on the Lightbox page now does, with the same DOM and classes as before apart from `data-slot`, `overscroll-contain`, `tabIndex` and the focus ring, and the safe-area box its `contentClassName` now sits in.
|
|
28
|
+
- Docs: https://raft-design.botiverse.build/components/lightbox/#document-mode
|
|
29
|
+
- **Lightbox document mode.** `<Lightbox modal="trap-focus">` traps focus without locking the page's scroll, for a file read top to bottom whose own body scrolls (a PDF, Markdown, a diagram); the content stays neither `inert` nor hidden, so find in page reaches it. In document mode `LightboxContent` takes Base UI's own focus defaults: focus moves into the lightbox when it opens and back to `LightboxTrigger` when it closes. A lightbox opened from state passes `finalFocus={openerRef}` to send focus back to what opened it.
|
|
30
|
+
- Why: slock's FileViewerShell (images and attachment previews: PDF, documents, Mermaid) and SelectShareLightbox move onto raft-ui's Lightbox, and they kept their own body-scroll switch and focus return (#triage:#278, #proj-raft-ui:#226).
|
|
31
|
+
- Affects: nothing changes for a lightbox until it passes `modal="trap-focus"`; `modal` keeps its default, `true`, and in it `initialFocus` and `finalFocus` stay `false`.
|
|
32
|
+
- Docs: https://raft-design.botiverse.build/components/lightbox/#document-mode
|
|
33
|
+
- **`attached` on ListRow: a row that belongs to the setting above it**: in a `ListRowGroup`, an `attached` row draws no hairline above it, and a `plain` one has no top padding, so it follows the row it belongs to with only that row's 12px bottom inset between them. An interactive one (a chevron, a menu) keeps the control height as its least, so it stays a 44px target on a phone and a touch screen (34px with a mouse on a desktop, which it already is). The row carries `data-attached`. Tie it to its setting for assistive technology with the setting's title id: a display-only attached row takes `role="group"` and `aria-labelledby`, an interactive one `aria-describedby`; ListRow can't know which title it belongs to. The row above keeps its height, and its switch stays centered on its own title and description; the row after draws its hairline as usual.
|
|
34
|
+
- Why: a hairline says "another setting", so quiet hours' start and end times, or a server's channel overrides under its level, read as settings of their own (cindyz, 2026-11-10, #proj-raft-ui:4ae1a74e).
|
|
35
|
+
- Affects: nothing until a row passes `attached`: without it, `ListRow` renders the same DOM and classes as before. slock's quiet hours times, its channel overrides and the message font size preview should take it.
|
|
36
|
+
- Docs: https://raft-design.botiverse.build/components/list-row/#a-row-that-belongs-to-the-setting-above, https://raft-design.botiverse.build/patterns/forms/#a-settings-row
|
|
37
|
+
- **FitToolbar's `plus` step: the primary action as + alone** (from `@botiverse/raft-ui/wip`). `FitToolbarButton` takes `plusOrder`: from that step on it is a + square, its icon and label gone, the label kept as its accessible name and shown as its tooltip; it carries `data-fit-plus`, and `useFitItem({ plus })` gives any item the step. Every item's steps share one order for the toolbar, by their numbers (lower first; at the same number the last item first, and one item's steps as `label`, `more`, `collapse`, `wrap`, `plus`). Give `plus` a higher number than any `wrap`, so the primary becomes + only once the groups have wrapped. `FitToolbarButton`'s `icon` is optional now, for a primary that has none until it is +.
|
|
38
|
+
- Why: Tasks at 375px and 420px: once the filters have wrapped to their own scrolling line, the first line (Mine and All with their counts, sort, list or board, Create task) fits only with Create task as + (sindresorhus, #proj-raft-ui:#225). The step was left out of 0.83.0 for want of a test.
|
|
39
|
+
- Affects: nothing until a button passes `plusOrder`: without it, `FitToolbarButton` renders the same DOM and classes as before.
|
|
40
|
+
- Docs: https://raft-design.botiverse.build/components/fit-toolbar/#behavior
|
|
41
|
+
- **`messageCard` and `messageEmbed`, from `@botiverse/raft-ui`**: the recipes every card in a message is drawn from, so an app's own card takes the same parts. `messageCard()` gives `source` (with `sourceIcon`, `sourceName`, `sourceHost`, `sourceHostText`), `sourceFooter`, `body`, `eyebrow`, `title`, `heading`, `meta`, `actions`, `actionRow`, `inset`, `actionGroup` (the buttons as one item of a row that also holds a status line) and `popover`; `messageEmbed({ size })` the frame and its widths, which `MessageEmbed size` applies.
|
|
42
|
+
- Why: the rule for a card in a message is to take its inset, title, details and buttons from these recipes, never classes of its own; slock's Artifact, audio and app message cards can't follow it without them (Jasper's review of raft-design #1125).
|
|
43
|
+
- Affects: nothing until an app uses them.
|
|
44
|
+
- Docs: https://raft-design.botiverse.build/patterns/cards-in-messages/#one-place-for-the-styles
|
|
45
|
+
- **`size="item"` on PopoverContent**: a popover about an app's item is 400px wide (never wider than the room the positioner leaves it, so on a phone the screen less its padding), and its `PopoverBody`, `PopoverTitle` and `PopoverActions` take a card's parts: the 12px inset, the `text-subhead` bold title, one row of buttons. The content carries `data-size="item"`.
|
|
46
|
+
- Why: the question an app's link asks before it shows previews stands in for the item card it leads to, so it looks like that card (stdrc, 2026-11-10); a popover belongs to the family of what it is about.
|
|
47
|
+
- Affects: nothing until a popover passes `size="item"`; the default renders the same DOM and classes as before. slock's link-preview question should take it.
|
|
48
|
+
- Docs: https://raft-design.botiverse.build/components/popover/#about-an-item
|
|
49
|
+
- **FitToolbarPicker and FitToolbarButton's `collapsedIcon`, for the Tasks filters** (from `@botiverse/raft-ui/wip`). `FitToolbarPicker` is a picker's trigger as one toolbar item: pass the popup's trigger as `trigger` (`<DropdownMenuTrigger />`, `<PopoverTrigger />`) with an `icon`, a label and what is chosen (`value`); it is a PickerTriggerButton, filled `info-soft` once a value is chosen (an applied filter, Select › Filters); a count is set in Space Mono and any other value in the label's font. From `labelOrder` on its label gives way and the icon, the value and the chevron stay (👤 1 ⌄), named "Assignee 1". `FitToolbarButton`'s `collapsedIcon` shows only once its label has given way, in place of `icon` (Clear filters becomes ×). A `FitToolbarButton` with neither refuses its `label` step.
|
|
50
|
+
- Why: the Tasks filters are pickers that must keep what is chosen in view once they narrow, and Clear filters reads as text while it has room (sindresorhus, #proj-raft-ui:#225).
|
|
51
|
+
- Affects: nothing until a page uses them. A `FitToolbarButton` without `collapsedIcon` renders the same DOM and classes as before, plus `data-fit-needs-icon`.
|
|
52
|
+
- Docs: https://raft-design.botiverse.build/components/fit-toolbar/#anatomy
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
|
|
56
|
+
- **Toasts are drawn above a lightbox**: the toast viewport and an anchored toast's positioner move from `z-70` to `z-80`, above the lightbox's `z-70`. Elevation now writes the order down ([Layer order](https://raft-design.botiverse.build/foundations/elevation/#layer-order)).
|
|
57
|
+
- Why: a toast fired from inside a lightbox (Link copied, a failed download from FileViewerShell's toolbar) was drawn under it, and in document mode the lightbox's body is opaque, so it couldn't be seen (Kirby's review of #1130).
|
|
58
|
+
- Affects: every toast; it is drawn as before over the page, the popups and the dialogs. Nothing else sits between `z-70` and `z-80`.
|
|
59
|
+
- Docs: https://raft-design.botiverse.build/foundations/elevation/#layer-order, https://raft-design.botiverse.build/components/toast/#where-it-appears
|
|
60
|
+
|
|
61
|
+
- **SaveStatus shows only a failure, and takes no room otherwise**: an empty `SaveStatus` is hidden (`empty:hidden`) and has no height, so a setting with it is as tall as one without it. With a `SaveStatusError` in it, the line appears 4px under the control or the description, as before, and the setting grows by it. `SaveStatusSaved` is deprecated: it renders nothing, whatever its children (no check, no "Saved", no live region). `ListRow variant="plain"` no longer leaves a SaveStatus line out of what its ends center on: its ends center on the whole row, with an error shown too.
|
|
62
|
+
- Why: a setting that applies at once needs no "Saved": the control showing its new value is the confirmation, as in macOS's and iOS's settings. The reserved, usually empty line made those rows taller than their neighbors (cindyz, 2026-11-10, #proj-raft-ui:4ae1a74e). This replaces the reserved "Saved" line chosen on 2026-10-30 (Kirby's option A, which stdrc agreed to, #proj-raft-ui:401f3241).
|
|
63
|
+
- Affects: every `SaveStatus` while it holds no error, and every plain `ListRow` whose body ends in one (slock's `ImmediateSaveStatus` and `ImmediateSwitchRow`, its Language & region, onboarding, member permissions, server translation and system channels settings, the composer's send key and the channel's thread sort): the row or field is shorter by the line and its 4px, and a switch at a row's end moves up by half of that. A failure now grows the setting by its line, and a row's ends move down by half of it. Drop `SaveStatusSaved` and its "Saved" copy; render `SaveStatusError` when a change failed and nothing otherwise. A plain row without a SaveStatus is unchanged except that it no longer carries the `--list-row-status-space` classes. slock, on upgrading:
|
|
64
|
+
- `SETTINGS_STACK_CARD_DESKTOP` (settingsCardClasses.ts:12) still draws a stacked card's last SaveStatus inside the 16px below it (`[&_[data-slot=save-status]:last-child]:mt-0` and `mb-[calc(-1lh)]`, the 2026-11-04 rule this replaces). With an error shown, its 18px line overlaps the next setting by 2px, or runs into the card's padding, and the setting doesn't grow. On staging only PublicVisibilitySection takes the class, with no SaveStatus, so nothing shows it yet; drop the two classes.
|
|
65
|
+
- `settingsImmediateApply.behavior.test.tsx` fails: `assertIdleLine` looks for the "Saved" live region (`getByRole("status")`, `data-slot=save-status-saved`), and "Saved shows the check inside the same line" waits for "Saved". Assert the idle line is hidden and empty instead, and drop the Saved test. The "saved" state in ImmediateSetting.tsx becomes dead; `SETTING_SAVED_MS` is also used by AppNotificationsControls.tsx, so keep it while that does.
|
|
66
|
+
- Docs: https://raft-design.botiverse.build/components/save-status/, https://raft-design.botiverse.build/patterns/forms/#settings-that-apply-at-once
|
|
67
|
+
|
|
68
|
+
- **Cards in messages take their styles from one place**: MessageEmbed's recipe now holds the card frame and its two widths, `size="item"` (at most 400px, the message's width below 768px) and `size="content"` (at most 560px), and the `messageCard` recipe beside it holds the parts every card and item popover shares: the source row, the body and its 12px inset, the id line, the title (`text-subhead`, 2 lines), the state and details row and the buttons row (at most two buttons; a third action goes in a More menu the card supplies; a status line or note wraps onto its own line before the buttons do, and they go under each other, whole, only in a column narrower than both). AppLinkCard, PreviewCard's body parts, MessageActionCard, MessageForwardedBundle and MessageReplies are drawn from them, and a test (`card-styles.test.ts`) fails, naming the file, line and class, on a card or popover component that writes its own radius, width, inset or title size.
|
|
69
|
+
- Why: the same frame had widths from 176 to 576px, insets of 16, 12 and 10px and titles of 17 and 15px; a card's style changed alone and drifted from the others (stdrc, 2026-11-10: change one card and the others follow).
|
|
70
|
+
- Affects: `MessageEmbed` without `size` renders as before.
|
|
71
|
+
- AppLinkCard: its header, body and buttons sit 12px from the edges instead of 16px, and its title is `text-subhead` (15px on a phone, 14px on a desktop) instead of `text-headline` (17/16px), so the card is shorter.
|
|
72
|
+
- PreviewCard: `PreviewCardBody`, `PreviewCardActions` and `PreviewCardFooter` take the same 12px, and a `PreviewCardTitle` in a body the same title and its link styles. This is every card that uses them, the item card (`size="item"`) and a default-size card that puts its sentence in `PreviewCardBody`, such as slock's 300px connect-account card. A profile card (`PreviewCardHeader`, `PreviewCardDescription`) is unchanged.
|
|
73
|
+
- MessageActionCard: at most 400px instead of 440px, and the message's width on a phone; its properties stack at the narrower width sooner. `MessageActionCardActionsNote` is now a line of its own above the buttons, not beside them.
|
|
74
|
+
- MessageForwardedBundle: at most 560px instead of 544px. MessageReplies: at most 560px instead of 480px; it stays a filled, borderless block, 10px x 8px.
|
|
75
|
+
- Docs: https://raft-design.botiverse.build/patterns/cards-in-messages/
|
|
76
|
+
|
|
77
|
+
### Fixed
|
|
78
|
+
|
|
79
|
+
- **Lightbox document mode: the keys scroll the document, never the page under the scrim**: with `initialFocus` unset, a document lightbox with a `LightboxBody` focuses the body when opened by a click or a key, so Page Down, the arrow keys and Space scroll the file at once; before, focus landed on the first toolbar control (Close), and those keys scrolled the page under the scrim while the file stayed put. A tap still focuses the lightbox itself, and a lightbox without a body still focuses its first control. A key that would scroll the page from elsewhere in the lightbox (a toolbar button, the lightbox itself after a tap) scrolls `LightboxBody` instead, by the browser's own steps, and does nothing with a body of the caller's own; keys a control uses (a text field's, a button's Space, a menu's arrows) are left to it (Kirby's review of #1142).
|
|
80
|
+
- Affects: document-mode lightboxes with a `LightboxBody` (none in slock yet). An `initialFocus` of the caller's still wins.
|
|
81
|
+
- Docs: https://raft-design.botiverse.build/components/lightbox/#accessibility
|
|
82
|
+
- **Lightbox document mode focuses as Base UI does when opened by a tap**: with `initialFocus` unset, a document lightbox opened by a tap focuses itself, not its first toolbar control, so a phone shows no ring and opens no keyboard; a click or a key still focuses the first control (Kirby).
|
|
83
|
+
- **The drag hint is drawn with the lifted copy, at `999`**: it sat at `z-70` with the lightbox, where the DOM order decided which was on top; elevation.md's Layer order lists it (Kirby).
|
|
84
|
+
- **TaskBoard:** `TaskBoardColumnChevron` draws a `ChevronDown` when given no children, so a column heading always shows whether the column is open. slock passed it empty, so no column had the arrow and a collapsed Closed column read as "Closed 2" over nothing (Kirby, Noel).
|
|
85
|
+
|
|
86
|
+
- **What opens inside a lightbox is drawn above it, and a lightbox opened from a dialog has its scrim**: `LightboxPortal` is a stacking context of its own at `z-70` (`relative isolate z-70`), with the scrim and the content inside it in document order. Base UI already put a Tooltip, Popover, menu and its submenus, Select, Combobox, PreviewCard, Dialog, AlertDialog or Drawer opened inside `LightboxContent` in the lightbox's portal; now its own `z-50` counts inside the lightbox, above the content. The lightbox's scrim is always rendered (`forceRender`), and the lightbox joins the stack of modal surfaces: opened from a dialog it draws `layer-backdrop-nested` (`data-nested`), and a Dialog, AlertDialog or Drawer opened inside it draws the nested scrim over it.
|
|
87
|
+
- Why: the scrim and the content sat at `z-70` and `z-71` among the page's layers, so a popup opened inside, at `z-50` in the lightbox's portal, was drawn under the scrim, and a press on it landed on the scrim and closed the lightbox. And Base UI renders no backdrop for a dialog opened under another, so a lightbox opened from a dialog had no scrim: the dialog showed through undimmed, and a press beside the file went to the dialog (#proj-raft-ui:#226, Kirby's review of #1130).
|
|
88
|
+
- Affects: every lightbox. The DOM is the same; the portal element gains `relative isolate z-70`, the scrim loses `z-70` and the content `z-71`, and a scrim stacked on another surface carries `data-nested`. A popup given a `container` outside the lightbox, or an app's own portal to the body, is drawn under the lightbox, as before. `LightboxPortalProps` takes a string `className`, as `LightboxContentProps` does.
|
|
89
|
+
- Docs: https://raft-design.botiverse.build/components/lightbox/#popups-inside, https://raft-design.botiverse.build/components/lightbox/#behavior
|
|
90
|
+
- **A cover or an excerpt no longer cuts off a card's corners**: `MessageEmbedContent` that is the card's first or last part takes the card's inner corner, `radius-container` less the 1px border, on the two corners it shares with the card, and clips what it holds to them (`overflow: clip`). A cover at the top is round at the top and square at its foot. The card itself still clips nothing, so a title link's focus ring around the whole card (its `::after`) still shows.
|
|
91
|
+
- Why: an Artifact card's cover or excerpt ran square to the card's edges and covered the border's arc, so its top corners looked cut off, in light and dark (cindyz, #triage:6be3db1b). The docs' link card had the same fault.
|
|
92
|
+
- Affects: every card on MessageEmbed whose first or last part is `MessageEmbedContent`: slock's Artifact card, the docs' link card, and MessageForwardedBundle's items when the bundle has no footer (they now clip at the card's bottom corners). Content between two rows is unchanged, and so is MessageQuotedPreview: its frame is a square bar (`data-frame="bar"` on its root and on `MessageQuotedPreviewLoading`), so its content stays square and clips nothing.
|
|
93
|
+
- Docs: https://raft-design.botiverse.build/components/message-embed/#anatomy
|
|
94
|
+
- **FitToolbar measures only what is laid out in its rows**: it leaves out what is hidden, out of flow (`position: absolute` or `fixed`) or of no size, and looks through `display: contents` on its lines too. A line has broken only where an item sits wholly below or above the first on it, so items of different heights on one line (a primary Button beside 32px filters) no longer read as a broken line.
|
|
95
|
+
- Why: a popover or menu opened from a toolbar item puts Base UI focus guards beside its trigger, fixed at the window's corner, and the toolbar counted them: opening it made labels give way (Jasper). And a wrapping toolbar whose first line mixed heights wrapped or gave way where it fit.
|
|
96
|
+
- Affects: every FitToolbar and SelectionToolbar. Opening a popup from one no longer changes it; a wrapping toolbar with items of different heights on a line gives way less, only where it doesn't fit.
|
|
97
|
+
- Docs: https://raft-design.botiverse.build/components/fit-toolbar/#behavior
|
|
98
|
+
- **DialogFooter and AlertDialogFooter decide whether to stack from the buttons laid out in their row only**: they leave out what is hidden, out of flow (`position: absolute` or `fixed`) or of no size, and look through `display: contents`, as FitToolbar does (one shared measure). There is no new prop.
|
|
99
|
+
- Why: an open Menu or Popover puts Base UI focus guards beside its trigger, and a Select or Combobox always adds a hidden input, each fixed at the window's corner. The footer counted them as the row's start, so the row reached to the window's left edge: a menu opened from the footer stacked the buttons at any width, and a Select in the footer stacked them for good, nothing open (Jasper's audit, after the Tasks filters lost their labels whenever a filter was open: stdrc, 2026-11-10, #wg-task-v2:8a7ae237).
|
|
100
|
+
- Affects: a footer holding a Menu or Popover trigger, a Select or a Combobox now stacks only when its buttons don't fit. A button inside a `display: contents` wrapper now counts toward the row; before, the wrapper read as no size and its button was left out. Every other footer is unchanged.
|
|
101
|
+
- Docs: https://raft-design.botiverse.build/components/dialog/#the-footer, https://raft-design.botiverse.build/patterns/responsive/#switch-on-the-regions-width
|
|
102
|
+
- **BannerAction comes back beside the text with a menu open from it, or a Select in it**: once the actions had taken a line of their own, they measured their width with a Range over all their contents. They now measure from their first laid-out item's left to the last one's right, with the same shared measure. There is no new prop.
|
|
103
|
+
- Why: the Range took in an open menu's focus guards and a Select's hidden input, fixed at the window's corner, so the actions seemed as wide as the distance to the window's left edge: they stayed under the text while a menu was open, and for good with a Select in them (Jasper's audit, stdrc, 2026-11-10, #wg-task-v2:8a7ae237).
|
|
104
|
+
- Affects: a banner whose actions hold a Menu or Popover trigger, a Select or a Combobox. Actions of plain buttons measure the same width as before.
|
|
105
|
+
- Docs: https://raft-design.botiverse.build/components/banner/#anatomy, https://raft-design.botiverse.build/patterns/responsive/#switch-on-the-regions-width
|
|
106
|
+
- **A sorted board column shows that it is the place while a card is over it**: `TaskDropZone` takes `dragInto`. With it, the zone carries `data-drag-into` and takes `fill-subtle` while a card it allows is over it, its cards and all; never at rest, and never while it refuses the card (`dropState` `blocked`).
|
|
107
|
+
- Why: Drag and drop says a column whose order isn't the person's to set (sorted by last update, by due date) is itself the place and takes `fill-subtle` while the item is over it, with no placeholder between its items. TaskDropZone had no way to: a sorted column with cards showed nothing while a card was over it, only an empty one its placeholder (review of slock #13852; the channel Tasks panel shares the column).
|
|
108
|
+
- Affects: nothing until a `TaskDropZone` passes `dragInto`: without it, every zone renders the same DOM and classes as before, and a column whose cards are drop targets keeps its placeholder as the one signal.
|
|
109
|
+
- Docs: https://raft-design.botiverse.build/components/task-dnd/#states, https://raft-design.botiverse.build/components/task-board/#states, https://raft-design.botiverse.build/patterns/drag-and-drop/#where-it-will-land
|
|
110
|
+
- **FitToolbar's focus follows the reading order, and a SegmentedControl in it is one toolbar item**: the arrow keys go through its controls line by line as they show, and Tab first enters at the first control on screen. A SegmentedControl inside registers as one item of the toolbar, so the toolbar stays one Tab stop and its arrow keys reach the segments without choosing one (Space chooses; Up and Down choose nothing). When the focused item moves into More, focus goes to More instead of the page. Items of the same kind in one group (the same `label` or `plus` step at the same number) change form at the same time. A SegmentedControl of text alone, or a FitToolbarButton with no icon, refuses its `label` step. There is no new prop.
|
|
111
|
+
- Why: a wrapped group moves with CSS `order`, later on screen than in the DOM, and the browser's Tab order and Base UI's toolbar followed the DOM: focus jumped to the second line and back (WCAG 2.4.3). The SegmentedControl was a second Tab stop whose arrow keys chose as they moved, and Mine and All, with no icons, emptied to nothing at their `label` step; at 780px three filters became icons while Assignee kept its label (Jasper and sindresorhus, 2026-11-10).
|
|
112
|
+
- Affects: every FitToolbar and SelectionToolbar: arrow-key order is the screen's, which is the DOM's unless a group has wrapped. A SegmentedControl in a FitToolbar is no longer a Tab stop of its own. Outside a FitToolbar nothing changes.
|
|
113
|
+
- Docs: https://raft-design.botiverse.build/components/fit-toolbar/#accessibility, https://raft-design.botiverse.build/components/segmented-control/#accessibility
|
|
114
|
+
- **A FitToolbar's scrolling group scrolls only as its last state, and stays where the person scrolled it**: while it can still wrap, it counts as not fitting whenever its items overflow, so its labels give way and it wraps before it scrolls. Each measure keeps its scroll position, only the item that just became active comes into view (on mount the first active one), not every active one with the first winning, and a resize after the person has scrolled leaves it there. There is no new prop.
|
|
115
|
+
- Why: at 780px the filters scrolled on the first line, the last ones hidden past the fade, where giving their labels fit them all; and a count changing or a measure pulled the group back to the active filter under the person's finger (risk 3 of sindresorhus' review, with Jasper, 2026-11-10).
|
|
116
|
+
- Affects: a FitToolbar with `wrap` and an `overflow="scroll"` group that can wrap now gives its labels and wraps sooner. Every scrolling group keeps its scroll position across a measure.
|
|
117
|
+
- Docs: https://raft-design.botiverse.build/components/fit-toolbar/#behavior
|
|
118
|
+
|
|
119
|
+
### Design System
|
|
120
|
+
|
|
121
|
+
- **A setting that applies at once shows no "Saved" and reserves no line** (cindyz, 2026-11-10, #proj-raft-ui:4ae1a74e): the control showing its new value is the confirmation; only a failure appears under it, saying what failed, while the control goes back to its old value. Where a section's settings are stored ("Saved on this device") is said once for the section, not on every row. This replaces the way chosen on 2026-10-30 (Kirby's option A, which stdrc agreed to, #proj-raft-ui:401f3241: "Saved" for a moment, on a line kept when empty) and the settings row's centering on its title and description above that line (2026-11-09).
|
|
122
|
+
- Docs: https://raft-design.botiverse.build/patterns/forms/#settings-that-apply-at-once, https://raft-design.botiverse.build/foundations/alignment/#a-control-and-its-label
|
|
123
|
+
- **A section of settings may hold more than one group, split by topic** (cindyz, 2026-11-10, #proj-raft-ui:4ae1a74e): in Appearance, the message font size and its preview are one `ListRowGroup` and the display switches another, 12px apart (`stack-y-3`) under the section's one header, with no hairline joining them. `ListRowGroup` is unchanged: it sets no space around itself, and the column of groups sets it.
|
|
124
|
+
- Docs: https://raft-design.botiverse.build/components/list-row/#more-than-one-group-in-a-section, https://raft-design.botiverse.build/patterns/forms/#a-settings-row
|
|
125
|
+
|
|
1
126
|
## [0.83.0](https://github.com/botiverse/raft-design/compare/v0.82.0...v0.83.0) (2026-11-10)
|
|
2
127
|
|
|
3
128
|
### Added
|
package/dist/cn.d.mts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { ClassValue } from "cnfast";
|
|
2
2
|
//#region src/lib/cn.d.ts
|
|
3
|
+
/** The `--z-index-*` names of styles.css, in order (design/foundations/elevation.md#layer-order). */
|
|
4
|
+
export declare const Z_INDEX_NAMES: string[];
|
|
3
5
|
export declare function cn(...inputs: ClassValue[]): string;
|
|
4
6
|
//#endregion
|
|
5
7
|
export type { ClassValue };
|
package/dist/cn.mjs
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import { clsx } from "cnfast";
|
|
2
2
|
import { extendTailwindMerge } from "tailwind-merge";
|
|
3
3
|
//#region src/lib/cn.ts
|
|
4
|
+
/** The `--z-index-*` names of styles.css, in order (design/foundations/elevation.md#layer-order). */
|
|
5
|
+
const Z_INDEX_NAMES = [
|
|
6
|
+
"below",
|
|
7
|
+
"base",
|
|
8
|
+
"raised",
|
|
9
|
+
"control",
|
|
10
|
+
"sticky",
|
|
11
|
+
"float",
|
|
12
|
+
"chrome",
|
|
13
|
+
"cover",
|
|
14
|
+
"app",
|
|
15
|
+
"scrollbar",
|
|
16
|
+
"popup",
|
|
17
|
+
"dialog",
|
|
18
|
+
"dialog-layer",
|
|
19
|
+
"lightbox",
|
|
20
|
+
"toast",
|
|
21
|
+
"drag"
|
|
22
|
+
];
|
|
4
23
|
const twMerge = extendTailwindMerge({ extend: {
|
|
5
24
|
theme: { spacing: [
|
|
6
25
|
"control",
|
|
@@ -59,6 +78,7 @@ const twMerge = extendTailwindMerge({ extend: {
|
|
|
59
78
|
"x"
|
|
60
79
|
] }],
|
|
61
80
|
opacity: [{ opacity: ["dimmed"] }],
|
|
81
|
+
z: [{ z: Z_INDEX_NAMES }],
|
|
62
82
|
"bg-image": [{ overlay: [
|
|
63
83
|
"subtle",
|
|
64
84
|
"medium",
|
|
@@ -71,4 +91,4 @@ function cn(...inputs) {
|
|
|
71
91
|
return twMerge(clsx(inputs));
|
|
72
92
|
}
|
|
73
93
|
//#endregion
|
|
74
|
-
export { cn };
|
|
94
|
+
export { Z_INDEX_NAMES, cn };
|
|
@@ -13,9 +13,9 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
13
13
|
//#region src/components/alert-dialog/alert-dialog.tsx
|
|
14
14
|
const alertDialog = tv({
|
|
15
15
|
slots: {
|
|
16
|
-
overlay: ["fixed inset-0 z-
|
|
16
|
+
overlay: ["fixed inset-0 z-dialog bg-layer-backdrop"],
|
|
17
17
|
content: [
|
|
18
|
-
"fixed top-1/2 left-1/2 z-
|
|
18
|
+
"fixed top-1/2 left-1/2 z-dialog grid w-[calc(100%-2rem)] -translate-1/2 outline-none",
|
|
19
19
|
decisionWidth,
|
|
20
20
|
"overflow-clip rounded-dialog bg-layer-popover text-foreground",
|
|
21
21
|
"shadow-float ring-1 ring-line-seam"
|
|
@@ -1,35 +1,16 @@
|
|
|
1
1
|
import { tv } from "../../lib/tv.mjs";
|
|
2
|
-
import { messageEmbed } from "../message-embed/message-embed.recipe.mjs";
|
|
2
|
+
import { messageCard, messageEmbed } from "../message-embed/message-embed.recipe.mjs";
|
|
3
3
|
import { Skeleton } from "../skeleton/skeleton.mjs";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
//#region src/components/app-link-card/app-link-card.tsx
|
|
6
6
|
const appLinkCard = tv({ slots: {
|
|
7
|
-
root: ["flex
|
|
8
|
-
header: [
|
|
9
|
-
"flex min-w-0 items-center gap-1.5",
|
|
10
|
-
"border-b border-line-hairline px-4 py-2",
|
|
11
|
-
"font-sans text-caption text-foreground-muted"
|
|
12
|
-
],
|
|
13
|
-
icon: ["inline-flex size-4 shrink-0 items-center justify-center", "*:size-full *:shrink-0 [&>img]:rounded-[2px] [&>img]:object-cover"],
|
|
14
|
-
name: ["min-w-0 truncate", "font-semibold text-foreground"],
|
|
15
|
-
host: ["flex min-w-0 items-baseline", "before:shrink-0 before:pe-1.5 before:content-['·']"],
|
|
16
|
-
hostText: ["min-w-0 truncate"],
|
|
17
|
-
body: ["flex min-w-0 flex-col gap-1.5", "px-4 py-3 last:pb-4"],
|
|
18
|
-
eyebrow: ["m-0 truncate", "font-mono text-mono-meta text-foreground-muted"],
|
|
19
|
-
title: [
|
|
20
|
-
"m-0 line-clamp-2",
|
|
21
|
-
"font-sans text-headline font-bold text-foreground",
|
|
22
|
-
"[&_a]:rounded-[2px] [&_a]:decoration-[1.5px] [&_a]:underline-offset-[3px] [&_a]:hover:underline",
|
|
23
|
-
"[&_a]:outline-none [&_a]:focus-visible:shadow-focus"
|
|
24
|
-
],
|
|
25
|
-
meta: ["flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1", "m-0 font-sans text-callout text-foreground-muted"],
|
|
26
|
-
actions: ["flex flex-wrap items-center justify-end gap-2", "px-4 pb-3 last:pb-4"],
|
|
7
|
+
root: ["flex flex-col", "my-2 first:mt-0 last:mb-0"],
|
|
27
8
|
placeholderLine: ["flex h-lh items-center"],
|
|
28
9
|
placeholderButton: ["h-8 md:pointer-fine:h-control w-28 rounded-control"]
|
|
29
10
|
} });
|
|
30
11
|
function frame(className) {
|
|
31
12
|
const { root } = appLinkCard();
|
|
32
|
-
return messageEmbed().root({ className: root({ className }) });
|
|
13
|
+
return messageEmbed({ size: "item" }).root({ className: root({ className }) });
|
|
33
14
|
}
|
|
34
15
|
/**
|
|
35
16
|
* An app link's card drawn in the message, after the link's paragraph, where
|
|
@@ -53,7 +34,8 @@ function AppLinkCard({ className, ...props }) {
|
|
|
53
34
|
* lines and a button follow, as gray bars.
|
|
54
35
|
*/
|
|
55
36
|
function AppLinkCardPlaceholder({ className, children, ...props }) {
|
|
56
|
-
const { body, eyebrow, title, meta, actions
|
|
37
|
+
const { body, eyebrow, title, meta, actions } = messageCard();
|
|
38
|
+
const { placeholderLine, placeholderButton } = appLinkCard();
|
|
57
39
|
return /* @__PURE__ */ jsxs("div", {
|
|
58
40
|
"data-slot": "app-link-card-placeholder",
|
|
59
41
|
"aria-busy": "true",
|
|
@@ -107,28 +89,28 @@ function AppLinkCardPlaceholder({ className, children, ...props }) {
|
|
|
107
89
|
}
|
|
108
90
|
/** Raft's row at the top: `AppLinkCardIcon`, `AppLinkCardName`, then `AppLinkCardHost`. */
|
|
109
91
|
function AppLinkCardHeader({ className, ...props }) {
|
|
110
|
-
const {
|
|
92
|
+
const { source } = messageCard();
|
|
111
93
|
return /* @__PURE__ */ jsx("div", {
|
|
112
94
|
"data-slot": "app-link-card-header",
|
|
113
|
-
className:
|
|
95
|
+
className: source({ className }),
|
|
114
96
|
...props
|
|
115
97
|
});
|
|
116
98
|
}
|
|
117
99
|
/** The app's registered icon, the image Raft stores and serves: one child at 16px. */
|
|
118
100
|
function AppLinkCardIcon({ className, ...props }) {
|
|
119
|
-
const {
|
|
101
|
+
const { sourceIcon } = messageCard();
|
|
120
102
|
return /* @__PURE__ */ jsx("span", {
|
|
121
103
|
"data-slot": "app-link-card-icon",
|
|
122
|
-
className:
|
|
104
|
+
className: sourceIcon({ className }),
|
|
123
105
|
...props
|
|
124
106
|
});
|
|
125
107
|
}
|
|
126
108
|
/** The app's name as Raft stores it. One line, truncated. */
|
|
127
109
|
function AppLinkCardName({ className, ...props }) {
|
|
128
|
-
const {
|
|
110
|
+
const { sourceName } = messageCard();
|
|
129
111
|
return /* @__PURE__ */ jsx("span", {
|
|
130
112
|
"data-slot": "app-link-card-name",
|
|
131
|
-
className:
|
|
113
|
+
className: sourceName({ className }),
|
|
132
114
|
...props
|
|
133
115
|
});
|
|
134
116
|
}
|
|
@@ -137,14 +119,14 @@ function AppLinkCardName({ className, ...props }) {
|
|
|
137
119
|
* the end, which says where the link goes, stays.
|
|
138
120
|
*/
|
|
139
121
|
function AppLinkCardHost({ className, children, ...props }) {
|
|
140
|
-
const {
|
|
122
|
+
const { sourceHost, sourceHostText } = messageCard();
|
|
141
123
|
return /* @__PURE__ */ jsx("span", {
|
|
142
124
|
"data-slot": "app-link-card-host",
|
|
143
|
-
className:
|
|
125
|
+
className: sourceHost({ className }),
|
|
144
126
|
...props,
|
|
145
127
|
children: /* @__PURE__ */ jsx("span", {
|
|
146
128
|
dir: "rtl",
|
|
147
|
-
className:
|
|
129
|
+
className: sourceHostText(),
|
|
148
130
|
children: /* @__PURE__ */ jsx("span", {
|
|
149
131
|
dir: "ltr",
|
|
150
132
|
children
|
|
@@ -154,7 +136,7 @@ function AppLinkCardHost({ className, children, ...props }) {
|
|
|
154
136
|
}
|
|
155
137
|
/** The item: `AppLinkCardEyebrow`, `AppLinkCardTitle`, `AppLinkCardMeta`, in that order. */
|
|
156
138
|
function AppLinkCardBody({ className, ...props }) {
|
|
157
|
-
const { body } =
|
|
139
|
+
const { body } = messageCard();
|
|
158
140
|
return /* @__PURE__ */ jsx("div", {
|
|
159
141
|
"data-slot": "app-link-card-body",
|
|
160
142
|
className: body({ className }),
|
|
@@ -163,7 +145,7 @@ function AppLinkCardBody({ className, ...props }) {
|
|
|
163
145
|
}
|
|
164
146
|
/** The app's id for the item, above the title. One line, truncated. */
|
|
165
147
|
function AppLinkCardEyebrow({ className, ...props }) {
|
|
166
|
-
const { eyebrow } =
|
|
148
|
+
const { eyebrow } = messageCard();
|
|
167
149
|
return /* @__PURE__ */ jsx("p", {
|
|
168
150
|
"data-slot": "app-link-card-eyebrow",
|
|
169
151
|
className: eyebrow({ className }),
|
|
@@ -175,7 +157,7 @@ function AppLinkCardEyebrow({ className, ...props }) {
|
|
|
175
157
|
* to the item in it: the title opens the item, in the title's ink.
|
|
176
158
|
*/
|
|
177
159
|
function AppLinkCardTitle({ className, ...props }) {
|
|
178
|
-
const { title } =
|
|
160
|
+
const { title } = messageCard();
|
|
179
161
|
return /* @__PURE__ */ jsx("p", {
|
|
180
162
|
"data-slot": "app-link-card-title",
|
|
181
163
|
className: title({ className }),
|
|
@@ -184,7 +166,7 @@ function AppLinkCardTitle({ className, ...props }) {
|
|
|
184
166
|
}
|
|
185
167
|
/** One state in a neutral Badge, then muted details. Wraps when it runs out of room. */
|
|
186
168
|
function AppLinkCardMeta({ className, ...props }) {
|
|
187
|
-
const { meta } =
|
|
169
|
+
const { meta } = messageCard();
|
|
188
170
|
return /* @__PURE__ */ jsx("div", {
|
|
189
171
|
"data-slot": "app-link-card-meta",
|
|
190
172
|
className: meta({ className }),
|
|
@@ -196,7 +178,7 @@ function AppLinkCardMeta({ className, ...props }) {
|
|
|
196
178
|
* last, with a More menu (⋯) to their left for the rest.
|
|
197
179
|
*/
|
|
198
180
|
function AppLinkCardActions({ className, ...props }) {
|
|
199
|
-
const { actions } =
|
|
181
|
+
const { actions } = messageCard();
|
|
200
182
|
return /* @__PURE__ */ jsx("div", {
|
|
201
183
|
"data-slot": "app-link-card-actions",
|
|
202
184
|
className: actions({ className }),
|
|
@@ -19,7 +19,7 @@ const appShell = tv({
|
|
|
19
19
|
sidebarSlot: ["hidden min-h-0 shrink-0 flex-col md:flex"],
|
|
20
20
|
mainSlot: ["flex min-h-0 min-w-0 flex-1 flex-col"],
|
|
21
21
|
panelSlot: ["hidden min-h-0 min-w-0 flex-col md:flex"],
|
|
22
|
-
overlaySlot: ["absolute inset-0 z-
|
|
22
|
+
overlaySlot: ["absolute inset-0 z-cover flex min-h-0 min-w-0"]
|
|
23
23
|
},
|
|
24
24
|
variants: { overlay: {
|
|
25
25
|
false: {},
|
|
@@ -135,7 +135,7 @@ function AppShellSidebarSlot({ className, id: idProp, render, ...props }) {
|
|
|
135
135
|
id,
|
|
136
136
|
inert: collapsed ? true : void 0,
|
|
137
137
|
className: sidebarSlot({ className: cn(className, sidebar?.collapsible && [
|
|
138
|
-
"group-data-[state=expanded]/app-shell:relative group-data-[state=expanded]/app-shell:z-
|
|
138
|
+
"group-data-[state=expanded]/app-shell:relative group-data-[state=expanded]/app-shell:z-raised",
|
|
139
139
|
"group-data-[state=collapsed]/app-shell:pointer-events-none",
|
|
140
140
|
"group-data-[state=collapsed]/app-shell:opacity-0"
|
|
141
141
|
]) })
|
|
@@ -37,7 +37,7 @@ const avatar = tv({
|
|
|
37
37
|
"[[data-size='3xs']>&]:[&>svg]:size-2"
|
|
38
38
|
],
|
|
39
39
|
badge: [
|
|
40
|
-
"absolute z-
|
|
40
|
+
"absolute z-raised select-none",
|
|
41
41
|
"size-[11px] -right-1 -bottom-1 rounded-[2px]",
|
|
42
42
|
"outline-2 outline-solid outline-layer-canvas",
|
|
43
43
|
"group-data-[size=xl]/avatar:size-5 group-data-[size=xl]/avatar:-right-2 group-data-[size=xl]/avatar:-bottom-2",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { laidOutChildren } from "../../lib/laid-out-children.mjs";
|
|
1
2
|
import { tv } from "../../lib/tv.mjs";
|
|
2
3
|
import { Button } from "../button/button.mjs";
|
|
3
4
|
import { X } from "lucide-react";
|
|
@@ -67,7 +68,7 @@ const banner = tv({
|
|
|
67
68
|
defaultVariants: { size: "md" },
|
|
68
69
|
compoundVariants: [{
|
|
69
70
|
size: "sm",
|
|
70
|
-
class: { root: ["[--banner-button:--spacing(8)] md:pointer-fine:[--banner-button:var(--control-height)]", "has-[>[data-slot=banner-action],>[data-banner-close]]:py-[calc((var(--banner-button)-1lh)/2
|
|
71
|
+
class: { root: ["[--banner-button:--spacing(8)] md:pointer-fine:[--banner-button:var(--control-height)]", "has-[>[data-slot=banner-action],>[data-banner-close]]:py-[calc((var(--banner-button)-1lh)/2+(--spacing(1)))]"] }
|
|
71
72
|
}]
|
|
72
73
|
});
|
|
73
74
|
/**
|
|
@@ -126,6 +127,18 @@ function contentWidth(element) {
|
|
|
126
127
|
range.selectNodeContents(element);
|
|
127
128
|
return range.getBoundingClientRect().width;
|
|
128
129
|
}
|
|
130
|
+
function actionsWidth(action) {
|
|
131
|
+
const items = laidOutChildren(action);
|
|
132
|
+
if (items.length === 0) return contentWidth(action);
|
|
133
|
+
let start = Number.POSITIVE_INFINITY;
|
|
134
|
+
let end = Number.NEGATIVE_INFINITY;
|
|
135
|
+
for (const item of items) {
|
|
136
|
+
const box = item.getBoundingClientRect();
|
|
137
|
+
start = Math.min(start, box.left);
|
|
138
|
+
end = Math.max(end, box.right);
|
|
139
|
+
}
|
|
140
|
+
return end - start;
|
|
141
|
+
}
|
|
129
142
|
function useWrapsBesideText(action) {
|
|
130
143
|
const [wraps, setWraps] = useState(false);
|
|
131
144
|
useLayoutEffect(() => {
|
|
@@ -148,7 +161,7 @@ function useWrapsBesideText(action) {
|
|
|
148
161
|
}
|
|
149
162
|
if (lines > 1) return;
|
|
150
163
|
const gap = Number.parseFloat(getComputedStyle(root).columnGap) || 0;
|
|
151
|
-
if (contentWidth(text) +
|
|
164
|
+
if (contentWidth(text) + actionsWidth(action) + gap + 1 <= text.getBoundingClientRect().width) setWraps(false);
|
|
152
165
|
};
|
|
153
166
|
let frame = 0;
|
|
154
167
|
const schedule = () => {
|
|
@@ -10,11 +10,11 @@ const buttonSplit = tv({ base: [
|
|
|
10
10
|
"data-sticker-edge:ring-[1.5px]",
|
|
11
11
|
"data-[size=lg]:rounded-(--control-radius)"
|
|
12
12
|
] });
|
|
13
|
-
const buttonSplitMain = tv({ base: ["rounded-e-none border-e-0", "ring-0 focus-visible:z-
|
|
13
|
+
const buttonSplitMain = tv({ base: ["rounded-e-none border-e-0", "ring-0 focus-visible:z-raised"] });
|
|
14
14
|
const buttonSplitMenu = tv({
|
|
15
15
|
base: [
|
|
16
16
|
"min-w-0 rounded-s-none px-0",
|
|
17
|
-
"ring-0 focus-visible:z-
|
|
17
|
+
"ring-0 focus-visible:z-raised",
|
|
18
18
|
"after:inset-s-0"
|
|
19
19
|
],
|
|
20
20
|
variants: {
|
|
@@ -38,8 +38,8 @@ const buttonVariants = tv({
|
|
|
38
38
|
"disabled:border-line-hairline disabled:bg-fill-muted disabled:bg-none disabled:text-foreground-disabled disabled:ring-0",
|
|
39
39
|
"aria-disabled:border-line-hairline aria-disabled:bg-fill-muted aria-disabled:bg-none aria-disabled:text-foreground-disabled aria-disabled:ring-0"
|
|
40
40
|
],
|
|
41
|
-
content: "relative z-
|
|
42
|
-
loadingIndicator: "pointer-events-none absolute inset-0 z-
|
|
41
|
+
content: "relative z-raised inline-flex min-w-0 max-w-full items-center justify-center gap-[inherit] data-[loading=true]:invisible",
|
|
42
|
+
loadingIndicator: "pointer-events-none absolute inset-0 z-raised flex items-center justify-center"
|
|
43
43
|
},
|
|
44
44
|
variants: {
|
|
45
45
|
variant: {
|
|
@@ -24,7 +24,7 @@ const choiceList = tv({
|
|
|
24
24
|
description: ["block truncate font-sans text-footnote font-normal text-foreground-muted", "in-data-disabled:text-foreground-disabled"],
|
|
25
25
|
meta: ["block truncate font-sans text-footnote font-normal text-foreground-muted", "in-data-disabled:text-foreground-disabled"],
|
|
26
26
|
action: [
|
|
27
|
-
"relative z-
|
|
27
|
+
"relative z-raised col-start-2 flex h-lh items-center self-start pr-1",
|
|
28
28
|
"mt-option-inset pointer-coarse:mt-[calc((--spacing(11)-1lh)/2)]",
|
|
29
29
|
"font-sans text-subhead"
|
|
30
30
|
],
|
|
@@ -16,11 +16,11 @@ const codeBlock = tv({ slots: {
|
|
|
16
16
|
actions: [
|
|
17
17
|
"ml-auto flex items-center gap-1",
|
|
18
18
|
"[[data-slot=code-block-body]>&]:col-start-2 [[data-slot=code-block-body]>&]:row-start-1",
|
|
19
|
-
"[[data-slot=code-block-body]>&]:z-
|
|
19
|
+
"[[data-slot=code-block-body]>&]:z-raised [[data-slot=code-block-body]>&]:ml-3",
|
|
20
20
|
"[[data-slot=code-block-body]>&]:my-[calc(var(--control-embedded-inset)-0.5rem-1px)] [[data-slot=code-block-body]>&]:mr-[calc(var(--control-embedded-inset)-0.75rem-1px)]"
|
|
21
21
|
],
|
|
22
22
|
corner: [
|
|
23
|
-
"pointer-events-none absolute z-
|
|
23
|
+
"pointer-events-none absolute z-raised size-1",
|
|
24
24
|
"data-[y=top]:top-0 data-[y=bottom]:bottom-0",
|
|
25
25
|
"data-[x=left]:left-0 data-[x=right]:right-0",
|
|
26
26
|
"hidden"
|
|
@@ -60,7 +60,7 @@ const combobox = tv({
|
|
|
60
60
|
"first:rounded-t-container has-focus-visible:keyboard:inset-ring-2 has-focus-visible:keyboard:inset-ring-foreground"
|
|
61
61
|
],
|
|
62
62
|
input: ["w-full outline-none"],
|
|
63
|
-
positioner: ["isolate z-
|
|
63
|
+
positioner: ["isolate z-popup outline-none"],
|
|
64
64
|
content: [
|
|
65
65
|
"h-auto max-h-(--available-height) min-w-[220px] overflow-hidden",
|
|
66
66
|
"max-w-[min(var(--available-width),max(var(--anchor-width),24rem))]",
|
|
@@ -7,7 +7,7 @@ const comment = tv({
|
|
|
7
7
|
"absolute inset-x-0 top-0",
|
|
8
8
|
"data-animate:transition-transform data-animate:duration-150 data-animate:ease-out",
|
|
9
9
|
"motion-reduce:transition-none",
|
|
10
|
-
"data-active:z-
|
|
10
|
+
"data-active:z-raised"
|
|
11
11
|
],
|
|
12
12
|
card: [
|
|
13
13
|
"relative flex min-w-0 flex-col gap-2",
|
|
@@ -16,7 +16,7 @@ const comment = tv({
|
|
|
16
16
|
"data-active:border-[1.5px] data-active:border-line-strong data-active:p-[11.5px] data-active:shadow-float"
|
|
17
17
|
],
|
|
18
18
|
cardTrigger: [
|
|
19
|
-
"absolute -inset-px z-
|
|
19
|
+
"absolute -inset-px z-raised cursor-pointer",
|
|
20
20
|
"rounded-container border-0 bg-transparent p-0",
|
|
21
21
|
"hover:bg-fill-subtle active:bg-fill-medium",
|
|
22
22
|
"outline-none focus-visible:shadow-focus"
|
|
@@ -20,7 +20,7 @@ const composerAttachment = tv({
|
|
|
20
20
|
"pe-1.5"
|
|
21
21
|
],
|
|
22
22
|
attachmentsNav: [
|
|
23
|
-
"absolute top-1/2 z-
|
|
23
|
+
"absolute top-1/2 z-control -translate-y-1/2",
|
|
24
24
|
"size-8 rounded-chip shadow-none",
|
|
25
25
|
"[&_svg:not([class*='size-'])]:size-4.5"
|
|
26
26
|
],
|
|
@@ -42,26 +42,26 @@ const composerAttachment = tv({
|
|
|
42
42
|
attachmentTitle: ["block truncate", "font-sans text-footnote font-semibold text-foreground"],
|
|
43
43
|
attachmentMeta: ["block w-full truncate", "text-left font-mono text-mono-meta text-foreground-hint"],
|
|
44
44
|
attachmentUploadingOverlay: [
|
|
45
|
-
"absolute inset-0 z-
|
|
45
|
+
"absolute inset-0 z-raised flex flex-col items-center justify-center px-1",
|
|
46
46
|
"text-center",
|
|
47
47
|
"rounded-control bg-layer-veil",
|
|
48
48
|
"font-mono text-mono-meta font-bold text-foreground"
|
|
49
49
|
],
|
|
50
50
|
attachmentUploadProgressBar: [
|
|
51
|
-
"absolute z-
|
|
51
|
+
"absolute z-raised h-1 overflow-hidden",
|
|
52
52
|
"right-1.5 bottom-1.5 left-1.5 w-auto rounded-[2px]",
|
|
53
53
|
"bg-layer-card"
|
|
54
54
|
],
|
|
55
55
|
attachmentUploadProgressBarIndicator: ["block h-full", "rounded-[inherit] bg-info"],
|
|
56
56
|
attachmentFailedOverlay: [
|
|
57
|
-
"absolute inset-0 z-
|
|
57
|
+
"absolute inset-0 z-raised flex flex-col items-center justify-center px-1",
|
|
58
58
|
"[border-width:var(--ca-overlay-border,2px)]",
|
|
59
59
|
"text-center",
|
|
60
60
|
"rounded-control border-danger-strong bg-layer-veil",
|
|
61
61
|
"font-sans text-caption font-semibold text-danger-strong"
|
|
62
62
|
],
|
|
63
63
|
attachmentRemove: [
|
|
64
|
-
"absolute z-
|
|
64
|
+
"absolute z-control flex items-center justify-center",
|
|
65
65
|
"transition-opacity group-hover/composer-attachment:opacity-100",
|
|
66
66
|
"[@media(hover:none)]:opacity-100",
|
|
67
67
|
"[&_svg]:shrink-0",
|
|
@@ -42,11 +42,11 @@ function ComposerDropOverlayPrompt({ children, ...props }) {
|
|
|
42
42
|
children: /* @__PURE__ */ jsx(FileText, { className: "text-foreground-muted" })
|
|
43
43
|
}),
|
|
44
44
|
/* @__PURE__ */ jsx("span", {
|
|
45
|
-
className: dropOverlayIcon({ className: "absolute z-
|
|
45
|
+
className: dropOverlayIcon({ className: "absolute z-raised" }),
|
|
46
46
|
children: /* @__PURE__ */ jsx(Upload, { className: "text-foreground-muted" })
|
|
47
47
|
}),
|
|
48
48
|
/* @__PURE__ */ jsx("span", {
|
|
49
|
-
className: dropOverlayIcon({ className: "absolute z-
|
|
49
|
+
className: dropOverlayIcon({ className: "absolute z-raised translate-x-7 translate-y-1 rotate-12" }),
|
|
50
50
|
children: /* @__PURE__ */ jsx(Image, { className: "text-foreground-muted" })
|
|
51
51
|
})
|
|
52
52
|
]
|
|
@@ -2,7 +2,7 @@ import { tv } from "../../lib/tv.mjs";
|
|
|
2
2
|
//#region src/components/composer/composer-drop-overlay.recipe.ts
|
|
3
3
|
const composerDropOverlay = tv({ slots: {
|
|
4
4
|
dropOverlay: [
|
|
5
|
-
"absolute inset-0 z-
|
|
5
|
+
"absolute inset-0 z-float flex items-center justify-center",
|
|
6
6
|
"rounded-container border-[1.5px] border-dashed border-line-seam",
|
|
7
7
|
"bg-layer-card bg-[linear-gradient(var(--info-soft),var(--info-soft))]"
|
|
8
8
|
],
|
|
@@ -225,7 +225,7 @@ function SortableComposerAttachment({ id, label, children }) {
|
|
|
225
225
|
const style = {
|
|
226
226
|
transform: isDragging ? void 0 : CSS.Translate.toString(transform),
|
|
227
227
|
transition: isDragging ? void 0 : transition,
|
|
228
|
-
zIndex: isDragging ?
|
|
228
|
+
zIndex: isDragging ? "var(--z-index-base)" : void 0
|
|
229
229
|
};
|
|
230
230
|
const place = isDragging && !home;
|
|
231
231
|
return /* @__PURE__ */ jsxs("div", {
|