@reopt-ai/opt-ui 1.8.0 → 1.10.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/COMPONENT_CATALOG.md +222 -34
- package/README.md +70 -6
- package/dist/core/index.cjs +170 -155
- package/dist/core/index.d.cts +3 -2
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.js +3 -3
- package/dist/docs/02-components/01-core.md +223 -18
- package/dist/docs/02-components/02-visuals.md +1 -1
- package/dist/docs/02-components/03-shells.md +17 -16
- package/dist/docs/02-components/04-surfaces.md +1 -1
- package/dist/docs/02-components/index.md +4 -4
- package/dist/{field-sidebar-Bqfp6dgg.cjs → field-sidebar-B6Ndr8HE.cjs} +304 -289
- package/dist/{field-sidebar-BYD_UIjG.js → field-sidebar-BXjvV7Cz.js} +25 -10
- package/dist/{field-sidebar-BYD_UIjG.js.map → field-sidebar-BXjvV7Cz.js.map} +1 -1
- package/dist/{field-sidebar-5KYVlpSs.d.ts → field-sidebar-DWfJAJlx.d.ts} +32 -3
- package/dist/{field-sidebar-5KYVlpSs.d.ts.map → field-sidebar-DWfJAJlx.d.ts.map} +1 -1
- package/dist/{field-sidebar-BP0iWkoV.d.cts → field-sidebar-nwcE9LC4.d.cts} +32 -3
- package/dist/{field-sidebar-BP0iWkoV.d.cts.map → field-sidebar-nwcE9LC4.d.cts.map} +1 -1
- package/dist/id-registry.cjs +98 -2
- package/dist/id-registry.d.cts.map +1 -1
- package/dist/id-registry.d.ts.map +1 -1
- package/dist/id-registry.js +98 -2
- package/dist/id-registry.js.map +1 -1
- package/dist/id-registry.json +202 -4
- package/dist/index.cjs +664 -643
- package/dist/index.d.cts +5 -4
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -4
- package/dist/index.js.map +1 -1
- package/dist/{shader-surface-BUPZ0P-n.js → key-pad-menu-BQunU20e.js} +727 -19
- package/dist/key-pad-menu-BQunU20e.js.map +1 -0
- package/dist/{shader-surface-BfTP9c1Q.d.ts → key-pad-menu-BsgJMjD4.d.ts} +415 -3
- package/dist/key-pad-menu-BsgJMjD4.d.ts.map +1 -0
- package/dist/{shader-surface-Fav5K9UC.cjs → key-pad-menu-D5mQcS5v.cjs} +969 -201
- package/dist/{shader-surface-DRrcLrWf.d.cts → key-pad-menu-DfZbeXhG.d.cts} +415 -3
- package/dist/key-pad-menu-DfZbeXhG.d.cts.map +1 -0
- package/dist/meta.cjs +641 -0
- package/dist/meta.d.cts.map +1 -1
- package/dist/meta.d.ts.map +1 -1
- package/dist/meta.js +641 -0
- package/dist/meta.js.map +1 -1
- package/dist/shells/index.cjs +1 -1
- package/dist/shells/index.d.cts +1 -1
- package/dist/shells/index.d.ts +1 -1
- package/dist/shells/index.js +1 -1
- package/dist/{types-D0FlcYnM.d.ts → text-truncate-BGr7bB_8.d.cts} +110 -2
- package/dist/text-truncate-BGr7bB_8.d.cts.map +1 -0
- package/dist/{types-D0FlcYnM.d.cts → text-truncate-BGr7bB_8.d.ts} +110 -2
- package/dist/text-truncate-BGr7bB_8.d.ts.map +1 -0
- package/dist/{pagination-DjfxBcjX.cjs → text-truncate-CxdzAnhS.cjs} +418 -8
- package/dist/{pagination-DWbvACuN.js → text-truncate-j6X3z7Pv.js} +392 -12
- package/dist/text-truncate-j6X3z7Pv.js.map +1 -0
- package/package.json +3 -3
- package/dist/pagination-DWbvACuN.js.map +0 -1
- package/dist/shader-surface-BUPZ0P-n.js.map +0 -1
- package/dist/shader-surface-BfTP9c1Q.d.ts.map +0 -1
- package/dist/shader-surface-DRrcLrWf.d.cts.map +0 -1
- package/dist/types-D0FlcYnM.d.cts.map +0 -1
- package/dist/types-D0FlcYnM.d.ts.map +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { Ft as SelectPopover$1, Gn as _objectSpread2, Hn as OPT_TEXT_SECONDARY, It as SelectRoot$1, Jn as cn, Kn as _objectWithoutProperties, Lt as SelectTrigger$1, Nt as SelectItem$1, On as OPT_FOCUS, Pn as OPT_SURFACE, Qt as useInputId, Un as OPT_TEXT_TERTIARY, Vn as OPT_TEXT_PRIMARY, bn as OPT_BORDER, cn as useControlledState, dn as getDescribedBy, dt as MenuRoot$1, en as Spinner, ft as MenuSeparator$1, in as koBreadcrumbLabels, jn as OPT_INPUT, kn as OPT_FOCUS_VISIBLE, ln as FieldMessages, mn as OPT_ANIMATE_ENTER_EXIT, pn as OPT_ANIMATE_DRAWER, pt as MenuTrigger$1, rn as Button, st as MenuItem$1, un as RequiredMark, ut as MenuPopover$1, zn as OPT_TEXTAREA_SIZE } from "./text-truncate-j6X3z7Pv.js";
|
|
3
3
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import { createContext, createElement, forwardRef, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
6
6
|
import { AccordionContent, AccordionItem, AccordionRoot, AccordionTrigger, DialogDismiss, DialogPanel, DialogRoot, Meter, RadioGroupRoot, Separator, Toggle, ToggleGroup, useDialogClose, useRadioGroupContext } from "@reopt-ai/opt-ui-primitives";
|
|
7
|
-
import { ChevronDown, GripVertical } from "lucide-react";
|
|
7
|
+
import { Check, ChevronDown, GripVertical, Pencil, X } from "lucide-react";
|
|
8
8
|
import { Group, Panel, Separator as Separator$1 } from "react-resizable-panels";
|
|
9
9
|
import { Mesh, Program, Renderer, Triangle } from "ogl";
|
|
10
10
|
import { hexToSrgb } from "@reopt-ai/opt-palette";
|
|
@@ -17,7 +17,7 @@ const _excluded$17 = [
|
|
|
17
17
|
"className",
|
|
18
18
|
"children"
|
|
19
19
|
];
|
|
20
|
-
const toneStyles = {
|
|
20
|
+
const toneStyles$1 = {
|
|
21
21
|
primary: "text-text-primary",
|
|
22
22
|
secondary: "text-text-secondary",
|
|
23
23
|
tertiary: "text-text-tertiary",
|
|
@@ -28,7 +28,7 @@ function Text(_ref) {
|
|
|
28
28
|
let { as, variant = "body", tone = "primary", truncate = false, className, children } = _ref, props = _objectWithoutProperties(_ref, _excluded$17);
|
|
29
29
|
const Component = as !== null && as !== void 0 ? as : "span";
|
|
30
30
|
const elementProps = _objectSpread2(_objectSpread2({}, props), {}, {
|
|
31
|
-
className: cn(`opt-type-${variant}`, toneStyles[tone], truncate && "truncate", className),
|
|
31
|
+
className: cn(`opt-type-${variant}`, toneStyles$1[tone], truncate && "truncate", className),
|
|
32
32
|
"data-opt-component": "text",
|
|
33
33
|
"data-opt-part": "root",
|
|
34
34
|
"data-opt-id": "NFKP2",
|
|
@@ -277,7 +277,7 @@ const _excluded2$8 = [
|
|
|
277
277
|
"children",
|
|
278
278
|
"orientation"
|
|
279
279
|
];
|
|
280
|
-
const sizeStyles$
|
|
280
|
+
const sizeStyles$8 = {
|
|
281
281
|
sm: "h-8 min-w-8 px-2 text-xs gap-1.5",
|
|
282
282
|
md: "h-10 min-w-10 px-3 text-sm gap-2"
|
|
283
283
|
};
|
|
@@ -285,7 +285,7 @@ const toggleBase = cn("inline-flex cursor-pointer items-center justify-center ro
|
|
|
285
285
|
/** A two-state (pressed) button. Use standalone or inside `ToggleGroup`. */
|
|
286
286
|
function Toggle$1(_ref) {
|
|
287
287
|
let { size = "md", className, children } = _ref, props = _objectWithoutProperties(_ref, _excluded$15);
|
|
288
|
-
return /* @__PURE__ */ jsx(Toggle, _objectSpread2(_objectSpread2({ className: cn(toggleBase, sizeStyles$
|
|
288
|
+
return /* @__PURE__ */ jsx(Toggle, _objectSpread2(_objectSpread2({ className: cn(toggleBase, sizeStyles$8[size], className) }, props), {}, {
|
|
289
289
|
"data-opt-id": "GFCMY",
|
|
290
290
|
"data-opt-slug": "toggle.Toggle",
|
|
291
291
|
children
|
|
@@ -559,7 +559,7 @@ const _excluded$12 = [
|
|
|
559
559
|
"aria-label",
|
|
560
560
|
"aria-labelledby"
|
|
561
561
|
];
|
|
562
|
-
const sizeStyles$
|
|
562
|
+
const sizeStyles$7 = {
|
|
563
563
|
sm: {
|
|
564
564
|
track: "h-5 w-9",
|
|
565
565
|
thumb: "h-4 w-4",
|
|
@@ -583,7 +583,7 @@ const Switch = forwardRef((_ref, ref) => {
|
|
|
583
583
|
const switchId = (_useInputId = useInputId(id, label, "switch")) !== null && _useInputId !== void 0 ? _useInputId : `switch-${fallbackId}`;
|
|
584
584
|
const hintText = hint !== null && hint !== void 0 ? hint : description;
|
|
585
585
|
const hasError = Boolean(error);
|
|
586
|
-
const styles = sizeStyles$
|
|
586
|
+
const styles = sizeStyles$7[size];
|
|
587
587
|
const labelId = label ? `${switchId}-label` : void 0;
|
|
588
588
|
const switchLabelledBy = ariaLabelledBy !== null && ariaLabelledBy !== void 0 ? ariaLabelledBy : ariaLabel ? void 0 : labelId;
|
|
589
589
|
const switchAriaLabel = ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : switchLabelledBy ? void 0 : "Switch";
|
|
@@ -889,7 +889,7 @@ function useImageLoadingStatus(src, { srcSet, sizes, crossOrigin, referrerPolicy
|
|
|
889
889
|
]);
|
|
890
890
|
return status;
|
|
891
891
|
}
|
|
892
|
-
const sizeStyles$
|
|
892
|
+
const sizeStyles$6 = {
|
|
893
893
|
xs: "h-6 w-6 text-[10px]",
|
|
894
894
|
sm: "h-8 w-8 text-xs",
|
|
895
895
|
md: "h-10 w-10 text-sm",
|
|
@@ -973,7 +973,7 @@ function Avatar(_ref) {
|
|
|
973
973
|
return /* @__PURE__ */ jsxs("span", _objectSpread2(_objectSpread2({
|
|
974
974
|
role: "img",
|
|
975
975
|
"aria-label": avatarLabel,
|
|
976
|
-
className: cn("relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full", sizeStyles$
|
|
976
|
+
className: cn("relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full", sizeStyles$6[size], !showImage && bgColor, className)
|
|
977
977
|
}, props), {}, {
|
|
978
978
|
"data-opt-id": "SCMM0",
|
|
979
979
|
"data-opt-slug": "avatar.Avatar",
|
|
@@ -996,7 +996,7 @@ function AvatarGroup(_ref5) {
|
|
|
996
996
|
className: "ring-surface relative -ml-2 inline-block rounded-full ring-2 first:ml-0",
|
|
997
997
|
children: child
|
|
998
998
|
}, index)), remainingCount > 0 && /* @__PURE__ */ jsxs("span", {
|
|
999
|
-
className: cn("bg-bg-subtle text-text-secondary ring-surface relative inline-flex items-center justify-center rounded-full font-medium ring-2", sizeStyles$
|
|
999
|
+
className: cn("bg-bg-subtle text-text-secondary ring-surface relative inline-flex items-center justify-center rounded-full font-medium ring-2", sizeStyles$6[size]),
|
|
1000
1000
|
children: ["+", remainingCount]
|
|
1001
1001
|
})]
|
|
1002
1002
|
}));
|
|
@@ -1025,7 +1025,7 @@ const _excluded2$3 = [
|
|
|
1025
1025
|
"indeterminate",
|
|
1026
1026
|
"className"
|
|
1027
1027
|
];
|
|
1028
|
-
const sizeStyles$
|
|
1028
|
+
const sizeStyles$5 = {
|
|
1029
1029
|
sm: "h-1",
|
|
1030
1030
|
md: "h-2",
|
|
1031
1031
|
lg: "h-3"
|
|
@@ -1065,7 +1065,7 @@ function Progress(_ref) {
|
|
|
1065
1065
|
children: progress.valueText
|
|
1066
1066
|
})]
|
|
1067
1067
|
}), /* @__PURE__ */ jsx("div", {
|
|
1068
|
-
className: cn("bg-bg-subtle w-full overflow-hidden rounded-full", sizeStyles$
|
|
1068
|
+
className: cn("bg-bg-subtle w-full overflow-hidden rounded-full", sizeStyles$5[size]),
|
|
1069
1069
|
role: "progressbar",
|
|
1070
1070
|
"aria-valuenow": progress === null || progress === void 0 ? void 0 : progress.clampedValue,
|
|
1071
1071
|
"aria-valuemin": progress ? 0 : void 0,
|
|
@@ -1158,7 +1158,7 @@ const _excluded$7 = [
|
|
|
1158
1158
|
"getValueLabel",
|
|
1159
1159
|
"className"
|
|
1160
1160
|
];
|
|
1161
|
-
const sizeStyles$
|
|
1161
|
+
const sizeStyles$4 = {
|
|
1162
1162
|
sm: "h-1",
|
|
1163
1163
|
md: "h-2",
|
|
1164
1164
|
lg: "h-3"
|
|
@@ -1196,7 +1196,7 @@ function Meter$1(_ref) {
|
|
|
1196
1196
|
max,
|
|
1197
1197
|
getValueLabel,
|
|
1198
1198
|
"aria-label": label !== null && label !== void 0 ? label : `${Math.round(percentage)}%`,
|
|
1199
|
-
className: cn("bg-bg-subtle w-full overflow-hidden rounded-full", sizeStyles$
|
|
1199
|
+
className: cn("bg-bg-subtle w-full overflow-hidden rounded-full", sizeStyles$4[size]),
|
|
1200
1200
|
children: /* @__PURE__ */ jsx("div", {
|
|
1201
1201
|
className: cn("h-full rounded-full transition-all duration-300 ease-out", variantStyles[variant]),
|
|
1202
1202
|
style: { width: `${percentage}%` }
|
|
@@ -1793,7 +1793,7 @@ const _excluded$5 = [
|
|
|
1793
1793
|
"disabled",
|
|
1794
1794
|
"className"
|
|
1795
1795
|
];
|
|
1796
|
-
const sizeStyles = {
|
|
1796
|
+
const sizeStyles$3 = {
|
|
1797
1797
|
sm: {
|
|
1798
1798
|
radio: "h-4 w-4",
|
|
1799
1799
|
dot: "h-2 w-2",
|
|
@@ -1861,7 +1861,7 @@ const Radio = forwardRef((_ref, ref) => {
|
|
|
1861
1861
|
const size = sizeProp !== null && sizeProp !== void 0 ? sizeProp : groupContext.size;
|
|
1862
1862
|
const disabled = (_ref2 = disabledProp !== null && disabledProp !== void 0 ? disabledProp : groupContext.disabled) !== null && _ref2 !== void 0 ? _ref2 : primContext === null || primContext === void 0 ? void 0 : primContext.disabled;
|
|
1863
1863
|
const hintText = hint !== null && hint !== void 0 ? hint : description;
|
|
1864
|
-
const styles = sizeStyles[size];
|
|
1864
|
+
const styles = sizeStyles$3[size];
|
|
1865
1865
|
const isChecked = (primContext === null || primContext === void 0 ? void 0 : primContext.value) === value;
|
|
1866
1866
|
const radioId = `radio-${useId().replace(/:/g, "")}`;
|
|
1867
1867
|
const labelId = label ? `${radioId}-label` : void 0;
|
|
@@ -3649,6 +3649,714 @@ const ShaderSurfaceComponent = forwardRef(function ShaderSurface({ preset, color
|
|
|
3649
3649
|
ShaderSurfaceComponent.displayName = "ShaderSurface";
|
|
3650
3650
|
const ShaderSurface = ShaderSurfaceComponent;
|
|
3651
3651
|
//#endregion
|
|
3652
|
-
|
|
3652
|
+
//#region src/core/facet.tsx
|
|
3653
|
+
const defaultLabels$1 = {
|
|
3654
|
+
empty: "No values in this range.",
|
|
3655
|
+
loading: "Counting…",
|
|
3656
|
+
showMore: (remaining) => `Show ${remaining} more`,
|
|
3657
|
+
showLess: "Show fewer",
|
|
3658
|
+
selectHint: (label, count, share) => `${label}, ${count} (${share})`
|
|
3659
|
+
};
|
|
3660
|
+
function defaultCountFormat(value) {
|
|
3661
|
+
return new Intl.NumberFormat(void 0, { notation: "compact" }).format(value);
|
|
3662
|
+
}
|
|
3663
|
+
function formatShare(share) {
|
|
3664
|
+
const percent = share * 100;
|
|
3665
|
+
if (percent > 0 && percent < .1) return "<0.1%";
|
|
3666
|
+
return `${percent >= 10 ? Math.round(percent) : percent.toFixed(1)}%`;
|
|
3667
|
+
}
|
|
3668
|
+
/**
|
|
3669
|
+
* Facet — one value of a field, its count, and its share of the whole.
|
|
3670
|
+
*
|
|
3671
|
+
* The row is the filter: reading it and acting on it are the same gesture,
|
|
3672
|
+
* which is what separates a facet from a legend.
|
|
3673
|
+
*/
|
|
3674
|
+
function Facet({ item, selected = false, onSelect, showShare = true, share, countFormatter = defaultCountFormat, layout = "vertical", labels, className }) {
|
|
3675
|
+
var _ref;
|
|
3676
|
+
const text = _objectSpread2(_objectSpread2({}, defaultLabels$1), labels);
|
|
3677
|
+
const resolvedShare = (_ref = share !== null && share !== void 0 ? share : item.share) !== null && _ref !== void 0 ? _ref : 0;
|
|
3678
|
+
const interactive = onSelect !== void 0 && !item.disabled;
|
|
3679
|
+
const countText = item.count === void 0 ? "" : countFormatter(item.count);
|
|
3680
|
+
const shareText = formatShare(resolvedShare);
|
|
3681
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3682
|
+
showShare && layout === "vertical" && /* @__PURE__ */ jsx("span", {
|
|
3683
|
+
"aria-hidden": "true",
|
|
3684
|
+
className: cn("absolute inset-y-0 left-0 rounded-[inherit] transition-[width] duration-300", selected ? "bg-accent/25" : "bg-accent/10"),
|
|
3685
|
+
style: {
|
|
3686
|
+
width: `${Math.max(resolvedShare * 100, resolvedShare > 0 ? 1.5 : 0)}%`,
|
|
3687
|
+
backgroundColor: item.color ? `color-mix(in oklab, ${item.color} ${selected ? 30 : 16}%, transparent)` : void 0
|
|
3688
|
+
}
|
|
3689
|
+
}),
|
|
3690
|
+
/* @__PURE__ */ jsxs("span", {
|
|
3691
|
+
className: "gap-element relative flex min-w-0 flex-1 items-center",
|
|
3692
|
+
children: [
|
|
3693
|
+
item.color && !item.icon && /* @__PURE__ */ jsx("span", {
|
|
3694
|
+
"aria-hidden": "true",
|
|
3695
|
+
className: "size-2 shrink-0 rounded-full",
|
|
3696
|
+
style: { backgroundColor: item.color }
|
|
3697
|
+
}),
|
|
3698
|
+
item.icon && /* @__PURE__ */ jsx("span", {
|
|
3699
|
+
"aria-hidden": "true",
|
|
3700
|
+
className: "shrink-0",
|
|
3701
|
+
children: item.icon
|
|
3702
|
+
}),
|
|
3703
|
+
/* @__PURE__ */ jsx("span", {
|
|
3704
|
+
className: "truncate",
|
|
3705
|
+
children: item.label
|
|
3706
|
+
})
|
|
3707
|
+
]
|
|
3708
|
+
}),
|
|
3709
|
+
item.count !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
3710
|
+
className: "text-text-tertiary relative shrink-0 tabular-nums",
|
|
3711
|
+
children: countText
|
|
3712
|
+
}),
|
|
3713
|
+
showShare && item.count !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
3714
|
+
className: "text-text-tertiary relative w-11 shrink-0 text-right text-xs tabular-nums",
|
|
3715
|
+
children: shareText
|
|
3716
|
+
})
|
|
3717
|
+
] });
|
|
3718
|
+
const shellClass = cn("relative flex w-full items-center gap-element overflow-hidden rounded-md px-2 py-1.5 text-left text-sm", layout === "horizontal" && "w-auto", selected ? "text-text-primary font-medium" : "text-text-secondary", interactive && "hover:bg-accent-subtle hover:text-text-primary cursor-pointer transition-colors", item.disabled && "cursor-not-allowed opacity-50", className);
|
|
3719
|
+
if (!interactive) return /* @__PURE__ */ jsx("div", {
|
|
3720
|
+
className: shellClass,
|
|
3721
|
+
"data-selected": selected || void 0,
|
|
3722
|
+
"data-opt-id": "E9H18",
|
|
3723
|
+
"data-opt-slug": "facet.Facet",
|
|
3724
|
+
children: content
|
|
3725
|
+
});
|
|
3726
|
+
return /* @__PURE__ */ jsx("button", {
|
|
3727
|
+
type: "button",
|
|
3728
|
+
className: cn(shellClass, OPT_FOCUS_VISIBLE),
|
|
3729
|
+
"aria-pressed": selected,
|
|
3730
|
+
"aria-label": text.selectHint(typeof item.label === "string" ? item.label : item.id, countText, shareText),
|
|
3731
|
+
disabled: item.disabled,
|
|
3732
|
+
onClick: () => onSelect(item.id),
|
|
3733
|
+
"data-selected": selected || void 0,
|
|
3734
|
+
"data-opt-id": "E9H18",
|
|
3735
|
+
"data-opt-slug": "facet.Facet",
|
|
3736
|
+
children: content
|
|
3737
|
+
});
|
|
3738
|
+
}
|
|
3739
|
+
/**
|
|
3740
|
+
* FacetGroup — the distribution of one field, as a list you can filter with.
|
|
3741
|
+
*
|
|
3742
|
+
* Counts alone do not say whether 4,000 is most of the data or a rounding
|
|
3743
|
+
* error, so every row carries its share of the whole as a bar behind the
|
|
3744
|
+
* label. The tail collapses by default: a high-cardinality field would
|
|
3745
|
+
* otherwise push everything else off the screen.
|
|
3746
|
+
*
|
|
3747
|
+
* @example
|
|
3748
|
+
* ```tsx
|
|
3749
|
+
* <FacetGroup
|
|
3750
|
+
* items={topValues}
|
|
3751
|
+
* selected={filters}
|
|
3752
|
+
* onSelectedChange={setFilters}
|
|
3753
|
+
* total={sampledRows}
|
|
3754
|
+
* max={5}
|
|
3755
|
+
* />
|
|
3756
|
+
* ```
|
|
3757
|
+
*/
|
|
3758
|
+
function FacetGroup({ items, selected, onSelectedChange, selection = "multiple", layout = "vertical", showShare = true, countFormatter = defaultCountFormat, max, total, loading = false, emptyState, labels, className, "aria-label": ariaLabel }) {
|
|
3759
|
+
const text = _objectSpread2(_objectSpread2({}, defaultLabels$1), labels);
|
|
3760
|
+
const [uncontrolled, setUncontrolled] = useState([]);
|
|
3761
|
+
const [expanded, setExpanded] = useState(false);
|
|
3762
|
+
const current = selected !== null && selected !== void 0 ? selected : uncontrolled;
|
|
3763
|
+
const denominator = useMemo(() => {
|
|
3764
|
+
if (total !== void 0 && total > 0) return total;
|
|
3765
|
+
const sum = items.reduce((acc, item) => {
|
|
3766
|
+
var _item$count;
|
|
3767
|
+
return acc + ((_item$count = item.count) !== null && _item$count !== void 0 ? _item$count : 0);
|
|
3768
|
+
}, 0);
|
|
3769
|
+
return sum > 0 ? sum : 1;
|
|
3770
|
+
}, [items, total]);
|
|
3771
|
+
const visible = max === void 0 || expanded ? items : items.slice(0, Math.max(max, 1));
|
|
3772
|
+
const remaining = items.length - visible.length;
|
|
3773
|
+
const handleSelect = (id) => {
|
|
3774
|
+
if (selection === "none") return;
|
|
3775
|
+
const next = selection === "single" ? current.includes(id) ? [] : [id] : current.includes(id) ? current.filter((value) => value !== id) : [...current, id];
|
|
3776
|
+
if (selected === void 0) setUncontrolled(next);
|
|
3777
|
+
onSelectedChange === null || onSelectedChange === void 0 || onSelectedChange(next);
|
|
3778
|
+
};
|
|
3779
|
+
if (loading) return /* @__PURE__ */ jsxs("div", {
|
|
3780
|
+
className: cn("flex flex-col gap-element", className),
|
|
3781
|
+
"aria-busy": "true",
|
|
3782
|
+
"aria-label": ariaLabel,
|
|
3783
|
+
"data-opt-id": "G1RNN",
|
|
3784
|
+
"data-opt-slug": "facet.FacetGroup",
|
|
3785
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
3786
|
+
className: "sr-only",
|
|
3787
|
+
children: text.loading
|
|
3788
|
+
}), Array.from({ length: Math.min(max !== null && max !== void 0 ? max : 4, 6) }, (_, index) => /* @__PURE__ */ jsx("span", {
|
|
3789
|
+
"aria-hidden": "true",
|
|
3790
|
+
className: "bg-bg-subtle h-7 animate-pulse rounded-md"
|
|
3791
|
+
}, index))]
|
|
3792
|
+
});
|
|
3793
|
+
if (items.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
3794
|
+
className: cn("text-text-tertiary px-2 py-1.5 text-sm", className),
|
|
3795
|
+
"aria-label": ariaLabel,
|
|
3796
|
+
"data-opt-id": "G1RNN",
|
|
3797
|
+
"data-opt-slug": "facet.FacetGroup",
|
|
3798
|
+
children: emptyState !== null && emptyState !== void 0 ? emptyState : text.empty
|
|
3799
|
+
});
|
|
3800
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3801
|
+
className: cn(layout === "vertical" ? "flex flex-col gap-0.5" : "flex flex-wrap items-center gap-element", className),
|
|
3802
|
+
role: selection === "none" ? "list" : "group",
|
|
3803
|
+
"aria-label": ariaLabel,
|
|
3804
|
+
"data-opt-id": "G1RNN",
|
|
3805
|
+
"data-opt-slug": "facet.FacetGroup",
|
|
3806
|
+
children: [visible.map((item) => {
|
|
3807
|
+
var _item$share, _item$count2;
|
|
3808
|
+
return /* @__PURE__ */ jsx(Facet, {
|
|
3809
|
+
item,
|
|
3810
|
+
layout,
|
|
3811
|
+
selected: current.includes(item.id),
|
|
3812
|
+
onSelect: selection === "none" ? void 0 : handleSelect,
|
|
3813
|
+
showShare,
|
|
3814
|
+
share: (_item$share = item.share) !== null && _item$share !== void 0 ? _item$share : ((_item$count2 = item.count) !== null && _item$count2 !== void 0 ? _item$count2 : 0) / denominator,
|
|
3815
|
+
countFormatter,
|
|
3816
|
+
labels
|
|
3817
|
+
}, item.id);
|
|
3818
|
+
}), (remaining > 0 || expanded) && max !== void 0 && /* @__PURE__ */ jsx("button", {
|
|
3819
|
+
type: "button",
|
|
3820
|
+
className: cn("text-text-tertiary hover:text-text-primary rounded-md px-2 py-1 text-left text-xs", "outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-ring-offset"),
|
|
3821
|
+
onClick: () => setExpanded((value) => !value),
|
|
3822
|
+
children: expanded ? text.showLess : text.showMore(remaining)
|
|
3823
|
+
})]
|
|
3824
|
+
});
|
|
3825
|
+
}
|
|
3826
|
+
//#endregion
|
|
3827
|
+
//#region src/core/expression.tsx
|
|
3828
|
+
const toneStyles = {
|
|
3829
|
+
accent: {
|
|
3830
|
+
text: "text-accent",
|
|
3831
|
+
rule: "decoration-accent"
|
|
3832
|
+
},
|
|
3833
|
+
success: {
|
|
3834
|
+
text: "text-success",
|
|
3835
|
+
rule: "decoration-success"
|
|
3836
|
+
},
|
|
3837
|
+
warning: {
|
|
3838
|
+
text: "text-warning",
|
|
3839
|
+
rule: "decoration-warning"
|
|
3840
|
+
},
|
|
3841
|
+
danger: {
|
|
3842
|
+
text: "text-danger",
|
|
3843
|
+
rule: "decoration-danger"
|
|
3844
|
+
},
|
|
3845
|
+
info: {
|
|
3846
|
+
text: "text-info",
|
|
3847
|
+
rule: "decoration-info"
|
|
3848
|
+
},
|
|
3849
|
+
muted: {
|
|
3850
|
+
text: "text-text-secondary",
|
|
3851
|
+
rule: "decoration-border-hover"
|
|
3852
|
+
}
|
|
3853
|
+
};
|
|
3854
|
+
/**
|
|
3855
|
+
* Expression — one clause of a condition, written as a sentence and edited in
|
|
3856
|
+
* place.
|
|
3857
|
+
*
|
|
3858
|
+
* A rule like "when the count of checkout events is above 100 over 5 minutes"
|
|
3859
|
+
* is a sentence before it is a form. Rendering it as one — with only the
|
|
3860
|
+
* variable halves clickable — lets a reader verify the rule by reading it,
|
|
3861
|
+
* which is the step a stack of labelled inputs skips.
|
|
3862
|
+
*
|
|
3863
|
+
* @example
|
|
3864
|
+
* ```tsx
|
|
3865
|
+
* <ExpressionGroup>
|
|
3866
|
+
* <Expression description="when" value="count()" onClick={openAggregation} />
|
|
3867
|
+
* <Expression description="is above" value="100" tone="danger" onClick={openThreshold} />
|
|
3868
|
+
* <Expression description="for the last" value="5 minutes" onClick={openWindow} />
|
|
3869
|
+
* </ExpressionGroup>
|
|
3870
|
+
* ```
|
|
3871
|
+
*/
|
|
3872
|
+
function Expression({ description, value, tone = "accent", active = false, invalid = false, disabled = false, onClick, display = "inline", "aria-label": ariaLabel, className }) {
|
|
3873
|
+
const palette = invalid ? toneStyles.danger : toneStyles[tone];
|
|
3874
|
+
const interactive = onClick !== void 0 && !disabled;
|
|
3875
|
+
const body = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
3876
|
+
className: cn("text-text-secondary text-xs font-semibold tracking-wide uppercase", disabled && "opacity-60"),
|
|
3877
|
+
children: description
|
|
3878
|
+
}), value !== void 0 && value !== null && value !== "" && /* @__PURE__ */ jsx("span", {
|
|
3879
|
+
className: cn("text-sm font-semibold", palette.text, interactive && cn("underline decoration-2 underline-offset-4", palette.rule, active ? "decoration-solid" : "decoration-dotted"), disabled && "opacity-60"),
|
|
3880
|
+
children: value
|
|
3881
|
+
})] });
|
|
3882
|
+
const shell = cn(display === "stacked" ? "flex flex-col items-start gap-0.5" : "inline-flex items-baseline gap-1.5", "rounded-md px-1.5 py-1 text-left", interactive && "hover:bg-accent-subtle cursor-pointer transition-colors", active && "bg-accent-subtle", disabled && "cursor-not-allowed", className);
|
|
3883
|
+
if (!interactive) return /* @__PURE__ */ jsx("span", {
|
|
3884
|
+
className: shell,
|
|
3885
|
+
"data-invalid": invalid || void 0,
|
|
3886
|
+
"data-opt-id": "TXGA8",
|
|
3887
|
+
"data-opt-slug": "expression.Expression",
|
|
3888
|
+
children: body
|
|
3889
|
+
});
|
|
3890
|
+
return /* @__PURE__ */ jsx("button", {
|
|
3891
|
+
type: "button",
|
|
3892
|
+
className: cn(shell, OPT_FOCUS_VISIBLE),
|
|
3893
|
+
onClick,
|
|
3894
|
+
disabled,
|
|
3895
|
+
"aria-expanded": active,
|
|
3896
|
+
"aria-label": ariaLabel,
|
|
3897
|
+
"data-invalid": invalid || void 0,
|
|
3898
|
+
"data-opt-id": "TXGA8",
|
|
3899
|
+
"data-opt-slug": "expression.Expression",
|
|
3900
|
+
children: body
|
|
3901
|
+
});
|
|
3902
|
+
}
|
|
3903
|
+
/**
|
|
3904
|
+
* ExpressionGroup — the clauses of one condition, laid out so they read as a
|
|
3905
|
+
* single sentence rather than as a row of unrelated controls.
|
|
3906
|
+
*/
|
|
3907
|
+
function ExpressionGroup({ children, display = "sentence", className, "aria-label": ariaLabel }) {
|
|
3908
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3909
|
+
className: cn(display === "sentence" ? "flex flex-wrap items-baseline gap-x-1 gap-y-1" : "grid grid-flow-col auto-cols-fr items-start gap-group", className),
|
|
3910
|
+
role: "group",
|
|
3911
|
+
"aria-label": ariaLabel,
|
|
3912
|
+
"data-opt-id": "F78CE",
|
|
3913
|
+
"data-opt-slug": "expression.ExpressionGroup",
|
|
3914
|
+
children
|
|
3915
|
+
});
|
|
3916
|
+
}
|
|
3917
|
+
//#endregion
|
|
3918
|
+
//#region src/core/live-region.tsx
|
|
3919
|
+
/**
|
|
3920
|
+
* LiveRegion — says out loud what a live view changed.
|
|
3921
|
+
*
|
|
3922
|
+
* Data that updates without a page change is invisible to a screen reader
|
|
3923
|
+
* unless something narrates it. This is that narrator: one polite region per
|
|
3924
|
+
* live surface, throttled so a fast stream produces a readable summary rather
|
|
3925
|
+
* than a torrent, and alternating between two nodes so a repeat of the same
|
|
3926
|
+
* words is still announced rather than swallowed as an unchanged region.
|
|
3927
|
+
*
|
|
3928
|
+
* @example
|
|
3929
|
+
* ```tsx
|
|
3930
|
+
* <LiveRegion
|
|
3931
|
+
* message={`${newRows} new events`}
|
|
3932
|
+
* nonce={batchId}
|
|
3933
|
+
* throttleMs={4000}
|
|
3934
|
+
* muted={paused}
|
|
3935
|
+
* />
|
|
3936
|
+
* ```
|
|
3937
|
+
*/
|
|
3938
|
+
function LiveRegion({ message, nonce, politeness = "polite", throttleMs = 0, muted = false }) {
|
|
3939
|
+
const [slots, setSlots] = useState(["", ""]);
|
|
3940
|
+
const activeSlot = useRef(0);
|
|
3941
|
+
const pending = useRef(null);
|
|
3942
|
+
const timer = useRef(null);
|
|
3943
|
+
useEffect(() => {
|
|
3944
|
+
if (muted || message === "") return;
|
|
3945
|
+
const flush = () => {
|
|
3946
|
+
const next = pending.current;
|
|
3947
|
+
pending.current = null;
|
|
3948
|
+
timer.current = null;
|
|
3949
|
+
if (next === null) return;
|
|
3950
|
+
const slot = activeSlot.current;
|
|
3951
|
+
activeSlot.current = slot === 0 ? 1 : 0;
|
|
3952
|
+
setSlots(slot === 0 ? [next, ""] : ["", next]);
|
|
3953
|
+
};
|
|
3954
|
+
pending.current = message;
|
|
3955
|
+
if (throttleMs <= 0) {
|
|
3956
|
+
flush();
|
|
3957
|
+
return;
|
|
3958
|
+
}
|
|
3959
|
+
if (timer.current === null) timer.current = setTimeout(flush, throttleMs);
|
|
3960
|
+
return () => {
|
|
3961
|
+
if (timer.current !== null) {
|
|
3962
|
+
clearTimeout(timer.current);
|
|
3963
|
+
timer.current = null;
|
|
3964
|
+
}
|
|
3965
|
+
};
|
|
3966
|
+
}, [
|
|
3967
|
+
message,
|
|
3968
|
+
nonce,
|
|
3969
|
+
throttleMs,
|
|
3970
|
+
muted
|
|
3971
|
+
]);
|
|
3972
|
+
useEffect(() => {
|
|
3973
|
+
if (!muted) return;
|
|
3974
|
+
pending.current = null;
|
|
3975
|
+
if (timer.current !== null) {
|
|
3976
|
+
clearTimeout(timer.current);
|
|
3977
|
+
timer.current = null;
|
|
3978
|
+
}
|
|
3979
|
+
setSlots(["", ""]);
|
|
3980
|
+
}, [muted]);
|
|
3981
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3982
|
+
className: "sr-only",
|
|
3983
|
+
"data-opt-id": "0CFGR",
|
|
3984
|
+
"data-opt-slug": "live-region.LiveRegion",
|
|
3985
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
3986
|
+
role: "status",
|
|
3987
|
+
"aria-live": politeness,
|
|
3988
|
+
"aria-atomic": "true",
|
|
3989
|
+
children: slots[0]
|
|
3990
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
3991
|
+
role: "status",
|
|
3992
|
+
"aria-live": politeness,
|
|
3993
|
+
"aria-atomic": "true",
|
|
3994
|
+
children: slots[1]
|
|
3995
|
+
})]
|
|
3996
|
+
});
|
|
3997
|
+
}
|
|
3998
|
+
//#endregion
|
|
3999
|
+
//#region src/core/color-palette.tsx
|
|
4000
|
+
/** The theme's own chart ramps, so a picker can offer them without a literal. */
|
|
4001
|
+
const THEME_PALETTES = [
|
|
4002
|
+
{
|
|
4003
|
+
id: "categorical",
|
|
4004
|
+
label: "Categorical",
|
|
4005
|
+
kind: "categorical",
|
|
4006
|
+
colors: Array.from({ length: 10 }, (_, index) => `var(--opt-chart-${index + 1})`)
|
|
4007
|
+
},
|
|
4008
|
+
{
|
|
4009
|
+
id: "sequential",
|
|
4010
|
+
label: "Sequential",
|
|
4011
|
+
kind: "sequential",
|
|
4012
|
+
colors: Array.from({ length: 5 }, (_, index) => `var(--opt-chart-sequential-${index + 1})`)
|
|
4013
|
+
},
|
|
4014
|
+
{
|
|
4015
|
+
id: "diverging",
|
|
4016
|
+
label: "Diverging",
|
|
4017
|
+
kind: "diverging",
|
|
4018
|
+
colors: [
|
|
4019
|
+
"var(--opt-chart-diverging-negative)",
|
|
4020
|
+
"var(--opt-chart-diverging-neutral)",
|
|
4021
|
+
"var(--opt-chart-diverging-positive)"
|
|
4022
|
+
]
|
|
4023
|
+
}
|
|
4024
|
+
];
|
|
4025
|
+
const sizeStyles$2 = {
|
|
4026
|
+
xs: "h-1.5",
|
|
4027
|
+
sm: "h-2.5",
|
|
4028
|
+
md: "h-4",
|
|
4029
|
+
lg: "h-6"
|
|
4030
|
+
};
|
|
4031
|
+
/**
|
|
4032
|
+
* ColorPaletteDisplay — a palette, shown as the thing it will look like.
|
|
4033
|
+
*
|
|
4034
|
+
* A palette name tells a reader nothing; the colours do. Categorical palettes
|
|
4035
|
+
* draw as discrete blocks and ramps draw as a blend, so the picture itself
|
|
4036
|
+
* states which kind of data the palette is for.
|
|
4037
|
+
*/
|
|
4038
|
+
function ColorPaletteDisplay({ palette, render, size = "sm", "aria-label": ariaLabel, className }) {
|
|
4039
|
+
const mode = render !== null && render !== void 0 ? render : palette.kind === "categorical" ? "stops" : "gradient";
|
|
4040
|
+
const colors = palette.colors.length > 0 ? palette.colors : ["transparent"];
|
|
4041
|
+
return /* @__PURE__ */ jsx("span", {
|
|
4042
|
+
className: cn("flex w-full overflow-hidden rounded-sm", sizeStyles$2[size], className),
|
|
4043
|
+
role: "img",
|
|
4044
|
+
"aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : palette.label,
|
|
4045
|
+
style: mode === "gradient" ? { backgroundImage: `linear-gradient(to right, ${colors.join(", ")})` } : void 0,
|
|
4046
|
+
"data-opt-id": "E7T89",
|
|
4047
|
+
"data-opt-slug": "color-palette.ColorPaletteDisplay",
|
|
4048
|
+
children: mode === "stops" && colors.map((color, index) => /* @__PURE__ */ jsx("span", {
|
|
4049
|
+
className: "h-full flex-1",
|
|
4050
|
+
style: { backgroundColor: color }
|
|
4051
|
+
}, `${color}-${index}`))
|
|
4052
|
+
});
|
|
4053
|
+
}
|
|
4054
|
+
/**
|
|
4055
|
+
* ColorPalettePicker — choose the colours a visualisation is drawn in.
|
|
4056
|
+
*
|
|
4057
|
+
* Each option shows its own palette, so the choice is made by looking rather
|
|
4058
|
+
* than by remembering what "Cool" meant last time.
|
|
4059
|
+
*
|
|
4060
|
+
* @example
|
|
4061
|
+
* ```tsx
|
|
4062
|
+
* <ColorPalettePicker
|
|
4063
|
+
* palettes={THEME_PALETTES}
|
|
4064
|
+
* value={paletteId}
|
|
4065
|
+
* onValueChange={setPaletteId}
|
|
4066
|
+
* label="Series colours"
|
|
4067
|
+
* />
|
|
4068
|
+
* ```
|
|
4069
|
+
*/
|
|
4070
|
+
function ColorPalettePicker({ palettes, value, onValueChange, label = "Colour palette", hideLabel = false, size = "sm", disabled = false, className }) {
|
|
4071
|
+
var _palettes$find, _ref;
|
|
4072
|
+
const labelId = useId();
|
|
4073
|
+
const selected = (_palettes$find = palettes.find((palette) => palette.id === value)) !== null && _palettes$find !== void 0 ? _palettes$find : palettes[0];
|
|
4074
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
4075
|
+
className: cn("flex flex-col gap-element", className),
|
|
4076
|
+
"data-opt-id": "5R0PG",
|
|
4077
|
+
"data-opt-slug": "color-palette.ColorPalettePicker",
|
|
4078
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
4079
|
+
id: labelId,
|
|
4080
|
+
className: cn("text-text-primary text-sm font-medium", hideLabel && "sr-only"),
|
|
4081
|
+
children: label
|
|
4082
|
+
}), /* @__PURE__ */ jsxs(SelectRoot$1, {
|
|
4083
|
+
value: (_ref = value !== null && value !== void 0 ? value : selected === null || selected === void 0 ? void 0 : selected.id) !== null && _ref !== void 0 ? _ref : "",
|
|
4084
|
+
setValue: (next) => {
|
|
4085
|
+
if (typeof next === "string") onValueChange === null || onValueChange === void 0 || onValueChange(next);
|
|
4086
|
+
},
|
|
4087
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger$1, {
|
|
4088
|
+
"aria-labelledby": labelId,
|
|
4089
|
+
disabled,
|
|
4090
|
+
className: cn("gap-group", OPT_FOCUS_VISIBLE),
|
|
4091
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
4092
|
+
className: "gap-group flex min-w-0 flex-1 items-center",
|
|
4093
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
4094
|
+
className: "text-text-primary shrink-0 truncate text-sm",
|
|
4095
|
+
children: selected === null || selected === void 0 ? void 0 : selected.label
|
|
4096
|
+
}), selected && /* @__PURE__ */ jsx(ColorPaletteDisplay, {
|
|
4097
|
+
palette: selected,
|
|
4098
|
+
size,
|
|
4099
|
+
className: cn("min-w-16 flex-1", "border border-border"),
|
|
4100
|
+
"aria-label": ""
|
|
4101
|
+
})]
|
|
4102
|
+
})
|
|
4103
|
+
}), /* @__PURE__ */ jsx(SelectPopover$1, { children: palettes.map((palette) => /* @__PURE__ */ jsx(SelectItem$1, {
|
|
4104
|
+
value: palette.id,
|
|
4105
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
4106
|
+
className: "gap-group flex w-full min-w-40 items-center",
|
|
4107
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
4108
|
+
className: "shrink-0 truncate text-sm",
|
|
4109
|
+
children: palette.label
|
|
4110
|
+
}), /* @__PURE__ */ jsx(ColorPaletteDisplay, {
|
|
4111
|
+
palette,
|
|
4112
|
+
size,
|
|
4113
|
+
className: "min-w-16 flex-1",
|
|
4114
|
+
"aria-label": ""
|
|
4115
|
+
})]
|
|
4116
|
+
})
|
|
4117
|
+
}, palette.id)) })]
|
|
4118
|
+
})]
|
|
4119
|
+
});
|
|
4120
|
+
}
|
|
4121
|
+
//#endregion
|
|
4122
|
+
//#region src/core/inline-edit.tsx
|
|
4123
|
+
const defaultLabels = {
|
|
4124
|
+
edit: (value) => `Edit ${value}`,
|
|
4125
|
+
save: "Save",
|
|
4126
|
+
cancel: "Cancel",
|
|
4127
|
+
invalid: "That value cannot be saved."
|
|
4128
|
+
};
|
|
4129
|
+
const sizeStyles$1 = {
|
|
4130
|
+
sm: "text-sm",
|
|
4131
|
+
md: "text-base",
|
|
4132
|
+
lg: "text-lg font-semibold",
|
|
4133
|
+
xl: "text-2xl font-semibold tracking-tight"
|
|
4134
|
+
};
|
|
4135
|
+
/**
|
|
4136
|
+
* InlineEdit — renames a thing where the thing is, instead of in a dialog.
|
|
4137
|
+
*
|
|
4138
|
+
* A dashboard title, a metric name, a saved segment: each is read far more
|
|
4139
|
+
* often than it is renamed, so the control has to disappear into the text and
|
|
4140
|
+
* reappear on intent. Read mode is a button carrying the text; edit mode is a
|
|
4141
|
+
* field sized to match it, so the words do not move when the mode does.
|
|
4142
|
+
*
|
|
4143
|
+
* Enter commits, Escape reverts, and a rejected save keeps the field open with
|
|
4144
|
+
* what was typed — losing an edit to a validation error is worse than the
|
|
4145
|
+
* error.
|
|
4146
|
+
*
|
|
4147
|
+
* @example
|
|
4148
|
+
* ```tsx
|
|
4149
|
+
* <InlineEdit as="h2" size="xl" value={dashboard.name} onSave={rename} />
|
|
4150
|
+
* ```
|
|
4151
|
+
*/
|
|
4152
|
+
function InlineEdit({ value, onSave, as: Tag = "span", size = "md", placeholder, emptyText = "Untitled", disabled = false, maxLength, suffix, labels, className }) {
|
|
4153
|
+
const text = _objectSpread2(_objectSpread2({}, defaultLabels), labels);
|
|
4154
|
+
const [editing, setEditing] = useState(false);
|
|
4155
|
+
const [draft, setDraft] = useState(value);
|
|
4156
|
+
const [rejected, setRejected] = useState(false);
|
|
4157
|
+
const [saving, setSaving] = useState(false);
|
|
4158
|
+
const inputRef = useRef(null);
|
|
4159
|
+
useEffect(() => {
|
|
4160
|
+
if (!editing) setDraft(value);
|
|
4161
|
+
}, [value, editing]);
|
|
4162
|
+
useEffect(() => {
|
|
4163
|
+
if (editing) {
|
|
4164
|
+
var _inputRef$current, _inputRef$current2;
|
|
4165
|
+
(_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 || _inputRef$current.focus();
|
|
4166
|
+
(_inputRef$current2 = inputRef.current) === null || _inputRef$current2 === void 0 || _inputRef$current2.select();
|
|
4167
|
+
}
|
|
4168
|
+
}, [editing]);
|
|
4169
|
+
const commit = async () => {
|
|
4170
|
+
if (saving) return;
|
|
4171
|
+
const next = draft.trim();
|
|
4172
|
+
if (next === value) {
|
|
4173
|
+
setEditing(false);
|
|
4174
|
+
setRejected(false);
|
|
4175
|
+
return;
|
|
4176
|
+
}
|
|
4177
|
+
setSaving(true);
|
|
4178
|
+
try {
|
|
4179
|
+
if (await onSave(next) === false) {
|
|
4180
|
+
setRejected(true);
|
|
4181
|
+
return;
|
|
4182
|
+
}
|
|
4183
|
+
setRejected(false);
|
|
4184
|
+
setEditing(false);
|
|
4185
|
+
} finally {
|
|
4186
|
+
setSaving(false);
|
|
4187
|
+
}
|
|
4188
|
+
};
|
|
4189
|
+
const cancel = () => {
|
|
4190
|
+
setDraft(value);
|
|
4191
|
+
setRejected(false);
|
|
4192
|
+
setEditing(false);
|
|
4193
|
+
};
|
|
4194
|
+
const handleKeyDown = (event) => {
|
|
4195
|
+
if (event.key === "Enter") {
|
|
4196
|
+
event.preventDefault();
|
|
4197
|
+
commit();
|
|
4198
|
+
} else if (event.key === "Escape") {
|
|
4199
|
+
event.preventDefault();
|
|
4200
|
+
cancel();
|
|
4201
|
+
}
|
|
4202
|
+
};
|
|
4203
|
+
if (editing) return /* @__PURE__ */ jsxs("span", {
|
|
4204
|
+
className: cn("inline-flex min-w-0 items-center gap-element", className),
|
|
4205
|
+
"data-opt-id": "RQ0H2",
|
|
4206
|
+
"data-opt-slug": "inline-edit.InlineEdit",
|
|
4207
|
+
children: [
|
|
4208
|
+
/* @__PURE__ */ jsx("input", {
|
|
4209
|
+
ref: inputRef,
|
|
4210
|
+
value: draft,
|
|
4211
|
+
onChange: (event) => {
|
|
4212
|
+
setDraft(event.target.value);
|
|
4213
|
+
setRejected(false);
|
|
4214
|
+
},
|
|
4215
|
+
onKeyDown: handleKeyDown,
|
|
4216
|
+
onBlur: () => void commit(),
|
|
4217
|
+
placeholder,
|
|
4218
|
+
maxLength,
|
|
4219
|
+
disabled: saving,
|
|
4220
|
+
"aria-invalid": rejected || void 0,
|
|
4221
|
+
"aria-errormessage": rejected ? "inline-edit-error" : void 0,
|
|
4222
|
+
className: cn(OPT_INPUT, sizeStyles$1[size], "min-w-0 flex-1")
|
|
4223
|
+
}),
|
|
4224
|
+
rejected && /* @__PURE__ */ jsx("span", {
|
|
4225
|
+
id: "inline-edit-error",
|
|
4226
|
+
role: "alert",
|
|
4227
|
+
className: "sr-only",
|
|
4228
|
+
children: text.invalid
|
|
4229
|
+
}),
|
|
4230
|
+
/* @__PURE__ */ jsx("button", {
|
|
4231
|
+
type: "button",
|
|
4232
|
+
"aria-label": text.save,
|
|
4233
|
+
disabled: saving,
|
|
4234
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
4235
|
+
onClick: () => void commit(),
|
|
4236
|
+
className: cn("text-success hover:bg-success-subtle rounded-md p-1", OPT_FOCUS_VISIBLE),
|
|
4237
|
+
children: /* @__PURE__ */ jsx(Check, {
|
|
4238
|
+
className: "size-4",
|
|
4239
|
+
"aria-hidden": "true"
|
|
4240
|
+
})
|
|
4241
|
+
}),
|
|
4242
|
+
/* @__PURE__ */ jsx("button", {
|
|
4243
|
+
type: "button",
|
|
4244
|
+
"aria-label": text.cancel,
|
|
4245
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
4246
|
+
onClick: cancel,
|
|
4247
|
+
className: cn("text-text-tertiary hover:bg-bg-subtle rounded-md p-1", OPT_FOCUS_VISIBLE),
|
|
4248
|
+
children: /* @__PURE__ */ jsx(X, {
|
|
4249
|
+
className: "size-4",
|
|
4250
|
+
"aria-hidden": "true"
|
|
4251
|
+
})
|
|
4252
|
+
})
|
|
4253
|
+
]
|
|
4254
|
+
});
|
|
4255
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
4256
|
+
className: cn("inline-flex min-w-0 items-center gap-element", className),
|
|
4257
|
+
"data-opt-id": "RQ0H2",
|
|
4258
|
+
"data-opt-slug": "inline-edit.InlineEdit",
|
|
4259
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
4260
|
+
type: "button",
|
|
4261
|
+
disabled,
|
|
4262
|
+
onClick: () => setEditing(true),
|
|
4263
|
+
"aria-label": text.edit(value || emptyText),
|
|
4264
|
+
className: cn("group -mx-1.5 -my-0.5 inline-flex min-w-0 items-center gap-element rounded-md px-1.5 py-0.5 text-left", !disabled && "hover:bg-bg-subtle cursor-pointer", disabled && "cursor-default", OPT_FOCUS_VISIBLE),
|
|
4265
|
+
children: [/* @__PURE__ */ jsx(Tag, {
|
|
4266
|
+
className: cn("truncate", sizeStyles$1[size], value ? "text-text-primary" : "text-text-tertiary italic"),
|
|
4267
|
+
children: value || emptyText
|
|
4268
|
+
}), !disabled && /* @__PURE__ */ jsx(Pencil, {
|
|
4269
|
+
"aria-hidden": "true",
|
|
4270
|
+
className: cn("text-text-tertiary size-3.5 shrink-0 opacity-0 transition-opacity", "group-hover:opacity-100 group-focus-visible:opacity-100")
|
|
4271
|
+
})]
|
|
4272
|
+
}), suffix]
|
|
4273
|
+
});
|
|
4274
|
+
}
|
|
4275
|
+
//#endregion
|
|
4276
|
+
//#region src/core/key-pad-menu.tsx
|
|
4277
|
+
const sizeStyles = {
|
|
4278
|
+
sm: "min-h-20 p-2 text-xs [&_[data-keypad-icon]]:size-5",
|
|
4279
|
+
md: "min-h-24 p-3 text-sm [&_[data-keypad-icon]]:size-6"
|
|
4280
|
+
};
|
|
4281
|
+
/**
|
|
4282
|
+
* KeyPadMenu — a choice between things that are easier to recognise than to
|
|
4283
|
+
* name.
|
|
4284
|
+
*
|
|
4285
|
+
* A chart type, a layout, an import source: the picture is the label, and a
|
|
4286
|
+
* dropdown of words makes the reader translate twice. Tiles are real radios or
|
|
4287
|
+
* checkboxes underneath, so arrow keys, screen readers, and form submission
|
|
4288
|
+
* all behave as they would for any other group.
|
|
4289
|
+
*
|
|
4290
|
+
* @example
|
|
4291
|
+
* ```tsx
|
|
4292
|
+
* <KeyPadMenu
|
|
4293
|
+
* label="Chart type"
|
|
4294
|
+
* items={vizTypes}
|
|
4295
|
+
* value={vizType}
|
|
4296
|
+
* onValueChange={setVizType}
|
|
4297
|
+
* />
|
|
4298
|
+
* ```
|
|
4299
|
+
*/
|
|
4300
|
+
function KeyPadMenu({ items, label, selection = "single", value, onValueChange, columns = "auto", size = "md", hideLabel = false, className }) {
|
|
4301
|
+
const name = useId();
|
|
4302
|
+
const selected = new Set(value === void 0 ? [] : Array.isArray(value) ? value : [value]);
|
|
4303
|
+
const handleChange = (id) => {
|
|
4304
|
+
if (selection === "multiple") {
|
|
4305
|
+
const next = selected.has(id) ? [...selected].filter((item) => item !== id) : [...selected, id];
|
|
4306
|
+
onValueChange === null || onValueChange === void 0 || onValueChange(next);
|
|
4307
|
+
return;
|
|
4308
|
+
}
|
|
4309
|
+
onValueChange === null || onValueChange === void 0 || onValueChange(id);
|
|
4310
|
+
};
|
|
4311
|
+
return /* @__PURE__ */ jsxs("fieldset", {
|
|
4312
|
+
className: cn("min-w-0", className),
|
|
4313
|
+
"data-opt-id": "ZZG2R",
|
|
4314
|
+
"data-opt-slug": "key-pad-menu.KeyPadMenu",
|
|
4315
|
+
children: [/* @__PURE__ */ jsx("legend", {
|
|
4316
|
+
className: cn("text-text-primary mb-element text-sm font-medium", hideLabel && "sr-only"),
|
|
4317
|
+
children: label
|
|
4318
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
4319
|
+
className: "gap-element grid",
|
|
4320
|
+
style: { gridTemplateColumns: columns === "auto" ? "repeat(auto-fill, minmax(6.5rem, 1fr))" : `repeat(${columns}, minmax(0, 1fr))` },
|
|
4321
|
+
children: items.map((item) => {
|
|
4322
|
+
const isSelected = selected.has(item.id);
|
|
4323
|
+
return /* @__PURE__ */ jsxs("label", {
|
|
4324
|
+
className: cn("relative flex cursor-pointer flex-col items-center justify-center gap-1.5 rounded-lg text-center transition-colors", OPT_BORDER, sizeStyles[size], isSelected ? "border-accent bg-accent-subtle text-text-primary" : "text-text-secondary hover:border-border-hover hover:bg-bg-subtle", item.disabled && "cursor-not-allowed opacity-50", "has-[:focus-visible]:outline-ring has-[:focus-visible]:outline-2"),
|
|
4325
|
+
children: [
|
|
4326
|
+
/* @__PURE__ */ jsx("input", {
|
|
4327
|
+
type: selection === "single" ? "radio" : "checkbox",
|
|
4328
|
+
name: selection === "single" ? name : void 0,
|
|
4329
|
+
value: item.id,
|
|
4330
|
+
checked: isSelected,
|
|
4331
|
+
disabled: item.disabled,
|
|
4332
|
+
onChange: () => handleChange(item.id),
|
|
4333
|
+
className: cn("sr-only", OPT_FOCUS_VISIBLE)
|
|
4334
|
+
}),
|
|
4335
|
+
/* @__PURE__ */ jsx("span", {
|
|
4336
|
+
"data-keypad-icon": "",
|
|
4337
|
+
"aria-hidden": "true",
|
|
4338
|
+
className: cn("flex items-center justify-center", isSelected ? "text-accent" : "text-text-tertiary"),
|
|
4339
|
+
children: item.icon
|
|
4340
|
+
}),
|
|
4341
|
+
/* @__PURE__ */ jsx("span", {
|
|
4342
|
+
className: "font-medium",
|
|
4343
|
+
children: item.label
|
|
4344
|
+
}),
|
|
4345
|
+
item.hint && /* @__PURE__ */ jsx("span", {
|
|
4346
|
+
className: "text-text-tertiary line-clamp-2 text-[11px] leading-tight",
|
|
4347
|
+
children: item.hint
|
|
4348
|
+
}),
|
|
4349
|
+
item.badge && /* @__PURE__ */ jsx("span", {
|
|
4350
|
+
className: "bg-info-subtle text-info absolute top-1 right-1 rounded px-1 text-[10px] font-semibold",
|
|
4351
|
+
children: item.badge
|
|
4352
|
+
})
|
|
4353
|
+
]
|
|
4354
|
+
}, item.id);
|
|
4355
|
+
})
|
|
4356
|
+
})]
|
|
4357
|
+
});
|
|
4358
|
+
}
|
|
4359
|
+
//#endregion
|
|
4360
|
+
export { Skeleton as $, PageHeader as A, DropdownTrigger as B, Logo as C, Section as D, ResizablePanelGroup as E, RadioGroup as F, formatOptTime as G, formatOptDateTime as H, DropdownContent as I, CircularProgress as J, StepBuilder as K, DropdownItem as L, BlockLayout as M, LoadingOverlay as N, Container as O, Radio as P, Textarea as Q, DropdownMenu as R, DrawerRoot as S, ResizablePanel as T, formatOptNumber as U, ConditionBuilder as V, formatOptShortDate as W, Avatar as X, Progress as Y, AvatarGroup as Z, getShaderFragment as _, ToggleGroup$1 as _t, THEME_PALETTES as a, Card as at, DrawerClose as b, Heading as bt, ExpressionGroup as c, CardFooter as ct, ShaderSurface as d, Breadcrumb as dt, SkeletonAvatar as et, SHADER_OVERLAY_PARAM_KEYS as f, AccordionContent$1 as ft, SHADER_PRESET_IDS as g, Toggle$1 as gt, SHADER_PRESETS as h, AccordionTrigger$1 as ht, ColorPalettePicker as i, Switch as it, createBlock as j, ScrollArea as k, Facet as l, CardHeader as lt, SHADER_PARAM_KEYS as m, AccordionRoot$1 as mt, InlineEdit as n, SkeletonTable as nt, LiveRegion as o, CardContent as ot, SHADER_PARAM_FALLBACK as p, AccordionItem$1 as pt, Meter$1 as q, ColorPaletteDisplay as r, SkeletonText as rt, Expression as s, CardDescription as st, KeyPadMenu as t, SkeletonCard as tt, FacetGroup as u, CardTitle as ut, getShaderPreset as v, OtpField as vt, ResizableHandle as w, DrawerPanel as x, Text as xt, VirtualList as y, Separator$2 as yt, DropdownSeparator as z };
|
|
3653
4361
|
|
|
3654
|
-
//# sourceMappingURL=
|
|
4362
|
+
//# sourceMappingURL=key-pad-menu-BQunU20e.js.map
|