@estiva-app/ui 0.55.0 → 0.56.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/dist/CollapsibleSection.d.ts +7 -1
- package/dist/CollapsibleSection.d.ts.map +1 -1
- package/dist/ContainerHeader.d.ts +7 -1
- package/dist/ContainerHeader.d.ts.map +1 -1
- package/dist/IconButton.d.ts.map +1 -1
- package/dist/ListColumn.d.ts +2 -0
- package/dist/ListColumn.d.ts.map +1 -1
- package/dist/Menu.d.ts.map +1 -1
- package/dist/NavItem.d.ts.map +1 -1
- package/dist/NavTree.d.ts +9 -1
- package/dist/NavTree.d.ts.map +1 -1
- package/dist/PersonTrigger.d.ts.map +1 -1
- package/dist/SectionHeader.d.ts +22 -3
- package/dist/SectionHeader.d.ts.map +1 -1
- package/dist/SectionLabel.d.ts.map +1 -1
- package/dist/gates/index.js +1 -1
- package/dist/gates/index.js.map +2 -2
- package/dist/index.js +136 -72
- package/dist/index.js.map +4 -4
- package/dist/looks.d.ts +4 -0
- package/dist/looks.d.ts.map +1 -1
- package/package.json +1 -1
- package/registry.json +93 -22
- package/src/CollapsibleSection.stories.tsx +14 -1
- package/src/CollapsibleSection.tsx +10 -4
- package/src/ContainerHeader.mdx +4 -0
- package/src/ContainerHeader.stories.tsx +14 -0
- package/src/ContainerHeader.test.tsx +8 -0
- package/src/ContainerHeader.tsx +34 -6
- package/src/IconButton.tsx +2 -1
- package/src/ListColumn.tsx +4 -2
- package/src/Menu.tsx +5 -4
- package/src/NavItem.test.tsx +2 -2
- package/src/NavItem.tsx +21 -12
- package/src/NavTree.mdx +16 -1
- package/src/NavTree.stories.tsx +49 -2
- package/src/NavTree.test.tsx +7 -0
- package/src/NavTree.tsx +21 -4
- package/src/PersonTrigger.tsx +6 -5
- package/src/SectionHeader.mdx +6 -0
- package/src/SectionHeader.stories.tsx +18 -0
- package/src/SectionHeader.test.tsx +26 -0
- package/src/SectionHeader.tsx +99 -23
- package/src/SectionLabel.tsx +6 -3
- package/src/Sidebar.tsx +1 -1
- package/src/eslint/no-copied-look.test.ts +2 -2
- package/src/gates/app-checks.ts +1 -1
- package/src/looks.ts +6 -0
package/dist/index.js
CHANGED
|
@@ -168,7 +168,7 @@ function Sidebar({ "aria-label": ariaLabel = "Workspace", children, className })
|
|
|
168
168
|
{
|
|
169
169
|
"aria-label": ariaLabel,
|
|
170
170
|
className: cn("flex w-60 shrink-0 flex-col", !onGround && "border-r border-border-default bg-bg-surface", className),
|
|
171
|
-
children: /* @__PURE__ */ jsx4(ScrollArea, { className: "min-h-0 flex-1", contentClassName: "flex flex-col gap-
|
|
171
|
+
children: /* @__PURE__ */ jsx4(ScrollArea, { className: "min-h-0 flex-1", contentClassName: "flex flex-col gap-0.5 px-2.5 py-3", children })
|
|
172
172
|
}
|
|
173
173
|
);
|
|
174
174
|
}
|
|
@@ -276,6 +276,25 @@ import {
|
|
|
276
276
|
// src/IconButton.tsx
|
|
277
277
|
import { Button as BaseButton } from "@base-ui/react/button";
|
|
278
278
|
|
|
279
|
+
// src/looks.ts
|
|
280
|
+
var FIELD_BOX_CLASSES = "bg-bg-field border border-border-field shadow-field hover:border-border-strong group-hover/field:border-border-strong focus:border-border-focus group-hover/field:focus:border-border-focus rounded-lg";
|
|
281
|
+
var FIELD_TEXT_CLASSES = "text-text-primary placeholder:text-text-muted";
|
|
282
|
+
var FIELD_DISABLED_CLASSES = "disabled:pointer-events-none disabled:bg-bg-disabled disabled:text-text-disabled disabled:border-border-field group-hover/field:disabled:border-border-field";
|
|
283
|
+
var FIELD_FOCUS_RING_CLASSES = "signal:transition-shadow signal:focus:shadow-focus-ring";
|
|
284
|
+
var FIELD_SIZE_CLASSES = {
|
|
285
|
+
default: "px-3 py-2 text-input-value",
|
|
286
|
+
small: "h-6 min-h-6 px-2 text-caption"
|
|
287
|
+
};
|
|
288
|
+
var FIELD_SHELL_CLASSES = "bg-bg-field border border-border-field shadow-field hover:border-border-strong group-hover/field:border-border-strong rounded-lg transition-colors";
|
|
289
|
+
var FLOATING_SURFACE_CLASSES = "rounded-lg border border-border-default bg-bg-elevated shadow-lg";
|
|
290
|
+
var CHIP_TEXT_CLASSES = "text-chip signal:font-mono signal:text-small signal:font-semibold signal:tabular-nums";
|
|
291
|
+
var BARE_INPUT_CLASSES = "bg-transparent text-text-primary placeholder:text-text-muted outline-none";
|
|
292
|
+
var MUTED_CONTROL_CLASSES = "text-text-secondary hover:bg-bg-hover hover:text-text-primary";
|
|
293
|
+
var MENU_TRIGGER_CLASSES = "cursor-pointer rounded-md transition-colors hover:bg-bg-hover data-[popup-open]:bg-bg-hover";
|
|
294
|
+
var SIDEBAR_ROW_CLASSES = "rounded-lg transition-colors";
|
|
295
|
+
var SIDEBAR_ROW_TEXT_CLASSES = "text-text-secondary hover:text-text-primary";
|
|
296
|
+
var COLLAPSIBLE_PANEL_CLASSES = "h-[var(--collapsible-panel-height)] overflow-hidden transition-[height] duration-150 ease-out motion-reduce:transition-none data-[starting-style]:h-0 data-[ending-style]:h-0";
|
|
297
|
+
|
|
279
298
|
// src/formBusy.ts
|
|
280
299
|
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
281
300
|
var FormBusyContext = createContext2(false);
|
|
@@ -306,23 +325,6 @@ function Kbd({ children, className, tone = "default" }) {
|
|
|
306
325
|
);
|
|
307
326
|
}
|
|
308
327
|
|
|
309
|
-
// src/looks.ts
|
|
310
|
-
var FIELD_BOX_CLASSES = "bg-bg-field border border-border-field shadow-field hover:border-border-strong group-hover/field:border-border-strong focus:border-border-focus group-hover/field:focus:border-border-focus rounded-lg";
|
|
311
|
-
var FIELD_TEXT_CLASSES = "text-text-primary placeholder:text-text-muted";
|
|
312
|
-
var FIELD_DISABLED_CLASSES = "disabled:pointer-events-none disabled:bg-bg-disabled disabled:text-text-disabled disabled:border-border-field group-hover/field:disabled:border-border-field";
|
|
313
|
-
var FIELD_FOCUS_RING_CLASSES = "signal:transition-shadow signal:focus:shadow-focus-ring";
|
|
314
|
-
var FIELD_SIZE_CLASSES = {
|
|
315
|
-
default: "px-3 py-2 text-input-value",
|
|
316
|
-
small: "h-6 min-h-6 px-2 text-caption"
|
|
317
|
-
};
|
|
318
|
-
var FIELD_SHELL_CLASSES = "bg-bg-field border border-border-field shadow-field hover:border-border-strong group-hover/field:border-border-strong rounded-lg transition-colors";
|
|
319
|
-
var FLOATING_SURFACE_CLASSES = "rounded-lg border border-border-default bg-bg-elevated shadow-lg";
|
|
320
|
-
var CHIP_TEXT_CLASSES = "text-chip signal:font-mono signal:text-small signal:font-semibold signal:tabular-nums";
|
|
321
|
-
var BARE_INPUT_CLASSES = "bg-transparent text-text-primary placeholder:text-text-muted outline-none";
|
|
322
|
-
var SIDEBAR_ROW_CLASSES = "rounded-lg transition-colors";
|
|
323
|
-
var SIDEBAR_ROW_TEXT_CLASSES = "text-text-secondary hover:text-text-primary";
|
|
324
|
-
var COLLAPSIBLE_PANEL_CLASSES = "h-[var(--collapsible-panel-height)] overflow-hidden transition-[height] duration-150 ease-out motion-reduce:transition-none data-[starting-style]:h-0 data-[ending-style]:h-0";
|
|
325
|
-
|
|
326
328
|
// src/Tooltip.tsx
|
|
327
329
|
import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
328
330
|
var OPEN_DELAY = 300;
|
|
@@ -410,7 +412,7 @@ function IconButton({
|
|
|
410
412
|
// stretched and carries no `self-center` — see the note there.
|
|
411
413
|
"flex items-center justify-center p-1 rounded-lg transition-colors shrink-0 self-center cursor-pointer",
|
|
412
414
|
!off && variant === "primary" && "bg-accent-primary hover:bg-accent-hover text-text-inverse",
|
|
413
|
-
!off && (variant === "muted" || variant === "resolve") &&
|
|
415
|
+
!off && (variant === "muted" || variant === "resolve") && MUTED_CONTROL_CLASSES,
|
|
414
416
|
!off && variant === "outlined" && "border border-border-default hover:bg-bg-hover text-text-secondary",
|
|
415
417
|
!off && variant === "current" && "hover:bg-bg-hover",
|
|
416
418
|
off && variant === "primary" && "bg-bg-disabled text-text-disabled",
|
|
@@ -1008,9 +1010,12 @@ function SectionLabel({
|
|
|
1008
1010
|
"span",
|
|
1009
1011
|
{
|
|
1010
1012
|
className: cn(
|
|
1011
|
-
"text-h5 leading-3
|
|
1012
|
-
//
|
|
1013
|
-
//
|
|
1013
|
+
"text-h5 leading-3",
|
|
1014
|
+
// Ship's type in every theme but Leaf, whose section labels are the
|
|
1015
|
+
// micro-label (Katerina, 28 September) unless the group folds (2
|
|
1016
|
+
// October). Signal dropped its micro-label on 5 October: every one of
|
|
1017
|
+
// Peek's section labels is Ship's type, folding or not, so Desk's
|
|
1018
|
+
// Screener and its Urgent and Open work read as one.
|
|
1014
1019
|
!folds && "leaf:font-mono leaf:text-small leaf:uppercase leaf:tracking-widest",
|
|
1015
1020
|
tone === "secondary" ? "text-text-secondary" : tone === "muted" ? "text-text-muted" : "text-text-primary",
|
|
1016
1021
|
truncate && "min-w-0 truncate",
|
|
@@ -1029,7 +1034,8 @@ function MenuPanel({ children, className, ...props }) {
|
|
|
1029
1034
|
"div",
|
|
1030
1035
|
{
|
|
1031
1036
|
className: cn(
|
|
1032
|
-
|
|
1037
|
+
// 2px between rows, as a list column's and a sidebar's (Katerina, 5 October).
|
|
1038
|
+
"flex flex-col gap-0.5 p-2",
|
|
1033
1039
|
FLOATING_SURFACE_CLASSES,
|
|
1034
1040
|
/*
|
|
1035
1041
|
* A divider in a menu runs the width of the rows it separates.
|
|
@@ -1088,7 +1094,7 @@ function Menu({ trigger, align = "left", openOnHover = false, open, onOpenChange
|
|
|
1088
1094
|
"data-interactive": true,
|
|
1089
1095
|
className: cn("min-w-[180px] outline-none p-0", className),
|
|
1090
1096
|
render: /* @__PURE__ */ jsx18(MenuPanel, {}),
|
|
1091
|
-
children: /* @__PURE__ */ jsx18(ScrollArea, { viewportClassName: "max-h-[var(--available-height)]", contentClassName: cn("flex flex-col p-2", contentClassName), children: /* @__PURE__ */ jsx18(MenuContext.Provider, { value: { openOnHover }, children: /* @__PURE__ */ jsx18(DividerInPanel.Provider, { value: true, children }) }) })
|
|
1097
|
+
children: /* @__PURE__ */ jsx18(ScrollArea, { viewportClassName: "max-h-[var(--available-height)]", contentClassName: cn("flex flex-col gap-0.5 p-2", contentClassName), children: /* @__PURE__ */ jsx18(MenuContext.Provider, { value: { openOnHover }, children: /* @__PURE__ */ jsx18(DividerInPanel.Provider, { value: true, children }) }) })
|
|
1092
1098
|
}
|
|
1093
1099
|
)
|
|
1094
1100
|
}
|
|
@@ -1219,12 +1225,12 @@ function MenuSection({ label, children, className }) {
|
|
|
1219
1225
|
const inMenu = useContext4(MenuContext) !== null;
|
|
1220
1226
|
const heading = /* @__PURE__ */ jsx18("div", { className: cn("flex h-8 items-center px-2", className), children: /* @__PURE__ */ jsx18(SectionLabel, { tone: "secondary", children: label }) });
|
|
1221
1227
|
if (!inMenu) {
|
|
1222
|
-
return /* @__PURE__ */ jsxs11("div", { className: "flex flex-col", children: [
|
|
1228
|
+
return /* @__PURE__ */ jsxs11("div", { className: "flex flex-col gap-0.5", children: [
|
|
1223
1229
|
heading,
|
|
1224
1230
|
children
|
|
1225
1231
|
] });
|
|
1226
1232
|
}
|
|
1227
|
-
return /* @__PURE__ */ jsxs11(BaseMenu.Group, { className: "flex flex-col", children: [
|
|
1233
|
+
return /* @__PURE__ */ jsxs11(BaseMenu.Group, { className: "flex flex-col gap-0.5", children: [
|
|
1228
1234
|
/* @__PURE__ */ jsx18(BaseMenu.GroupLabel, { render: heading }),
|
|
1229
1235
|
children
|
|
1230
1236
|
] });
|
|
@@ -2304,13 +2310,13 @@ function PersonTrigger({ name, picture, fallback, size, open = false, compact =
|
|
|
2304
2310
|
"aria-haspopup": "menu",
|
|
2305
2311
|
"aria-expanded": open,
|
|
2306
2312
|
className: cn(
|
|
2307
|
-
"flex h-8 min-h-8
|
|
2313
|
+
"flex h-8 min-h-8 items-center gap-1.5 pl-1.5 pr-1.5 text-body-2 text-text-primary",
|
|
2314
|
+
MENU_TRIGGER_CLASSES,
|
|
2308
2315
|
open && "bg-bg-hover",
|
|
2309
2316
|
// Under a `Menu` the open state is Base UI's, not a prop: the trigger
|
|
2310
|
-
// carries `data-popup-open` while its menu is up
|
|
2311
|
-
//
|
|
2312
|
-
//
|
|
2313
|
-
"data-[popup-open]:bg-bg-hover",
|
|
2317
|
+
// carries `data-popup-open` while its menu is up (MENU_TRIGGER_CLASSES
|
|
2318
|
+
// holds the fill for it), and sets its own `aria-expanded`; `open`
|
|
2319
|
+
// holds it for a caller that drives it.
|
|
2314
2320
|
className
|
|
2315
2321
|
),
|
|
2316
2322
|
...props,
|
|
@@ -2615,16 +2621,30 @@ import { IconX as IconX5 } from "@tabler/icons-react";
|
|
|
2615
2621
|
|
|
2616
2622
|
// src/ContainerHeader.tsx
|
|
2617
2623
|
import { IconChevronDown as IconChevronDown3 } from "@tabler/icons-react";
|
|
2624
|
+
import { Button as BaseButton6 } from "@base-ui/react/button";
|
|
2618
2625
|
import { jsx as jsx37, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
2619
|
-
function ContainerHeader({ title, chevron = false, actions, className }) {
|
|
2626
|
+
function ContainerHeader({ title, chevron = false, titleMenu, actions, className }) {
|
|
2620
2627
|
const text = typeof title === "string";
|
|
2621
|
-
|
|
2622
|
-
/* @__PURE__ */ jsx37("
|
|
2623
|
-
|
|
2624
|
-
chevron && /* @__PURE__ */ jsx37(IconChevronDown3, { size: 12, stroke: 1.5, className: "shrink-0 text-text-secondary" })
|
|
2625
|
-
] }) }),
|
|
2626
|
-
actions && /* @__PURE__ */ jsx37("div", { className: "flex shrink-0 items-center justify-end gap-3", children: /* @__PURE__ */ jsx37("div", { className: "flex shrink-0 items-center gap-1", children: actions }) })
|
|
2628
|
+
const name = /* @__PURE__ */ jsxs26("div", { className: cn("flex items-center gap-1", text ? "shrink-0" : "min-w-0 flex-1"), children: [
|
|
2629
|
+
text ? /* @__PURE__ */ jsx37("span", { className: "whitespace-nowrap text-body-2-strong text-text-primary", children: title }) : /* @__PURE__ */ jsx37("div", { className: "min-w-0 flex-1", children: title }),
|
|
2630
|
+
(chevron || titleMenu != null) && /* @__PURE__ */ jsx37(IconChevronDown3, { size: 12, stroke: 1.5, className: "shrink-0 text-text-secondary" })
|
|
2627
2631
|
] });
|
|
2632
|
+
return (
|
|
2633
|
+
// With a title menu the bar gives the button 6px of its left padding
|
|
2634
|
+
// (pl-3.5 + the button's px-1.5 = pl-5): the words stay where a title's
|
|
2635
|
+
// are, and the fill is inside the bar — a negative margin had it clipped
|
|
2636
|
+
// (Katerina, 5 October).
|
|
2637
|
+
/* @__PURE__ */ jsxs26("div", { className: cn("flex h-12 shrink-0 items-center justify-between overflow-hidden border-b border-border-subtle py-2 pr-4", titleMenu != null ? "pl-3.5" : "pl-5", className), children: [
|
|
2638
|
+
/* @__PURE__ */ jsx37("div", { className: cn("flex items-center gap-2 overflow-hidden", text ? "shrink-0" : "min-w-0 flex-1"), children: titleMenu != null ? /* @__PURE__ */ jsx37(
|
|
2639
|
+
Menu,
|
|
2640
|
+
{
|
|
2641
|
+
trigger: /* @__PURE__ */ jsx37(BaseButton6, { type: "button", className: cn("flex min-w-0 items-center px-1.5 py-1", MENU_TRIGGER_CLASSES), children: name }),
|
|
2642
|
+
children: titleMenu
|
|
2643
|
+
}
|
|
2644
|
+
) : name }),
|
|
2645
|
+
actions && /* @__PURE__ */ jsx37("div", { className: "flex shrink-0 items-center justify-end gap-3", children: /* @__PURE__ */ jsx37("div", { className: "flex shrink-0 items-center gap-1", children: actions }) })
|
|
2646
|
+
] })
|
|
2647
|
+
);
|
|
2628
2648
|
}
|
|
2629
2649
|
|
|
2630
2650
|
// src/DialogShell.tsx
|
|
@@ -2979,7 +2999,7 @@ function NavItem({
|
|
|
2979
2999
|
};
|
|
2980
3000
|
const besides = menu != null || hasRows;
|
|
2981
3001
|
const mark = unread ? urgent ? /* @__PURE__ */ jsx45(UrgentMark, {}) : /* @__PURE__ */ jsx45(UnreadDot, {}) : null;
|
|
2982
|
-
const stepsAside = menu != null && "transition-opacity group-hover/nav:opacity-0 group-focus-
|
|
3002
|
+
const stepsAside = menu != null && "transition-opacity group-hover/nav:opacity-0 group-has-[:focus-visible]/nav:opacity-0 group-has-[[data-nav-menu]_[aria-expanded=true]]/nav:opacity-0";
|
|
2983
3003
|
const row = /* @__PURE__ */ jsxs30(
|
|
2984
3004
|
"a",
|
|
2985
3005
|
{
|
|
@@ -3007,14 +3027,14 @@ function NavItem({
|
|
|
3007
3027
|
className: cn(
|
|
3008
3028
|
// With rows under it the slot is kept even with no icon: the arrow lands in it.
|
|
3009
3029
|
"flex min-w-4 shrink-0 items-center",
|
|
3010
|
-
hasRows && "transition-opacity group-hover/nav:opacity-0 group-focus-
|
|
3030
|
+
hasRows && "transition-opacity group-hover/nav:opacity-0 group-has-[:focus-visible]/nav:opacity-0"
|
|
3011
3031
|
),
|
|
3012
3032
|
children: icon
|
|
3013
3033
|
}
|
|
3014
3034
|
),
|
|
3015
3035
|
/* @__PURE__ */ jsx45("span", { className: cn("flex-1 truncate", unread && "font-medium"), children: label }),
|
|
3016
3036
|
hint != null && // Takes room only while it shows, so the label gives way to it.
|
|
3017
|
-
/* @__PURE__ */ jsx45("span", { className: "hidden shrink-0 items-center group-hover/nav:flex group-focus-
|
|
3037
|
+
/* @__PURE__ */ jsx45("span", { className: "hidden shrink-0 items-center group-hover/nav:flex group-has-[:focus-visible]/nav:flex", children: hint }),
|
|
3018
3038
|
count ? /* @__PURE__ */ jsx45(WithTooltip, { label: countLabel ?? `${count} open`, children: /* @__PURE__ */ jsx45(
|
|
3019
3039
|
"span",
|
|
3020
3040
|
{
|
|
@@ -3026,11 +3046,16 @@ function NavItem({
|
|
|
3026
3046
|
children: count
|
|
3027
3047
|
}
|
|
3028
3048
|
) }) : null,
|
|
3029
|
-
mark ?
|
|
3049
|
+
mark ? (
|
|
3050
|
+
// `-mr-1`: the 24px slot's dot then sits on the axis of a count and
|
|
3051
|
+
// of the ⋮ (8px from the edge to the 16px box), one column with a
|
|
3052
|
+
// folder heading's dot and ⋮ (Katerina, 5 October).
|
|
3053
|
+
/* @__PURE__ */ jsx45("span", { className: cn("-mr-1 flex shrink-0", stepsAside), children: mark })
|
|
3054
|
+
) : menu != null && !count ? (
|
|
3030
3055
|
// Nothing at the right, but a menu: no room is kept at rest, so the
|
|
3031
3056
|
// label has the row's whole width; while the ⋮ shows, its 24px slot
|
|
3032
3057
|
// is kept, so a long label ends before it instead of running under it.
|
|
3033
|
-
/* @__PURE__ */ jsx45("span", { "aria-hidden": "true", className: "hidden w-6 shrink-0 group-hover/nav:block group-focus-
|
|
3058
|
+
/* @__PURE__ */ jsx45("span", { "aria-hidden": "true", className: "hidden w-6 shrink-0 group-hover/nav:block group-has-[:focus-visible]/nav:block group-has-[[data-nav-menu]_[aria-expanded=true]]/nav:block" })
|
|
3034
3059
|
) : null
|
|
3035
3060
|
]
|
|
3036
3061
|
}
|
|
@@ -3041,7 +3066,7 @@ function NavItem({
|
|
|
3041
3066
|
row,
|
|
3042
3067
|
hasRows && // `left-1`: the 24px button's 16px arrow then sits where the icon
|
|
3043
3068
|
// sits (the row's px-2), so the icon turns into the arrow in place.
|
|
3044
|
-
/* @__PURE__ */ jsx45("div", { className: "absolute inset-y-0 left-1 flex items-center opacity-0 transition-opacity
|
|
3069
|
+
/* @__PURE__ */ jsx45("div", { className: "absolute inset-y-0 left-1 flex items-center opacity-0 transition-opacity group-hover/nav:opacity-100 group-has-[:focus-visible]/nav:opacity-100", children: /* @__PURE__ */ jsx45(
|
|
3045
3070
|
IconButton,
|
|
3046
3071
|
{
|
|
3047
3072
|
tooltip: open ? "Collapse" : "Expand",
|
|
@@ -3052,11 +3077,13 @@ function NavItem({
|
|
|
3052
3077
|
children: /* @__PURE__ */ jsx45(IconChevronRight2, { size: 16, stroke: 1.5, className: cn("transition-transform duration-150", open && "rotate-90") })
|
|
3053
3078
|
}
|
|
3054
3079
|
) }),
|
|
3055
|
-
menu != null && //
|
|
3056
|
-
//
|
|
3057
|
-
//
|
|
3058
|
-
//
|
|
3059
|
-
|
|
3080
|
+
menu != null && // `right-1`: the 24px button's 16px icon ends 8px from the row's
|
|
3081
|
+
// edge, where a count ends (px-2) — and where a SectionHeader's
|
|
3082
|
+
// actions and ⋮ end, so a folder's ⋮ and its rows' ⋮ are one column
|
|
3083
|
+
// (Katerina, 5 October: the rows move to the heading, not the
|
|
3084
|
+
// heading to the rows). Over a dot or the empty slot it sits 4px
|
|
3085
|
+
// right of the slot's centre.
|
|
3086
|
+
/* @__PURE__ */ jsx45("div", { "data-nav-menu": true, className: cn("absolute inset-y-0 right-1 flex items-center", "opacity-0 transition-opacity group-hover/nav:opacity-100 group-has-[:focus-visible]/nav:opacity-100 has-[[aria-expanded=true]]:opacity-100"), children: /* @__PURE__ */ jsx45(
|
|
3060
3087
|
Menu,
|
|
3061
3088
|
{
|
|
3062
3089
|
align: "right",
|
|
@@ -3074,7 +3101,7 @@ function NavItem({
|
|
|
3074
3101
|
id: panelId,
|
|
3075
3102
|
hiddenUntilFound: true,
|
|
3076
3103
|
className: COLLAPSIBLE_PANEL_CLASSES,
|
|
3077
|
-
children: /* @__PURE__ */ jsx45("div", { className: "flex flex-col gap-
|
|
3104
|
+
children: /* @__PURE__ */ jsx45("div", { className: "flex flex-col gap-0.5 pt-0.5 pl-6", children })
|
|
3078
3105
|
}
|
|
3079
3106
|
)
|
|
3080
3107
|
] });
|
|
@@ -3091,23 +3118,39 @@ import { useState as useState9 } from "react";
|
|
|
3091
3118
|
import { Collapsible as Collapsible2 } from "@base-ui/react/collapsible";
|
|
3092
3119
|
|
|
3093
3120
|
// src/SectionHeader.tsx
|
|
3094
|
-
import {
|
|
3121
|
+
import { isValidElement as isValidElement2 } from "react";
|
|
3122
|
+
import { IconChevronRight as IconChevronRight3, IconDotsVertical as IconDotsVertical2 } from "@tabler/icons-react";
|
|
3095
3123
|
import { useRender } from "@base-ui/react/use-render";
|
|
3096
3124
|
import { Fragment as Fragment11, jsx as jsx46, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
3097
|
-
function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, trailing, actions, showActions = "hover", hover = "fill", look = "heading", render, className }) {
|
|
3125
|
+
function SectionHeader({ title, chevron = false, icon, isExpanded = true, onToggle, trailing, actions, menu, menuLabel, showActions = "hover", hover = "fill", look = "heading", render, className }) {
|
|
3098
3126
|
const row = look === "row";
|
|
3127
|
+
const acts = actions && actions.length > 0 || menu != null;
|
|
3128
|
+
const dot = isValidElement2(trailing) && trailing.type === UnreadDot;
|
|
3129
|
+
const shared = acts && showActions === "hover" && dot;
|
|
3130
|
+
const reveal = showActions === "hover" && "opacity-0 transition-opacity group-hover:opacity-100 has-[:focus-visible]:opacity-100 has-[[aria-expanded=true]]:opacity-100";
|
|
3099
3131
|
const titleElement = useRender({
|
|
3100
3132
|
render: render ?? (chevron ? /* @__PURE__ */ jsx46("button", { type: "button", onClick: onToggle, "aria-expanded": isExpanded }) : /* @__PURE__ */ jsx46("span", {})),
|
|
3101
3133
|
props: {
|
|
3102
3134
|
// `text-left`: a button centres its text. `h-full` and `flex-1`: the
|
|
3103
3135
|
// whole row up to the actions is the hit target, as it was when the
|
|
3104
3136
|
// row itself carried the click.
|
|
3105
|
-
|
|
3137
|
+
// An icon is a row's: NavItem's 8px after it.
|
|
3138
|
+
className: cn("flex h-full min-w-0 flex-1 items-center text-left", row || chevron || icon != null ? "gap-2" : "gap-1"),
|
|
3106
3139
|
children: /* @__PURE__ */ jsxs31(Fragment11, { children: [
|
|
3107
|
-
|
|
3140
|
+
icon != null ? /* @__PURE__ */ jsxs31("span", { className: "relative flex size-4 shrink-0 items-center justify-center text-text-secondary", children: [
|
|
3141
|
+
/* @__PURE__ */ jsx46("span", { className: cn("flex", chevron && "transition-opacity group-hover:opacity-0 group-has-[:focus-visible]:opacity-0"), children: icon }),
|
|
3142
|
+
chevron && /* @__PURE__ */ jsx46(
|
|
3143
|
+
IconChevronRight3,
|
|
3144
|
+
{
|
|
3145
|
+
size: 16,
|
|
3146
|
+
stroke: 1.5,
|
|
3147
|
+
className: cn("absolute opacity-0 transition-[opacity,transform] duration-150 group-hover:opacity-100 group-has-[:focus-visible]:opacity-100", isExpanded && "rotate-90")
|
|
3148
|
+
}
|
|
3149
|
+
)
|
|
3150
|
+
] }) : chevron && /* @__PURE__ */ jsx46(
|
|
3108
3151
|
IconChevronRight3,
|
|
3109
3152
|
{
|
|
3110
|
-
size:
|
|
3153
|
+
size: 16,
|
|
3111
3154
|
stroke: 1.5,
|
|
3112
3155
|
className: cn("shrink-0 transition-transform duration-150", !row && "text-text-secondary", isExpanded && "rotate-90")
|
|
3113
3156
|
}
|
|
@@ -3131,20 +3174,37 @@ function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, tr
|
|
|
3131
3174
|
// lights up, a fixed heading over rows does not (2026-09-09, the
|
|
3132
3175
|
// Sidebar's fixed group).
|
|
3133
3176
|
// A quiet label never lights up: it is a label; its button does.
|
|
3134
|
-
hover === "fill" && look !== "quiet" && (chevron ||
|
|
3177
|
+
hover === "fill" && look !== "quiet" && (chevron || acts) && "hover:bg-bg-hover",
|
|
3178
|
+
// While its menu is open the pointer is on the menu, not the row: the
|
|
3179
|
+
// row keeps its hover look, as a NavItem's does.
|
|
3180
|
+
hover === "fill" && look !== "quiet" && menu != null && "has-[[data-section-menu]_[aria-expanded=true]]:bg-bg-hover",
|
|
3135
3181
|
className
|
|
3136
3182
|
),
|
|
3137
3183
|
children: [
|
|
3138
3184
|
titleElement,
|
|
3139
|
-
trailing != null &&
|
|
3140
|
-
|
|
3185
|
+
trailing != null && !shared && // A dot with no buttons to share a place with still sits on their
|
|
3186
|
+
// axis — the rows' dots and ⋮ (`-mr-1`, as the buttons are pulled).
|
|
3187
|
+
/* @__PURE__ */ jsx46("div", { className: cn("flex shrink-0 items-center", dot && !acts && "-mr-1"), children: trailing }),
|
|
3188
|
+
acts && /* @__PURE__ */ jsxs31(
|
|
3141
3189
|
"div",
|
|
3142
3190
|
{
|
|
3143
|
-
className:
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3191
|
+
className: "group/acts relative -mr-1 flex shrink-0 items-center",
|
|
3192
|
+
children: [
|
|
3193
|
+
/* @__PURE__ */ jsxs31("div", { className: cn("flex items-center gap-1", reveal), children: [
|
|
3194
|
+
actions?.map((action) => /* @__PURE__ */ jsx46(IconButton, { tooltip: action.tooltip, "aria-label": action.tooltip, onClick: action.onClick, children: action.icon }, action.tooltip)),
|
|
3195
|
+
menu != null && /* @__PURE__ */ jsx46("div", { "data-section-menu": true, className: "flex items-center", children: /* @__PURE__ */ jsx46(
|
|
3196
|
+
Menu,
|
|
3197
|
+
{
|
|
3198
|
+
align: "right",
|
|
3199
|
+
trigger: /* @__PURE__ */ jsx46(IconButton, { tooltip: "More options", "aria-label": menuLabel ?? `More options for ${title}`, children: /* @__PURE__ */ jsx46(IconDotsVertical2, { size: 16, stroke: 1.5 }) }),
|
|
3200
|
+
children: menu
|
|
3201
|
+
}
|
|
3202
|
+
) })
|
|
3203
|
+
] }),
|
|
3204
|
+
shared && // The dot in the last button's 24px place, on the axis of its icon;
|
|
3205
|
+
// gone while the buttons show.
|
|
3206
|
+
/* @__PURE__ */ jsx46("div", { className: "pointer-events-none absolute inset-y-0 right-0 flex w-6 items-center justify-center transition-opacity group-hover:opacity-0 group-has-[:focus-visible]/acts:opacity-0 group-has-[[aria-expanded=true]]/acts:opacity-0", children: trailing })
|
|
3207
|
+
]
|
|
3148
3208
|
}
|
|
3149
3209
|
)
|
|
3150
3210
|
]
|
|
@@ -3172,7 +3232,7 @@ function writeStored(key, open) {
|
|
|
3172
3232
|
} catch {
|
|
3173
3233
|
}
|
|
3174
3234
|
}
|
|
3175
|
-
function CollapsibleSection({ title, defaultOpen = true, open: openProp, onOpenChange, storageKey, trailing, actions, showActions, look = "heading", indent = false, children, className, contentClassName }) {
|
|
3235
|
+
function CollapsibleSection({ title, defaultOpen = true, open: openProp, onOpenChange, storageKey, icon, trailing, actions, menu, menuLabel, showActions, look = "heading", indent = false, children, className, contentClassName }) {
|
|
3176
3236
|
const [openState, setOpenState] = useState9(() => readStored(storageKey) ?? defaultOpen);
|
|
3177
3237
|
const open = openProp ?? openState;
|
|
3178
3238
|
const setOpen = (next) => {
|
|
@@ -3181,13 +3241,13 @@ function CollapsibleSection({ title, defaultOpen = true, open: openProp, onOpenC
|
|
|
3181
3241
|
onOpenChange?.(next);
|
|
3182
3242
|
};
|
|
3183
3243
|
return /* @__PURE__ */ jsxs32(Collapsible2.Root, { open, onOpenChange: setOpen, className: cn("flex flex-col", className), children: [
|
|
3184
|
-
/* @__PURE__ */ jsx47(SectionHeader, { title, chevron: true, isExpanded: open, trailing, actions, showActions, look, className: "shrink-0", render: /* @__PURE__ */ jsx47(Collapsible2.Trigger, {}) }),
|
|
3244
|
+
/* @__PURE__ */ jsx47(SectionHeader, { title, chevron: true, icon, isExpanded: open, trailing, actions, menu, menuLabel, showActions, look, className: "shrink-0", render: /* @__PURE__ */ jsx47(Collapsible2.Trigger, {}) }),
|
|
3185
3245
|
/* @__PURE__ */ jsx47(
|
|
3186
3246
|
Collapsible2.Panel,
|
|
3187
3247
|
{
|
|
3188
3248
|
hiddenUntilFound: true,
|
|
3189
3249
|
className: COLLAPSIBLE_PANEL_CLASSES,
|
|
3190
|
-
children: /* @__PURE__ */ jsx47("div", { className: cn("flex flex-col", look === "row"
|
|
3250
|
+
children: /* @__PURE__ */ jsx47("div", { className: cn("flex flex-col pt-0.5", (look === "row" || indent) && "pl-6", contentClassName), children })
|
|
3191
3251
|
}
|
|
3192
3252
|
)
|
|
3193
3253
|
] });
|
|
@@ -3235,6 +3295,8 @@ function Group({ group, selected, onSelect, storageKey }) {
|
|
|
3235
3295
|
loading: group.nodes === null,
|
|
3236
3296
|
trailing: group.trailing,
|
|
3237
3297
|
actions: group.actions,
|
|
3298
|
+
menu: group.menu,
|
|
3299
|
+
icon: group.icon,
|
|
3238
3300
|
defaultOpen: group.defaultOpen ?? holds(group.nodes, selected),
|
|
3239
3301
|
open: group.open,
|
|
3240
3302
|
onOpenChange: group.onOpenChange,
|
|
@@ -3244,14 +3306,14 @@ function Group({ group, selected, onSelect, storageKey }) {
|
|
|
3244
3306
|
}
|
|
3245
3307
|
);
|
|
3246
3308
|
}
|
|
3247
|
-
function NavTreeSection({ id, title, message, loading = false, trailing, actions, defaultOpen = true, open, onOpenChange, storageKey, onIntent, children }) {
|
|
3309
|
+
function NavTreeSection({ id, title, message, loading = false, trailing, actions, menu, icon, defaultOpen = true, open, onOpenChange, storageKey, onIntent, children }) {
|
|
3248
3310
|
const timer = useRef10(void 0);
|
|
3249
3311
|
useEffect5(() => () => clearTimeout(timer.current), []);
|
|
3250
3312
|
const intent = onIntent;
|
|
3251
3313
|
return /* @__PURE__ */ jsx48(
|
|
3252
3314
|
"div",
|
|
3253
3315
|
{
|
|
3254
|
-
className: "shrink-0",
|
|
3316
|
+
className: "mt-1 shrink-0 first:mt-0",
|
|
3255
3317
|
"data-nav-group": id,
|
|
3256
3318
|
onPointerEnter: intent ? () => {
|
|
3257
3319
|
clearTimeout(timer.current);
|
|
@@ -3270,8 +3332,10 @@ function NavTreeSection({ id, title, message, loading = false, trailing, actions
|
|
|
3270
3332
|
storageKey,
|
|
3271
3333
|
trailing,
|
|
3272
3334
|
actions,
|
|
3273
|
-
|
|
3274
|
-
|
|
3335
|
+
menu,
|
|
3336
|
+
icon,
|
|
3337
|
+
className: "shrink-0",
|
|
3338
|
+
contentClassName: "gap-0.5",
|
|
3275
3339
|
children: message !== void 0 ? /* @__PURE__ */ jsx48(Line, { message }) : loading ? /* @__PURE__ */ jsx48(SkeletonList, { rows: 3 }) : children
|
|
3276
3340
|
}
|
|
3277
3341
|
)
|
|
@@ -3702,7 +3766,7 @@ function Panel(props) {
|
|
|
3702
3766
|
|
|
3703
3767
|
// src/ListColumn.tsx
|
|
3704
3768
|
import { jsx as jsx60, jsxs as jsxs42 } from "react/jsx-runtime";
|
|
3705
|
-
function Column({ title, chevron = false, actions, above, collapsed = false, children, className, broken = false }) {
|
|
3769
|
+
function Column({ title, chevron = false, titleMenu, actions, above, collapsed = false, children, className, broken = false }) {
|
|
3706
3770
|
return /* @__PURE__ */ jsxs42(
|
|
3707
3771
|
"div",
|
|
3708
3772
|
{
|
|
@@ -3714,7 +3778,7 @@ function Column({ title, chevron = false, actions, above, collapsed = false, chi
|
|
|
3714
3778
|
),
|
|
3715
3779
|
"data-collapsed": collapsed || void 0,
|
|
3716
3780
|
children: [
|
|
3717
|
-
/* @__PURE__ */ jsx60(ContainerHeader, { title, chevron, actions }),
|
|
3781
|
+
/* @__PURE__ */ jsx60(ContainerHeader, { title, chevron, titleMenu, actions }),
|
|
3718
3782
|
above,
|
|
3719
3783
|
broken ? (
|
|
3720
3784
|
// The message takes the room the list had and centres itself in it (Katerina, 19 September).
|