@botiverse/raft-ui 0.82.0 → 0.84.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +147 -0
  2. package/dist/components/app-link-card/app-link-card.mjs +19 -37
  3. package/dist/components/banner/banner.mjs +15 -2
  4. package/dist/components/dialog/dialog-sheet-context.mjs +7 -2
  5. package/dist/components/dialog/dialog-sheet.d.mts +3 -2
  6. package/dist/components/dialog/dialog-sheet.mjs +29 -43
  7. package/dist/components/dialog/dialog.d.mts +12 -1
  8. package/dist/components/dialog/dialog.mjs +50 -10
  9. package/dist/components/drag/drag.recipe.mjs +1 -1
  10. package/dist/components/fit-toolbar/fit-toolbar.d.mts +165 -0
  11. package/dist/components/fit-toolbar/fit-toolbar.mjs +736 -0
  12. package/dist/components/fit-toolbar/fit-toolbar.recipe.mjs +35 -0
  13. package/dist/components/fit-toolbar/index.d.mts +2 -0
  14. package/dist/components/lightbox/index.d.mts +2 -2
  15. package/dist/components/lightbox/lightbox.d.mts +70 -4
  16. package/dist/components/lightbox/lightbox.mjs +285 -91
  17. package/dist/components/list-row/list-row.d.mts +14 -1
  18. package/dist/components/list-row/list-row.mjs +4 -2
  19. package/dist/components/list-row/list-row.recipe.d.mts +15 -3
  20. package/dist/components/list-row/list-row.recipe.mjs +18 -1
  21. package/dist/components/message-action-card/message-action-card-content.recipe.mjs +3 -3
  22. package/dist/components/message-action-card/message-action-card.mjs +7 -3
  23. package/dist/components/message-action-card/message-action-card.recipe.mjs +2 -6
  24. package/dist/components/message-embed/index.d.mts +2 -1
  25. package/dist/components/message-embed/message-embed.d.mts +8 -1
  26. package/dist/components/message-embed/message-embed.mjs +2 -2
  27. package/dist/components/message-embed/message-embed.recipe.d.mts +160 -0
  28. package/dist/components/message-embed/message-embed.recipe.mjs +49 -10
  29. package/dist/components/message-forwarded-bundle/message-forwarded-bundle.mjs +1 -0
  30. package/dist/components/message-forwarded-bundle/message-forwarded-bundle.recipe.mjs +1 -1
  31. package/dist/components/message-quoted-preview/message-quoted-preview.mjs +2 -0
  32. package/dist/components/message-replies/message-replies.mjs +1 -1
  33. package/dist/components/message-replies/message-replies.recipe.mjs +1 -1
  34. package/dist/components/mobile-nav/mobile-nav-server.d.mts +21 -8
  35. package/dist/components/mobile-nav/mobile-nav-server.mjs +142 -13
  36. package/dist/components/popover/popover.d.mts +11 -2
  37. package/dist/components/popover/popover.mjs +51 -38
  38. package/dist/components/preview-card/preview-card.d.mts +5 -1
  39. package/dist/components/preview-card/preview-card.mjs +24 -27
  40. package/dist/components/save-status/save-status.d.mts +16 -9
  41. package/dist/components/save-status/save-status.mjs +19 -30
  42. package/dist/components/segmented-control/segmented-control.d.mts +1 -1
  43. package/dist/components/segmented-control/segmented-control.mjs +56 -10
  44. package/dist/components/selection-toolbar/index.d.mts +2 -0
  45. package/dist/components/selection-toolbar/selection-toolbar.d.mts +100 -0
  46. package/dist/components/selection-toolbar/selection-toolbar.mjs +217 -0
  47. package/dist/components/selection-toolbar/selection-toolbar.recipe.mjs +42 -0
  48. package/dist/components/task-board/task-board.d.mts +5 -0
  49. package/dist/components/task-board/task-board.mjs +8 -1
  50. package/dist/components/task-dnd/task-dnd.d.mts +10 -1
  51. package/dist/components/task-dnd/task-dnd.mjs +2 -1
  52. package/dist/components/toast/toast.mjs +2 -2
  53. package/dist/index.d.mts +4 -2
  54. package/dist/index.mjs +4 -2
  55. package/dist/lib/label-value-fit.mjs +33 -6
  56. package/dist/lib/laid-out-children.mjs +32 -0
  57. package/dist/lib/stacked-actions.mjs +7 -4
  58. package/dist/lib/use-mobile-viewport.mjs +5 -2
  59. package/dist/lib/use-soft-keyboard.mjs +72 -0
  60. package/dist/wip.d.mts +8 -3
  61. package/dist/wip.mjs +5 -3
  62. package/package.json +1 -1
@@ -6,13 +6,15 @@ import { tv } from "../../lib/tv.mjs";
6
6
  import { useThemeScopeProps } from "../../lib/theme/theme-scope.mjs";
7
7
  import { useParentOverlayKind, withOverlayLayer } from "../../lib/overlay-depth.mjs";
8
8
  import { Button } from "../button/button.mjs";
9
- import { DialogSheetHeadContext, DialogSheetReset, dialogSheet, useDialogSheet, useInDialogSheetHead } from "./dialog-sheet-context.mjs";
9
+ import { DialogSheetHeadContext, DialogSheetReset, DialogSheetSurfaceContext, dialogSheet, useDialogSheet, useInDialogSheetHead } from "./dialog-sheet-context.mjs";
10
10
  import { ScrollArea } from "../scroll-area/scroll-area.mjs";
11
+ import { useMobileViewport } from "../../lib/use-mobile-viewport.mjs";
12
+ import { useSoftKeyboard } from "../../lib/use-soft-keyboard.mjs";
11
13
  import { TextLink } from "../text-link/text-link.mjs";
12
14
  import { PanelAction } from "../panel/panel-action.mjs";
13
15
  import { Dialog } from "@base-ui/react/dialog";
14
16
  import { ArrowLeft, X } from "lucide-react";
15
- import { Children, createContext, isValidElement, useContext, useEffect } from "react";
17
+ import { Children, createContext, isValidElement, useContext, useEffect, useState } from "react";
16
18
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
17
19
  import { mergeProps } from "@base-ui/react/merge-props";
18
20
  import { useRender } from "@base-ui/react/use-render";
@@ -59,12 +61,20 @@ const dialog = tv({
59
61
  lg: { content: ["max-w-160"] },
60
62
  wide: { content: ["max-w-4xl h-[min(760px,calc(100dvh-2rem))]"] }
61
63
  },
64
+ placement: {
65
+ center: {},
66
+ top: { content: ["md:top-(--dialog-top,8vh) md:translate-y-0", "md:max-h-[calc(100dvh-var(--dialog-top,8vh)-16px)]"] }
67
+ },
68
+ keyboard: {
69
+ false: {},
70
+ true: { content: ["data-soft-keyboard:top-[calc(var(--dialog-visible-top)+env(safe-area-inset-top,0px)+var(--header-height))] data-soft-keyboard:translate-y-0", "data-soft-keyboard:max-h-[calc(var(--dialog-visible-height)-env(safe-area-inset-top,0px)-var(--header-height)-16px)]"] }
71
+ },
62
72
  bar: {
63
73
  false: {},
64
74
  true: {
65
- header: ["flex h-(--header-height) shrink-0 items-center gap-2 border-b px-(--surface-inset) py-0", "border-line-seam pt-0 [&>[data-slot=dialog-close]]:my-0"],
66
- body: ["flex min-h-0 flex-1 overflow-hidden p-0", "px-0 pt-0 pb-0 first:pt-0 text-foreground"],
67
- footer: ["shrink-0 border-t px-(--surface-inset) py-3", "border-line-seam pt-3 pb-3 first:pt-3 [[data-slot=dialog-header]+&]:pt-3"]
75
+ header: ["flex h-(--header-height) shrink-0 items-center gap-2 border-b px-(--surface-inset) py-0", "border-line-seam pt-0 *:data-[slot=dialog-close]:my-0"],
76
+ body: ["flex min-h-0 flex-1 overflow-hidden p-0", "p-0 first:pt-0 text-foreground"],
77
+ footer: ["shrink-0 border-t px-(--surface-inset) py-3", "border-line-seam py-3 first:pt-3 [[data-slot=dialog-header]+&]:pt-3"]
68
78
  }
69
79
  },
70
80
  layer: {
@@ -125,10 +135,16 @@ function DialogOverlay({ className, layer, ...props }) {
125
135
  });
126
136
  }
127
137
  const DialogSizeContext = createContext(void 0);
128
- function DialogContent({ className, children, portalProps, overlay, size, layer, ...props }) {
138
+ function DialogContent({ className, children, portalProps, overlay, size, layer, placement, ...props }) {
139
+ const top = placement === "top";
140
+ const sheetSurface = useContext(DialogSheetSurfaceContext);
141
+ const [keyboard, setKeyboard] = useState(null);
142
+ const raised = !sheetSurface && keyboard != null;
129
143
  const { content } = dialog({
130
144
  size,
131
- layer
145
+ layer,
146
+ placement,
147
+ keyboard: raised || void 0
132
148
  });
133
149
  const overlayProps = typeof overlay === "object" ? overlay : {};
134
150
  return /* @__PURE__ */ jsxs(DialogPortal, {
@@ -136,17 +152,41 @@ function DialogContent({ className, children, portalProps, overlay, size, layer,
136
152
  children: [overlay === false ? null : /* @__PURE__ */ jsx(DialogOverlay, {
137
153
  layer,
138
154
  ...overlayProps
139
- }), /* @__PURE__ */ jsx(Dialog.Popup, {
155
+ }), /* @__PURE__ */ jsxs(Dialog.Popup, {
140
156
  "data-slot": "dialog-content",
157
+ "data-placement": top ? "top" : void 0,
158
+ "data-soft-keyboard": raised ? "" : void 0,
141
159
  className: content({ className }),
142
160
  ...props,
143
- children: /* @__PURE__ */ jsx(DialogSizeContext.Provider, {
161
+ ...raised ? { style: withVisibleArea(props.style, keyboard) } : null,
162
+ children: [sheetSurface ? null : /* @__PURE__ */ jsx(SoftKeyboardWatch, { onChange: setKeyboard }), /* @__PURE__ */ jsx(DialogSizeContext.Provider, {
144
163
  value: size ?? (/(^|\s)max-w-/.test(className ?? "") ? void 0 : "md"),
145
164
  children: /* @__PURE__ */ jsx(DialogSheetReset, { children })
146
- })
165
+ })]
147
166
  })]
148
167
  });
149
168
  }
169
+ /**
170
+ * Reports the on-screen keyboard on a phone to a centred dialog, which moves
171
+ * up while it is open (dialog.md, "On a phone"). Rendered inside the popup,
172
+ * so it listens only while the dialog is open, and only below 768px.
173
+ */
174
+ function SoftKeyboardWatch({ onChange }) {
175
+ const keyboard = useSoftKeyboard(useMobileViewport());
176
+ useEffect(() => {
177
+ onChange(keyboard);
178
+ }, [keyboard, onChange]);
179
+ useEffect(() => () => onChange(null), [onChange]);
180
+ return null;
181
+ }
182
+ /** The popup's style with the area the keyboard leaves visible, for its CSS. */
183
+ function withVisibleArea(style, keyboard) {
184
+ return {
185
+ ...style,
186
+ "--dialog-visible-top": `${keyboard.top}px`,
187
+ "--dialog-visible-height": `${keyboard.height}px`
188
+ };
189
+ }
150
190
  function DialogClose({ className, variant, size, children, render, "aria-label": ariaLabel, ...props }) {
151
191
  const inSheetHead = useInDialogSheetHead();
152
192
  if (render) return /* @__PURE__ */ jsx(Dialog.Close, {
@@ -18,7 +18,7 @@ const drag = tv({
18
18
  "border-[1.5px] border-dashed border-line-strong bg-fill-subtle"
19
19
  ],
20
20
  hint: [
21
- "pointer-events-none fixed top-0 left-0 z-70 max-w-80",
21
+ "pointer-events-none fixed top-0 left-0 z-999 max-w-80",
22
22
  "rounded-chip px-2 py-1",
23
23
  "font-sans text-caption",
24
24
  "bg-layer-hud text-foreground-on-hud shadow-float ring-1 ring-line-seam"
@@ -0,0 +1,165 @@
1
+ import { StyledProps } from "../../lib/styled-props.mjs";
2
+ import { ButtonProps } from "../button/button.mjs";
3
+ import { PickerTriggerButtonProps } from "../picker-trigger-button/picker-trigger-button.mjs";
4
+ import { ReactElement, ReactNode, Ref } from "react";
5
+ import { Toolbar } from "@base-ui/react/toolbar";
6
+ //#region src/components/fit-toolbar/fit-toolbar.d.ts
7
+ /**
8
+ * The ways an item gives way, each one step:
9
+ * - `label`: its label hides and its icon stays (the label stays its accessible name);
10
+ * - `more`: it moves into More;
11
+ * - `collapse`: a group shows its one button instead of its items (a Filter button for its filters);
12
+ * - `wrap`: a group moves to a line of its own, only in a toolbar that allows it;
13
+ * - `plus`: a primary action shows as + alone (the label stays its accessible name).
14
+ */
15
+ export type FitStep = "label" | "more" | "collapse" | "wrap" | "plus";
16
+ /**
17
+ * When each of an item's steps comes, one order for the whole toolbar: lower
18
+ * gives way first; at the same number, the last item first, and one item's
19
+ * steps in the order of `FitStep`.
20
+ */
21
+ export type FitSteps = Partial<Record<FitStep, number>>;
22
+ export type FitToolbarProps = StyledProps<Toolbar.Root.Props> & {
23
+ ref?: Ref<HTMLDivElement>;
24
+ /**
25
+ * Groups may move to a line of their own (the `wrap` step) when the row
26
+ * runs out of room. Off for a toolbar that floats over content: a second
27
+ * line would cover it, so its last step is into More.
28
+ */
29
+ wrap?: boolean;
30
+ };
31
+ /**
32
+ * A toolbar whose items give way, in named steps and an order the items
33
+ * declare, as the region it sits in narrows: it measures its content in its
34
+ * own width, never the window's. The steps are written on each item as
35
+ * `data-fit-<step>` for its styles. A Base UI Toolbar: one Tab stop, the
36
+ * arrow keys inside.
37
+ */
38
+ export declare function FitToolbar({ className, wrap, ref, ...props }: FitToolbarProps): import("react").JSX.Element;
39
+ /**
40
+ * Inside a FitToolbar: a control that would measure its own fit (a
41
+ * SegmentedControl's `iconsOnlyBelow`) leaves it to the toolbar's steps.
42
+ */
43
+ export declare function useInFitToolbar(): boolean;
44
+ export type FitItemOptions = FitSteps & {
45
+ /** What the item is in More's menu while it is there (`FitToolbarMoreItems`). */
46
+ moreItem?: ReactNode;
47
+ };
48
+ /**
49
+ * Makes an element an item of the nearest FitToolbar: spread `props` on it.
50
+ * Says which steps it has taken, for what it renders (a tooltip once its
51
+ * label is gone, say).
52
+ */
53
+ export declare function useFitItem({ moreItem, ...steps }?: FitItemOptions): {
54
+ id: string;
55
+ props: Record<string, string | undefined>;
56
+ /** Steps it has taken. */
57
+ taken: (step: FitStep) => boolean;
58
+ };
59
+ /**
60
+ * Makes an element the toolbar's More: spread `props` on it. It shows only
61
+ * while an item is in More (`data-fit-shown`); hide it otherwise.
62
+ */
63
+ export declare function useFitMore(): {
64
+ shown: boolean;
65
+ props: Record<string, string | undefined>;
66
+ };
67
+ /** The items in More, in their order, each as its `moreItem`: put it in More's menu. */
68
+ export declare function FitToolbarMoreItems(): import("react").JSX.Element[] | null;
69
+ export type FitToolbarGroupProps = StyledProps<Toolbar.Group.Props> & {
70
+ /** When it moves to a line of its own (the `wrap` step), in a toolbar that allows it. */
71
+ wrapOrder?: number;
72
+ /** Stays on the first line: it never wraps. The page decides which groups do. */
73
+ firstLine?: boolean;
74
+ /**
75
+ * Groups with the same `wrapLine` wrap onto the same line, in their order, a
76
+ * fixed gap apart; a group without one takes a line of its own.
77
+ */
78
+ wrapLine?: string;
79
+ /** When it shows `collapsed` instead of its items (the `collapse` step). */
80
+ collapseOrder?: number;
81
+ /** The group as one button (a Filter button for its filters), shown once it collapses. */
82
+ collapsed?: ReactNode;
83
+ /**
84
+ * `scroll`: the group takes the rest of its line and scrolls sideways
85
+ * instead of giving way, its items whole, the side that hides more fading.
86
+ * Mark an applied or chosen item `data-active`: it is kept in view.
87
+ */
88
+ overflow?: "scroll";
89
+ };
90
+ /**
91
+ * A group of items in one row, a fixed gap apart. A toolbar wraps only
92
+ * between groups, the last first; groups with the same `wrapLine` share a
93
+ * line, any other takes one of its own.
94
+ */
95
+ export declare function FitToolbarGroup({ className, wrapOrder, firstLine, wrapLine, collapseOrder, collapsed, overflow, children, ref, ...props }: FitToolbarGroupProps & {
96
+ ref?: Ref<HTMLDivElement>;
97
+ }): import("react").JSX.Element;
98
+ export type FitToolbarButtonProps = Omit<ButtonProps, "children" | "size"> & {
99
+ /** Its icon, which stays when its label gives way. A button with a `label` step needs one (or a `collapsedIcon`). */
100
+ icon?: ReactNode;
101
+ /**
102
+ * An icon shown only once its label has given way (the `label` step), in
103
+ * place of `icon`: a button that reads as text while it has room ("Clear
104
+ * filters") and as an icon (×) once it hasn't.
105
+ */
106
+ collapsedIcon?: ReactNode;
107
+ /** Its label: its accessible name and, once it has given way, its tooltip. */
108
+ children: ReactNode;
109
+ /** When its label gives way (the `label` step). */
110
+ labelOrder?: number;
111
+ /**
112
+ * When it shows as + alone, its icon and label gone (the `plus` step): for
113
+ * the primary action, after the groups have wrapped.
114
+ */
115
+ plusOrder?: number;
116
+ /** When it moves into More (the `more` step). */
117
+ moreOrder?: number;
118
+ /** What it is in More's menu. */
119
+ moreItem?: ReactNode;
120
+ };
121
+ /**
122
+ * A toolbar button that can give way: an `md` Button (ghost unless you say)
123
+ * with an icon and a label, an icon square with its label as a tooltip from
124
+ * `labelOrder` on, a + square with its label as a tooltip from `plusOrder`
125
+ * on, and hidden from `moreOrder` on.
126
+ */
127
+ export declare function FitToolbarButton({ icon, collapsedIcon, children, labelOrder, plusOrder, moreOrder, moreItem, variant, className, ...props }: FitToolbarButtonProps): import("react").JSX.Element;
128
+ export type FitToolbarPickerProps = Omit<PickerTriggerButtonProps, "children" | "render"> & {
129
+ /**
130
+ * The picker's trigger, which opens its popup: `<PopoverTrigger />`,
131
+ * `<DropdownMenuTrigger />` or a Base UI `Menu.Trigger`. The picker renders
132
+ * it as a toolbar button, so the page nests nothing itself.
133
+ */
134
+ trigger: ReactElement<{
135
+ render?: unknown;
136
+ }>;
137
+ /** Its icon, which stays when its label gives way. */
138
+ icon: ReactNode;
139
+ /** Its label ("Assignee"): part of its accessible name and, once it has given way, its tooltip. */
140
+ children: ReactNode;
141
+ /**
142
+ * What is chosen: the value ("In progress", "Kirby") or a count (`1`). It
143
+ * stays in view when the label gives way, and follows the label in the
144
+ * accessible name. A count (a number, or a string of digits) is set in Space
145
+ * Mono, as machine-written text; any other value keeps the label's font.
146
+ */
147
+ value?: ReactNode;
148
+ /** When its label gives way (the `label` step). */
149
+ labelOrder?: number;
150
+ /** When it moves into More (the `more` step). */
151
+ moreOrder?: number;
152
+ /** What it is in More's menu. */
153
+ moreItem?: ReactNode;
154
+ };
155
+ /**
156
+ * A picker's trigger as one toolbar item (a filter: Assignee, Channel): a
157
+ * PickerTriggerButton with an icon, a label, what is chosen and a chevron,
158
+ * filled `info-soft` once a value is chosen, as an applied filter is
159
+ * (Select › Filters). From `labelOrder` on its label gives way, while its
160
+ * icon, what is chosen and the chevron stay ("# ⌄", "👤 1 ⌄"); its label
161
+ * stays its accessible name, read before the value ("Assignee 1"), and shows
162
+ * as its tooltip.
163
+ */
164
+ export declare function FitToolbarPicker({ trigger, icon, children, value, labelOrder, moreOrder, moreItem, className, ...props }: FitToolbarPickerProps): import("react").JSX.Element;
165
+ //#endregion