@photon-ai/pho-ui 2.12.0 → 2.14.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/chunks/IconChevronRight-CzoJnYNI.js +11 -0
- package/dist/chunks/IconChevronRight-CzoJnYNI.js.map +1 -0
- package/dist/chunks/badge-DlLJPLeZ.js +78 -0
- package/dist/chunks/badge-DlLJPLeZ.js.map +1 -0
- package/dist/chunks/bar-chart-BJfr3eyd.js +190 -0
- package/dist/chunks/bar-chart-BJfr3eyd.js.map +1 -0
- package/dist/chunks/{basic-page-CJwG4I83.js → basic-page-CdmVeL_Z.js} +433 -436
- package/dist/chunks/basic-page-CdmVeL_Z.js.map +1 -0
- package/dist/chunks/code-block-CdozzaHM.js +139 -0
- package/dist/chunks/code-block-CdozzaHM.js.map +1 -0
- package/dist/chunks/combobox-D4LiGJB2.js +136 -0
- package/dist/chunks/combobox-D4LiGJB2.js.map +1 -0
- package/dist/chunks/command-Ck8IAgV7.js +192 -0
- package/dist/chunks/command-Ck8IAgV7.js.map +1 -0
- package/dist/chunks/copy-button-B_B15mEi.js +56 -0
- package/dist/chunks/copy-button-B_B15mEi.js.map +1 -0
- package/dist/chunks/description-list-C-mwe4ki.js +59 -0
- package/dist/chunks/description-list-C-mwe4ki.js.map +1 -0
- package/dist/chunks/filter-bar-Cjjq38xo.js +259 -0
- package/dist/chunks/filter-bar-Cjjq38xo.js.map +1 -0
- package/dist/chunks/legend-C0mU7dqd.js +60 -0
- package/dist/chunks/legend-C0mU7dqd.js.map +1 -0
- package/dist/chunks/line-chart-CUR3AxrR.js +453 -0
- package/dist/chunks/line-chart-CUR3AxrR.js.map +1 -0
- package/dist/chunks/log-list-0AXnjFrk.js +244 -0
- package/dist/chunks/log-list-0AXnjFrk.js.map +1 -0
- package/dist/chunks/stat-hnNqjWVG.js +85 -0
- package/dist/chunks/stat-hnNqjWVG.js.map +1 -0
- package/dist/chunks/toggle-o84tawXy.js +33 -0
- package/dist/chunks/toggle-o84tawXy.js.map +1 -0
- package/dist/chunks/tooltip-BTF7GChl.js +32 -0
- package/dist/chunks/tooltip-BTF7GChl.js.map +1 -0
- package/dist/chunks/trace-CbH0IohX.js +314 -0
- package/dist/chunks/trace-CbH0IohX.js.map +1 -0
- package/dist/chunks/use-copy-D6JwKUll.js +25 -0
- package/dist/chunks/use-copy-D6JwKUll.js.map +1 -0
- package/dist/chunks/use-measure-CMVgLouO.js +616 -0
- package/dist/chunks/use-measure-CMVgLouO.js.map +1 -0
- package/dist/components/badge.js +5 -75
- package/dist/components/bar-chart.js +5 -0
- package/dist/components/chart.js +8 -0
- package/dist/components/code-block.js +6 -0
- package/dist/components/combobox.js +1 -1
- package/dist/components/command.js +3 -189
- package/dist/components/copy-button.js +6 -0
- package/dist/components/description-list.js +5 -0
- package/dist/components/filter-bar.js +8 -0
- package/dist/components/line-chart.js +5 -0
- package/dist/components/log-list.js +8 -0
- package/dist/components/phone-input.js +1 -1
- package/dist/components/stat.js +6 -0
- package/dist/components/toggle.js +3 -30
- package/dist/components/tooltip.js +2 -29
- package/dist/components/trace.js +9 -0
- package/dist/index.js +132 -93
- package/dist/primitives.js +2 -2
- package/dist/sections/basic-page.js +17 -17
- package/dist/src/components/accordion/index.d.ts +1 -1
- package/dist/src/components/bar-chart/bar-chart.d.ts +9 -0
- package/dist/src/components/bar-chart/index.d.ts +2 -0
- package/dist/src/components/chart/axes.d.ts +22 -0
- package/dist/src/components/chart/color.d.ts +10 -0
- package/dist/src/components/chart/format.d.ts +25 -0
- package/dist/src/components/chart/index.d.ts +8 -0
- package/dist/src/components/chart/legend.d.ts +26 -0
- package/dist/src/components/chart/scale.d.ts +34 -0
- package/dist/src/components/chart/shell.d.ts +42 -0
- package/dist/src/components/chart/tooltip.d.ts +30 -0
- package/dist/src/components/chart/types.d.ts +76 -0
- package/dist/src/components/chart/use-measure.d.ts +8 -0
- package/dist/src/components/code-block/code-block.d.ts +43 -0
- package/dist/src/components/code-block/index.d.ts +2 -0
- package/dist/src/components/code-block/tokenize-json.d.ts +12 -0
- package/dist/src/components/combobox/combobox.d.ts +30 -4
- package/dist/src/components/combobox/index.d.ts +1 -1
- package/dist/src/components/copy-button/copy-button.d.ts +49 -0
- package/dist/src/components/copy-button/index.d.ts +1 -0
- package/dist/src/components/description-list/description-list.d.ts +60 -0
- package/dist/src/components/description-list/index.d.ts +1 -0
- package/dist/src/components/filter-bar/filter-bar.d.ts +101 -0
- package/dist/src/components/filter-bar/index.d.ts +1 -0
- package/dist/src/components/line-chart/index.d.ts +2 -0
- package/dist/src/components/line-chart/line-chart.d.ts +11 -0
- package/dist/src/components/log-list/index.d.ts +1 -0
- package/dist/src/components/log-list/log-list.d.ts +136 -0
- package/dist/src/components/stat/index.d.ts +1 -0
- package/dist/src/components/stat/stat.d.ts +61 -0
- package/dist/src/components/toggle/toggle.d.ts +5 -1
- package/dist/src/components/trace/index.d.ts +2 -0
- package/dist/src/components/trace/layout.d.ts +48 -0
- package/dist/src/components/trace/trace.d.ts +53 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/sections/basic-page/basic-page.d.ts +3 -3
- package/dist/src/utils/index.d.ts +1 -0
- package/dist/src/utils/use-copy.d.ts +14 -0
- package/dist/utils.js +6 -3
- package/package.json +47 -1
- package/src/styles/theme.css +81 -0
- package/dist/chunks/basic-page-CJwG4I83.js.map +0 -1
- package/dist/chunks/combobox-Dj5m_dDd.js +0 -117
- package/dist/chunks/combobox-Dj5m_dDd.js.map +0 -1
- package/dist/components/badge.js.map +0 -1
- package/dist/components/command.js.map +0 -1
- package/dist/components/toggle.js.map +0 -1
- package/dist/components/tooltip.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-button-B_B15mEi.js","names":[],"sources":["../../../../node_modules/.pnpm/@tabler+icons-react@3.46.0_react@19.2.8/node_modules/@tabler/icons-react/dist/esm/icons/IconCopy.mjs","../../src/components/copy-button/copy-button.tsx"],"sourcesContent":["/**\n * @license @tabler/icons-react v3.46.0 - MIT\n *\n * This source code is licensed under the MIT license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createReactComponent from '../createReactComponent.mjs';\n\nconst __iconNode = [[\"path\", { \"d\": \"M7 9.667a2.667 2.667 0 0 1 2.667 -2.667h8.666a2.667 2.667 0 0 1 2.667 2.667v8.666a2.667 2.667 0 0 1 -2.667 2.667h-8.666a2.667 2.667 0 0 1 -2.667 -2.667l0 -8.666\", \"key\": \"svg-0\" }], [\"path\", { \"d\": \"M4.012 16.737a2.005 2.005 0 0 1 -1.012 -1.737v-10c0 -1.1 .9 -2 2 -2h10c.75 0 1.158 .385 1.5 1\", \"key\": \"svg-1\" }]];\nconst IconCopy = createReactComponent(\"outline\", \"copy\", \"Copy\", __iconNode);\n\nexport { __iconNode, IconCopy as default };\n//# sourceMappingURL=IconCopy.mjs.map\n","import { type ButtonHTMLAttributes, type Ref } from \"react\";\nimport { IconCheck, IconCopy } from \"@tabler/icons-react\";\nimport { cn } from \"../../utils/cn\";\nimport { resolveVariant, type VariantMap } from \"../../utils/resolve-variant\";\nimport { useCopy } from \"../../utils/use-copy\";\n\n/**\n * Size axis — a square ghost control that sits inside a row or a panel\n * header. `sm` is the default: a 28px target beside 13px mono text.\n */\nexport const COPY_BUTTON_SIZES = {\n xs: {\n classes: \"size-6 [&>svg]:size-3.5\",\n description: \"Extra small — inline after a mono value\",\n },\n sm: {\n classes: \"size-7 [&>svg]:size-4\",\n description: \"Small — the default; row cells, panel headers\",\n },\n md: {\n classes: \"size-8 [&>svg]:size-4\",\n description: \"Medium — toolbars\",\n },\n} satisfies VariantMap;\n\nexport type CopyButtonSize = keyof typeof COPY_BUTTON_SIZES;\n\ninterface CopyButtonBaseProps {\n /** The text that goes to the clipboard. */\n value: string;\n /**\n * What is being copied, for the accessible name — \"request id\",\n * \"API key\". Read as `Copy {label}`, then `{label} copied`.\n * @default \"to clipboard\"\n */\n label?: string;\n /** Size variant. @default \"sm\" */\n size?: CopyButtonSize;\n /** Called after the clipboard write lands. */\n onCopied?: (value: string) => void;\n ref?: Ref<HTMLButtonElement>;\n}\n\nexport interface CopyButtonProps\n extends\n CopyButtonBaseProps,\n Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n keyof CopyButtonBaseProps | \"children\"\n > {}\n\n/**\n * Copy Button — the one copy affordance: a quiet icon button that writes\n * `value` to the clipboard, swaps its glyph to a check for a moment, and\n * says \"Copied\" to assistive tech through a polite live region. Ghost at\n * rest so a mono value stays the loudest thing on its line; the wash and\n * the ink lift on hover. Put it after the value it copies.\n */\nexport function CopyButton({\n value,\n label = \"to clipboard\",\n size = \"sm\",\n onCopied,\n className,\n onClick,\n ...props\n}: CopyButtonProps) {\n const { copied, copy } = useCopy();\n return (\n <span className=\"inline-flex shrink-0\">\n <button\n type=\"button\"\n aria-label={copied ? `${label} copied` : `Copy ${label}`}\n data-copied={copied || undefined}\n onClick={async (event) => {\n onClick?.(event);\n if (event.defaultPrevented) return;\n if (await copy(value)) onCopied?.(value);\n }}\n className={cn(\n \"text-pho-secondary outline-pho-brand hover:bg-pho-ghost-hover hover:text-pho-secondary-hover inline-flex shrink-0 cursor-pointer items-center justify-center rounded-sm transition-[color,background-color] duration-100 ease-linear focus-visible:outline-2 focus-visible:outline-offset-2\",\n \"data-[copied]:text-pho-success data-[copied]:hover:text-pho-success\",\n resolveVariant(COPY_BUTTON_SIZES, size),\n className,\n )}\n {...props}\n >\n {copied ? <IconCheck aria-hidden /> : <IconCopy aria-hidden />}\n </button>\n <output className=\"sr-only\" aria-live=\"polite\">\n {copied ? \"Copied\" : \"\"}\n </output>\n </span>\n );\n}\n\nCopyButton.displayName = \"CopyButton\";\n"],"x_google_ignoreList":[0],"mappings":";;;;;;;;AASA,IAAM,IAAa,CAAC,CAAC,QAAQ;AAAA,EAAE,GAAK;AAAA,EAAoK,KAAO;AAAQ,CAAC,GAAG,CAAC,QAAQ;AAAA,EAAE,GAAK;AAAA,EAAiG,KAAO;AAAQ,CAAC,CAAC,GACvV,IAAW,EAAqB,WAAW,QAAQ,QAAQ,CAAU,GCA9D,IAAoB;AAAA,EAC/B,IAAI;AAAA,IACF,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA,EACA,IAAI;AAAA,IACF,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAAA,EACA,IAAI;AAAA,IACF,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AACF;AAmCA,SAAgB,EAAW,EACzB,OAAA,GACA,OAAA,IAAQ,gBACR,MAAA,IAAO,MACP,UAAA,GACA,WAAA,GACA,SAAA,GACA,GAAG,EAAA,GACe;AAClB,QAAM,EAAE,QAAA,GAAQ,MAAA,EAAA,IAAS,EAAQ;AACjC,SACE,gBAAA,EAAC,QAAD;AAAA,IAAM,WAAU;AAAA,IAAhB,UAAA,CACE,gBAAA,EAAC,UAAD;AAAA,MACE,MAAK;AAAA,MACL,cAAY,IAAS,GAAG,CAAA,YAAiB,QAAQ,CAAA;AAAA,MACjD,eAAa,KAAU;AAAA,MACvB,SAAS,OAAO,MAAU;AAExB,QADA,IAAU,CAAK,GACX,CAAA,EAAM,oBACN,MAAM,EAAK,CAAK,KAAG,IAAW,CAAK;AAAA,MACzC;AAAA,MACA,WAAW,EACT,+RACA,uEACA,EAAe,GAAmB,CAAI,GACtC,CACF;AAAA,MACA,GAAI;AAAA,MAEH,UAAA,IAAS,gBAAA,EAAC,GAAD,EAAW,eAAA,GAAa,CAAA,IAAI,gBAAA,EAAC,GAAD,EAAU,eAAA,GAAa,CAAA;AAAA,IACvD,CAAA,GACR,gBAAA,EAAC,UAAD;AAAA,MAAQ,WAAU;AAAA,MAAU,aAAU;AAAA,MACnC,UAAA,IAAS,WAAW;AAAA,IACf,CAAA,CACJ;AAAA;AAEV;AAEA,EAAW,cAAc"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { t as s } from "./cn-ChIgwEl3.js";
|
|
3
|
+
import { n as y } from "./copy-button-B_B15mEi.js";
|
|
4
|
+
import { createContext as N, useContext as w } from "react";
|
|
5
|
+
import { Fragment as v, jsx as t, jsxs as m } from "react/jsx-runtime";
|
|
6
|
+
var p = N("inline"), b = {
|
|
7
|
+
1: "",
|
|
8
|
+
2: "sm:grid-cols-2",
|
|
9
|
+
3: "sm:grid-cols-3"
|
|
10
|
+
};
|
|
11
|
+
function C({ layout: e = "inline", columns: n = 1, className: r, ...a }) {
|
|
12
|
+
return /* @__PURE__ */ t(p.Provider, {
|
|
13
|
+
value: e,
|
|
14
|
+
children: /* @__PURE__ */ t("dl", {
|
|
15
|
+
"data-description-list": e,
|
|
16
|
+
className: s("m-0 grid min-w-0", e === "inline" ? "grid-cols-[auto_minmax(0,1fr)] gap-x-6" : s("gap-x-6 gap-y-3", b[n]), r),
|
|
17
|
+
...a
|
|
18
|
+
})
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function L({ label: e, value: n, children: r, mono: a, copy: o, optional: d, empty: x = "Not set", className: g, ...u }) {
|
|
22
|
+
const f = w(p), i = r ?? n, l = i == null || i === "" || i === !1, c = typeof o == "string" ? o : o && typeof n == "string" ? n : null, h = typeof e == "string" ? e.toLowerCase() : "value";
|
|
23
|
+
return /* @__PURE__ */ m("div", {
|
|
24
|
+
"data-description-item": "",
|
|
25
|
+
"data-empty": l || void 0,
|
|
26
|
+
className: s("min-w-0", f === "inline" ? "col-span-2 grid grid-cols-subgrid items-baseline py-1" : "flex flex-col gap-0.5", g),
|
|
27
|
+
...u,
|
|
28
|
+
children: [/* @__PURE__ */ m("dt", {
|
|
29
|
+
className: "text-pho-description min-w-0 text-base",
|
|
30
|
+
children: [e, d ? /* @__PURE__ */ t("span", {
|
|
31
|
+
className: "text-pho-placeholder",
|
|
32
|
+
children: " (Optional)"
|
|
33
|
+
}) : null]
|
|
34
|
+
}), /* @__PURE__ */ t("dd", {
|
|
35
|
+
className: s("m-0 flex min-w-0 items-center gap-1", l ? "text-pho-placeholder text-base" : a ? "text-pho-primary font-mono text-xs" : "text-pho-primary text-base"),
|
|
36
|
+
children: l ? /* @__PURE__ */ t("span", {
|
|
37
|
+
className: "truncate",
|
|
38
|
+
children: x
|
|
39
|
+
}) : /* @__PURE__ */ m(v, { children: [/* @__PURE__ */ t("span", {
|
|
40
|
+
className: s("min-w-0", a ? "truncate" : "[overflow-wrap:anywhere]"),
|
|
41
|
+
children: i
|
|
42
|
+
}), c != null && c !== "" ? /* @__PURE__ */ t(y, {
|
|
43
|
+
value: c,
|
|
44
|
+
label: h,
|
|
45
|
+
size: "xs",
|
|
46
|
+
className: "-my-1"
|
|
47
|
+
}) : null] })
|
|
48
|
+
})]
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
var B = {
|
|
52
|
+
Root: C,
|
|
53
|
+
Item: L
|
|
54
|
+
};
|
|
55
|
+
export {
|
|
56
|
+
B as t
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=description-list-C-mwe4ki.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"description-list-C-mwe4ki.js","names":[],"sources":["../../src/components/description-list/description-list.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n type ComponentProps,\n type ReactNode,\n type Ref,\n} from \"react\";\nimport { CopyButton } from \"../copy-button\";\nimport { cn } from \"../../utils/cn\";\n\n/**\n * Description List — the facts about one record, label beside value:\n * the status, the request id, the time, the ip, the api version, the\n * idempotency key, the api key. A `<dl>` under the hood.\n *\n * Where `BasicPage.Row` is a 68px row built for a control on its right,\n * these are dense lines of text: `inline` puts the label in a column\n * beside the value, every label the same width (a subgrid), one line per\n * fact; `stacked` puts the label above the value and flows the pairs\n * into `columns`. A `mono` value wears the mono face with a copy\n * control after it; a value that is not there says so in muted ink.\n */\n\nexport type DescriptionListLayout = \"inline\" | \"stacked\";\n\nconst LayoutContext = createContext<DescriptionListLayout>(\"inline\");\n\nexport interface DescriptionListRootProps extends ComponentProps<\"dl\"> {\n /**\n * `inline` (default): label column beside the value column, one line\n * per item. `stacked`: label above value, pairs flowing into `columns`.\n */\n layout?: DescriptionListLayout;\n /** For `stacked`: pairs per row from `sm` up. @default 1 */\n columns?: 1 | 2 | 3;\n ref?: Ref<HTMLDListElement>;\n}\n\nconst COLUMNS: Record<1 | 2 | 3, string> = {\n 1: \"\",\n 2: \"sm:grid-cols-2\",\n 3: \"sm:grid-cols-3\",\n};\n\nfunction Root({\n layout = \"inline\",\n columns = 1,\n className,\n ...props\n}: DescriptionListRootProps) {\n return (\n <LayoutContext.Provider value={layout}>\n <dl\n data-description-list={layout}\n className={cn(\n \"m-0 grid min-w-0\",\n layout === \"inline\"\n ? \"grid-cols-[auto_minmax(0,1fr)] gap-x-6\"\n : cn(\"gap-x-6 gap-y-3\", COLUMNS[columns]),\n className,\n )}\n {...props}\n />\n </LayoutContext.Provider>\n );\n}\n\nexport interface DescriptionListItemProps extends Omit<\n ComponentProps<\"div\">,\n \"children\" | \"title\"\n> {\n /** The fact's name — \"Request ID\", \"Idempotency key\". */\n label: ReactNode;\n /** The fact. A string wears the item's type; a node is yours to style. */\n value?: ReactNode;\n /** Composed content instead of `value` — a `Badge`, a link. */\n children?: ReactNode;\n /** The value in the mono face — ids, keys, addresses. */\n mono?: boolean;\n /**\n * A copy control after the value. `true` copies a string `value`;\n * pass the text to copy when the value is composed.\n */\n copy?: boolean | string;\n /** Marks the label `(Optional)` — a fact that may not be there. */\n optional?: boolean;\n /** What an absent value says. @default \"Not set\" */\n empty?: ReactNode;\n ref?: Ref<HTMLDivElement>;\n}\n\nfunction Item({\n label,\n value,\n children,\n mono,\n copy,\n optional,\n empty = \"Not set\",\n className,\n ...props\n}: DescriptionListItemProps) {\n const layout = useContext(LayoutContext);\n const content = children ?? value;\n const absent = content == null || content === \"\" || content === false;\n const copyText =\n typeof copy === \"string\"\n ? copy\n : copy && typeof value === \"string\"\n ? value\n : null;\n const copyLabel = typeof label === \"string\" ? label.toLowerCase() : \"value\";\n return (\n <div\n data-description-item=\"\"\n data-empty={absent || undefined}\n className={cn(\n \"min-w-0\",\n layout === \"inline\"\n ? \"col-span-2 grid grid-cols-subgrid items-baseline py-1\"\n : \"flex flex-col gap-0.5\",\n className,\n )}\n {...props}\n >\n <dt className=\"text-pho-description min-w-0 text-base\">\n {label}\n {optional ? (\n <span className=\"text-pho-placeholder\"> (Optional)</span>\n ) : null}\n </dt>\n <dd\n className={cn(\n \"m-0 flex min-w-0 items-center gap-1\",\n absent\n ? \"text-pho-placeholder text-base\"\n : mono\n ? \"text-pho-primary font-mono text-xs\"\n : \"text-pho-primary text-base\",\n )}\n >\n {absent ? (\n <span className=\"truncate\">{empty}</span>\n ) : (\n <>\n {/* An id keeps its line and truncates; prose wraps. */}\n <span\n className={cn(\n \"min-w-0\",\n mono ? \"truncate\" : \"[overflow-wrap:anywhere]\",\n )}\n >\n {content}\n </span>\n {copyText != null && copyText !== \"\" ? (\n <CopyButton\n value={copyText}\n label={copyLabel}\n size=\"xs\"\n className=\"-my-1\"\n />\n ) : null}\n </>\n )}\n </dd>\n </div>\n );\n}\n\n/**\n * ```tsx\n * <DescriptionList.Root>\n * <DescriptionList.Item label=\"Status\"><Badge …/></DescriptionList.Item>\n * <DescriptionList.Item label=\"Request ID\" value=\"pho_req_…\" mono copy />\n * <DescriptionList.Item label=\"Idempotency key\" optional mono copy />\n * </DescriptionList.Root>\n * ```\n */\nexport const DescriptionList = { Root, Item };\n"],"mappings":";;;;;AAyBA,IAAM,IAAgB,EAAqC,QAAQ,GAa7D,IAAqC;AAAA,EACzC,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AAEA,SAAS,EAAK,EACZ,QAAA,IAAS,UACT,SAAA,IAAU,GACV,WAAA,GACA,GAAG,EAAA,GACwB;AAC3B,SACE,gBAAA,EAAC,EAAc,UAAf;AAAA,IAAwB,OAAO;AAAA,IAC7B,UAAA,gBAAA,EAAC,MAAD;AAAA,MACE,yBAAuB;AAAA,MACvB,WAAW,EACT,oBACA,MAAW,WACP,2CACA,EAAG,mBAAmB,EAAQ,CAAA,CAAQ,GAC1C,CACF;AAAA,MACA,GAAI;AAAA,IACL,CAAA;AAAA,EACqB,CAAA;AAE5B;AA0BA,SAAS,EAAK,EACZ,OAAA,GACA,OAAA,GACA,UAAA,GACA,MAAA,GACA,MAAA,GACA,UAAA,GACA,OAAA,IAAQ,WACR,WAAA,GACA,GAAG,EAAA,GACwB;AAC3B,QAAM,IAAS,EAAW,CAAa,GACjC,IAAU,KAAY,GACtB,IAAS,KAAW,QAAQ,MAAY,MAAM,MAAY,IAC1D,IACJ,OAAO,KAAS,WACZ,IACA,KAAQ,OAAO,KAAU,WACvB,IACA,MACF,IAAY,OAAO,KAAU,WAAW,EAAM,YAAY,IAAI;AACpE,SACE,gBAAA,EAAC,OAAD;AAAA,IACE,yBAAsB;AAAA,IACtB,cAAY,KAAU;AAAA,IACtB,WAAW,EACT,WACA,MAAW,WACP,0DACA,yBACJ,CACF;AAAA,IACA,GAAI;AAAA,IAVN,UAAA,CAYE,gBAAA,EAAC,MAAD;AAAA,MAAI,WAAU;AAAA,MAAd,UAAA,CACG,GACA,IACC,gBAAA,EAAC,QAAD;AAAA,QAAM,WAAU;AAAA,QAAuB,UAAA;AAAA,MAAiB,CAAA,IACtD,IACF;AAAA,IACJ,CAAA,GAAA,gBAAA,EAAC,MAAD;AAAA,MACE,WAAW,EACT,uCACA,IACI,mCACA,IACE,uCACA,4BACR;AAAA,MAEC,UAAA,IACC,gBAAA,EAAC,QAAD;AAAA,QAAM,WAAU;AAAA,QAAY,UAAA;AAAA,MAAY,CAAA,IAExC,gBAAA,EAAA,GAAA,EAAA,UAAA,CAEE,gBAAA,EAAC,QAAD;AAAA,QACE,WAAW,EACT,WACA,IAAO,aAAa,0BACtB;AAAA,QAEC,UAAA;AAAA,MACG,CAAA,GACL,KAAY,QAAQ,MAAa,KAChC,gBAAA,EAAC,GAAD;AAAA,QACE,OAAO;AAAA,QACP,OAAO;AAAA,QACP,MAAK;AAAA,QACL,WAAU;AAAA,MACX,CAAA,IACC,IACJ,EAAA,CAAA;AAAA,IAEF,CAAA,CACD;AAAA;AAET;AAWA,IAAa,IAAkB;AAAA,EAAE,MAAA;AAAA,EAAM,MAAA;AAAK"}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { t as y } from "./cn-ChIgwEl3.js";
|
|
3
|
+
import { a as P } from "./button-CdqS_t5a.js";
|
|
4
|
+
import { t as R } from "./createReactComponent-BqCmnqfX.js";
|
|
5
|
+
import { t as S } from "./IconCheck-D2WuculQ.js";
|
|
6
|
+
import { t as L } from "./IconSearch-BKAHkLyX.js";
|
|
7
|
+
import { t as $ } from "./IconX-pZ9vrtSr.js";
|
|
8
|
+
import { t as w } from "./input-group-fc9_jD4d.js";
|
|
9
|
+
import { t as g } from "./popover-BhBJO346.js";
|
|
10
|
+
import { t as d } from "./command-Ck8IAgV7.js";
|
|
11
|
+
import { useRef as D, useState as T } from "react";
|
|
12
|
+
import { jsx as a, jsxs as u } from "react/jsx-runtime";
|
|
13
|
+
var E = [["path", {
|
|
14
|
+
d: "M12 5l0 14",
|
|
15
|
+
key: "svg-0"
|
|
16
|
+
}], ["path", {
|
|
17
|
+
d: "M5 12l14 0",
|
|
18
|
+
key: "svg-1"
|
|
19
|
+
}]], F = R("outline", "plus", "Plus", E), O = [
|
|
20
|
+
{
|
|
21
|
+
label: "Last hour",
|
|
22
|
+
hours: 1
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
label: "Last 24 hours",
|
|
26
|
+
hours: 24
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
label: "Last 7 days",
|
|
30
|
+
hours: 168
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
label: "Last 30 days",
|
|
34
|
+
hours: 720
|
|
35
|
+
}
|
|
36
|
+
], _ = 8;
|
|
37
|
+
function k(e) {
|
|
38
|
+
return e == null ? !1 : typeof e == "string" ? e !== "" : Array.isArray(e) ? e.length > 0 : e.from != null || e.to != null;
|
|
39
|
+
}
|
|
40
|
+
var A = /^\d{4}-\d{2}-\d{2}$/;
|
|
41
|
+
function x(e) {
|
|
42
|
+
const r = A.test(e), n = new Date(r ? `${e}T00:00:00Z` : e);
|
|
43
|
+
return Number.isNaN(n.getTime()) ? e : new Intl.DateTimeFormat("en-US", {
|
|
44
|
+
month: "short",
|
|
45
|
+
day: "numeric",
|
|
46
|
+
...r ? { timeZone: "UTC" } : {}
|
|
47
|
+
}).format(n);
|
|
48
|
+
}
|
|
49
|
+
function z(e, r) {
|
|
50
|
+
if (!k(r) || r == null) return "";
|
|
51
|
+
if (e.type === "date-range") {
|
|
52
|
+
const l = r;
|
|
53
|
+
return l.preset ? l.preset : l.from && l.to ? `${x(l.from)} to ${x(l.to)}` : l.from ? `From ${x(l.from)}` : l.to ? `Until ${x(l.to)}` : "";
|
|
54
|
+
}
|
|
55
|
+
const n = (l) => e.options.find((p) => p.value === l)?.label ?? l;
|
|
56
|
+
return Array.isArray(r) ? r.map(n).join(", ") : typeof r == "string" ? n(r) : "";
|
|
57
|
+
}
|
|
58
|
+
var C = "outline-pho-brand flex h-8 min-w-0 cursor-pointer items-center gap-1.5 rounded-full text-base transition-[color,background-color] duration-100 ease-linear focus-visible:outline-2 focus-visible:-outline-offset-2 [&>svg]:size-4 [&>svg]:shrink-0";
|
|
59
|
+
function B({ definition: e, value: r, onChange: n }) {
|
|
60
|
+
const [l, p] = T(!1), i = D(null), c = k(r), t = z(e, r);
|
|
61
|
+
return /* @__PURE__ */ u(g.Root, {
|
|
62
|
+
open: l,
|
|
63
|
+
onOpenChange: p,
|
|
64
|
+
children: [/* @__PURE__ */ u("span", {
|
|
65
|
+
"data-filter-chip": e.key,
|
|
66
|
+
"data-set": c || void 0,
|
|
67
|
+
className: y("ring-pho-primary inline-flex max-w-full shrink-0 items-stretch rounded-full ring-1 ring-inset", c ? "bg-pho-secondary" : "bg-transparent"),
|
|
68
|
+
children: [/* @__PURE__ */ u(g.Trigger, {
|
|
69
|
+
"aria-label": c ? void 0 : `${e.label} filter`,
|
|
70
|
+
className: y(C, "hover:bg-pho-ghost-hover", c ? "pr-1.5 pl-3" : "pr-3 pl-2.5", c ? "rounded-r-none" : ""),
|
|
71
|
+
children: [c ? null : /* @__PURE__ */ a(F, { "aria-hidden": !0 }), c ? /* @__PURE__ */ u("span", {
|
|
72
|
+
className: "flex min-w-0 items-baseline gap-1",
|
|
73
|
+
children: [/* @__PURE__ */ u("span", {
|
|
74
|
+
className: "text-pho-description shrink-0",
|
|
75
|
+
children: [e.label, ":"]
|
|
76
|
+
}), /* @__PURE__ */ a("span", {
|
|
77
|
+
className: "text-pho-primary max-w-56 truncate font-medium",
|
|
78
|
+
children: t
|
|
79
|
+
})]
|
|
80
|
+
}) : /* @__PURE__ */ a("span", {
|
|
81
|
+
className: "text-pho-secondary",
|
|
82
|
+
children: e.label
|
|
83
|
+
})]
|
|
84
|
+
}), c ? /* @__PURE__ */ a("button", {
|
|
85
|
+
type: "button",
|
|
86
|
+
"aria-label": `Clear ${e.label.toLowerCase()} filter`,
|
|
87
|
+
onClick: () => n(void 0),
|
|
88
|
+
className: y(C, "text-pho-secondary hover:bg-pho-ghost-hover hover:text-pho-secondary-hover rounded-l-none pr-2.5 pl-1 [&>svg]:size-3.5"),
|
|
89
|
+
children: /* @__PURE__ */ a($, { "aria-hidden": !0 })
|
|
90
|
+
}) : null]
|
|
91
|
+
}), /* @__PURE__ */ a(g.Portal, { children: /* @__PURE__ */ a(g.Positioner, {
|
|
92
|
+
align: "start",
|
|
93
|
+
sideOffset: 6,
|
|
94
|
+
children: /* @__PURE__ */ a(g.Popup, {
|
|
95
|
+
"aria-label": `${e.label} filter`,
|
|
96
|
+
initialFocus: i,
|
|
97
|
+
className: y("w-64 p-0", e.type === "date-range" && "w-72"),
|
|
98
|
+
children: e.type === "date-range" ? /* @__PURE__ */ a(U, {
|
|
99
|
+
definition: e,
|
|
100
|
+
value: r,
|
|
101
|
+
focusRef: i,
|
|
102
|
+
onChange: (m) => {
|
|
103
|
+
n(m), m?.preset && p(!1);
|
|
104
|
+
}
|
|
105
|
+
}) : /* @__PURE__ */ a(G, {
|
|
106
|
+
definition: e,
|
|
107
|
+
value: r,
|
|
108
|
+
focusRef: i,
|
|
109
|
+
onChange: (m) => {
|
|
110
|
+
n(m), e.multiple || p(!1);
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
}) })]
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function G({ definition: e, value: r, focusRef: n, onChange: l }) {
|
|
118
|
+
const [p, i] = T(""), c = e.searchable ?? e.options.length > _, t = new Set(r == null ? [] : Array.isArray(r) ? r : [r]), m = p.trim().toLowerCase(), f = m ? e.options.filter((s) => s.label.toLowerCase().includes(m) || s.value.toLowerCase().includes(m)) : e.options, v = (s) => {
|
|
119
|
+
if (e.multiple) {
|
|
120
|
+
const b = new Set(t);
|
|
121
|
+
b.has(s.value) ? b.delete(s.value) : b.add(s.value);
|
|
122
|
+
const o = e.options.map((h) => h.value).filter((h) => b.has(h));
|
|
123
|
+
l(o.length > 0 ? o : void 0);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
l(t.has(s.value) ? void 0 : s.value);
|
|
127
|
+
};
|
|
128
|
+
return /* @__PURE__ */ u(d.Root, {
|
|
129
|
+
label: `${e.label} options`,
|
|
130
|
+
ref: (s) => {
|
|
131
|
+
c || (n.current = s);
|
|
132
|
+
},
|
|
133
|
+
className: "rounded-base",
|
|
134
|
+
children: [c ? /* @__PURE__ */ u(d.InputGroup, { children: [/* @__PURE__ */ a(L, {
|
|
135
|
+
"aria-hidden": !0,
|
|
136
|
+
className: "text-pho-description pointer-events-none absolute left-3 size-4"
|
|
137
|
+
}), /* @__PURE__ */ a(d.Input, {
|
|
138
|
+
ref: (s) => {
|
|
139
|
+
n.current = s;
|
|
140
|
+
},
|
|
141
|
+
value: p,
|
|
142
|
+
onValueChange: i,
|
|
143
|
+
placeholder: e.searchPlaceholder ?? "Search",
|
|
144
|
+
"aria-label": `Search ${e.label.toLowerCase()} options`,
|
|
145
|
+
className: "py-2 pr-3 pl-9 text-base"
|
|
146
|
+
})] }) : null, /* @__PURE__ */ u(d.List, { children: [/* @__PURE__ */ a(d.Empty, { children: "Nothing matches." }), f.map((s) => /* @__PURE__ */ a(d.Item, {
|
|
147
|
+
value: s.value,
|
|
148
|
+
title: s.label,
|
|
149
|
+
"aria-checked": t.has(s.value),
|
|
150
|
+
icon: t.has(s.value) ? /* @__PURE__ */ a(S, { "aria-hidden": !0 }) : null,
|
|
151
|
+
onSelect: () => v(s)
|
|
152
|
+
}, s.value))] })]
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
var I = "bg-pho-primary ring-pho-primary text-pho-primary focus-visible:ring-pho-brand w-full min-w-0 rounded-sm px-2 py-1 text-base ring-1 transition-shadow duration-100 ease-linear outline-none ring-inset focus-visible:ring-2";
|
|
156
|
+
function U({ definition: e, value: r, focusRef: n, onChange: l }) {
|
|
157
|
+
const p = e.presets ?? O, i = (t) => t == null ? "" : A.test(t) ? t : t.slice(0, 10), c = (t, m) => {
|
|
158
|
+
const f = {
|
|
159
|
+
from: i(r?.from) || null,
|
|
160
|
+
to: i(r?.to) || null,
|
|
161
|
+
[t]: m || null
|
|
162
|
+
};
|
|
163
|
+
l(f.from == null && f.to == null ? void 0 : f);
|
|
164
|
+
};
|
|
165
|
+
return /* @__PURE__ */ u("div", {
|
|
166
|
+
className: "flex flex-col",
|
|
167
|
+
children: [/* @__PURE__ */ a(d.Root, {
|
|
168
|
+
label: `${e.label} presets`,
|
|
169
|
+
ref: (t) => {
|
|
170
|
+
n.current = t;
|
|
171
|
+
},
|
|
172
|
+
className: "rounded-t-[inherit]",
|
|
173
|
+
children: /* @__PURE__ */ a(d.List, { children: p.map((t) => /* @__PURE__ */ a(d.Item, {
|
|
174
|
+
value: t.label,
|
|
175
|
+
title: t.label,
|
|
176
|
+
"aria-checked": r?.preset === t.label,
|
|
177
|
+
icon: r?.preset === t.label ? /* @__PURE__ */ a(S, { "aria-hidden": !0 }) : null,
|
|
178
|
+
onSelect: () => l({
|
|
179
|
+
from: (/* @__PURE__ */ new Date(Date.now() - t.hours * 60 * 60 * 1e3)).toISOString(),
|
|
180
|
+
to: null,
|
|
181
|
+
preset: t.label
|
|
182
|
+
})
|
|
183
|
+
}, t.label)) })
|
|
184
|
+
}), /* @__PURE__ */ u("div", {
|
|
185
|
+
className: "border-pho-secondary grid grid-cols-2 gap-2 border-t p-2",
|
|
186
|
+
children: [/* @__PURE__ */ u("label", {
|
|
187
|
+
className: "text-pho-description flex min-w-0 flex-col gap-1 text-xs",
|
|
188
|
+
children: ["From", /* @__PURE__ */ a("input", {
|
|
189
|
+
type: "date",
|
|
190
|
+
value: i(r?.from),
|
|
191
|
+
max: i(r?.to) || void 0,
|
|
192
|
+
onChange: (t) => c("from", t.target.value),
|
|
193
|
+
className: I
|
|
194
|
+
})]
|
|
195
|
+
}), /* @__PURE__ */ u("label", {
|
|
196
|
+
className: "text-pho-description flex min-w-0 flex-col gap-1 text-xs",
|
|
197
|
+
children: ["To", /* @__PURE__ */ a("input", {
|
|
198
|
+
type: "date",
|
|
199
|
+
value: i(r?.to),
|
|
200
|
+
min: i(r?.from) || void 0,
|
|
201
|
+
onChange: (t) => c("to", t.target.value),
|
|
202
|
+
className: I
|
|
203
|
+
})]
|
|
204
|
+
})]
|
|
205
|
+
})]
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
function j({ definitions: e, filters: r, onFiltersChange: n, search: l, onSearchChange: p, searchPlaceholder: i = "Search", searchLabel: c, sticky: t, className: m, children: f, ...v }) {
|
|
209
|
+
const s = e.filter((o) => k(r[o.key])).length, b = (o, h) => {
|
|
210
|
+
const N = { ...r };
|
|
211
|
+
h === void 0 ? delete N[o] : N[o] = h, n(N);
|
|
212
|
+
};
|
|
213
|
+
return /* @__PURE__ */ u("div", {
|
|
214
|
+
"data-filter-bar": "",
|
|
215
|
+
className: y("flex min-w-0 flex-wrap items-center gap-2", t && "sticky top-0 z-10 bg-[var(--pho-surface,var(--background-color-pho-page))] py-3", m),
|
|
216
|
+
...v,
|
|
217
|
+
children: [
|
|
218
|
+
p != null ? /* @__PURE__ */ u(w.Root, {
|
|
219
|
+
size: "sm",
|
|
220
|
+
className: "w-64 max-w-full",
|
|
221
|
+
children: [/* @__PURE__ */ a(w.Addon, { children: /* @__PURE__ */ a(L, {}) }), /* @__PURE__ */ a(w.Input, {
|
|
222
|
+
type: "search",
|
|
223
|
+
value: l ?? "",
|
|
224
|
+
onChange: (o) => p(o.target.value),
|
|
225
|
+
onKeyDown: (o) => {
|
|
226
|
+
o.key === "Escape" && (o.preventDefault(), p(""));
|
|
227
|
+
},
|
|
228
|
+
placeholder: i,
|
|
229
|
+
"aria-label": c ?? i
|
|
230
|
+
})]
|
|
231
|
+
}) : null,
|
|
232
|
+
e.map((o) => /* @__PURE__ */ a(B, {
|
|
233
|
+
definition: o,
|
|
234
|
+
value: r[o.key],
|
|
235
|
+
onChange: (h) => b(o.key, h)
|
|
236
|
+
}, o.key)),
|
|
237
|
+
s > 1 ? /* @__PURE__ */ a(P, {
|
|
238
|
+
variant: "ghost",
|
|
239
|
+
size: "sm",
|
|
240
|
+
onClick: () => {
|
|
241
|
+
const o = { ...r };
|
|
242
|
+
for (const h of e) delete o[h.key];
|
|
243
|
+
n(o);
|
|
244
|
+
},
|
|
245
|
+
children: "Clear filters"
|
|
246
|
+
}) : null,
|
|
247
|
+
f
|
|
248
|
+
]
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
j.displayName = "FilterBar";
|
|
252
|
+
export {
|
|
253
|
+
k as i,
|
|
254
|
+
j as n,
|
|
255
|
+
z as r,
|
|
256
|
+
O as t
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
//# sourceMappingURL=filter-bar-Cjjq38xo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-bar-Cjjq38xo.js","names":[],"sources":["../../../../node_modules/.pnpm/@tabler+icons-react@3.46.0_react@19.2.8/node_modules/@tabler/icons-react/dist/esm/icons/IconPlus.mjs","../../src/components/filter-bar/filter-bar.tsx"],"sourcesContent":["/**\n * @license @tabler/icons-react v3.46.0 - MIT\n *\n * This source code is licensed under the MIT license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createReactComponent from '../createReactComponent.mjs';\n\nconst __iconNode = [[\"path\", { \"d\": \"M12 5l0 14\", \"key\": \"svg-0\" }], [\"path\", { \"d\": \"M5 12l14 0\", \"key\": \"svg-1\" }]];\nconst IconPlus = createReactComponent(\"outline\", \"plus\", \"Plus\", __iconNode);\n\nexport { __iconNode, IconPlus as default };\n//# sourceMappingURL=IconPlus.mjs.map\n","import {\n useRef,\n useState,\n type ComponentProps,\n type ReactNode,\n type Ref,\n type RefObject,\n} from \"react\";\nimport { IconCheck, IconPlus, IconSearch, IconX } from \"@tabler/icons-react\";\nimport { Button } from \"../button\";\nimport { Command } from \"../command\";\nimport { InputGroup } from \"../input-group\";\nimport { Popover } from \"../popover\";\nimport { cn } from \"../../utils/cn\";\n\n/**\n * Filter Bar — the row that pins above a log list: a search field for an\n * id, then one chip per filter. A chip with nothing chosen reads\n * `+ Status`; with a value it reads `Status: Failed` and carries its own\n * clear. Each chip opens a small popover: a keyboard-first list of\n * options (with a search field past eight of them), or presets and a\n * from / to pair for a date range.\n *\n * Design notes:\n * - Fully controlled. The page owns `filters` and `search` and hands\n * them back changed; the bar keeps no state but which popover is open.\n * That keeps the query in one place (the URL, a store) and lets the\n * list refetch from it.\n * - Chips are real buttons (the trigger, and the clear beside it), so\n * the bar is one tab stop per control and Escape closes a popover\n * back onto its chip.\n * - No scroll container of its own: chips wrap. `sticky` pins it to the\n * top of the nearest scroll pane on the surface it sits on.\n */\n\nexport interface FilterOption {\n value: string;\n label: string;\n}\n\ninterface FilterDefinitionBase {\n /** The key in `filters` this chip reads and writes. */\n key: string;\n /** The chip's word — \"Status\", \"Event type\". Sentence case. */\n label: string;\n}\n\nexport interface SelectFilterDefinition extends FilterDefinitionBase {\n type?: \"select\";\n options: readonly FilterOption[];\n /** Several values at once (`string[]`); one at a time by default. */\n multiple?: boolean;\n /**\n * A search field above the options. On by default past eight options,\n * off below; say so to override.\n */\n searchable?: boolean;\n /** The search field's placeholder. @default \"Search\" */\n searchPlaceholder?: string;\n}\n\n/** A relative preset for a date-range filter — \"Last 7 days\" is 168 hours. */\nexport interface DateRangePreset {\n label: string;\n hours: number;\n}\n\nexport interface DateRangeFilterDefinition extends FilterDefinitionBase {\n type: \"date-range\";\n /** The relative presets listed above the from / to pair. */\n presets?: readonly DateRangePreset[];\n}\n\nexport type FilterDefinition =\n SelectFilterDefinition | DateRangeFilterDefinition;\n\n/**\n * A date-range value. A preset stores an absolute ISO timestamp in `from`\n * (computed when chosen) and its label in `preset`; the from / to pair\n * stores plain `YYYY-MM-DD` dates. Either side may be open (`null`).\n */\nexport interface DateRangeValue {\n from: string | null;\n to: string | null;\n preset?: string;\n}\n\nexport type FilterValue = string | string[] | DateRangeValue;\n\n/** The page's filter state — one entry per definition key that is set. */\nexport type FilterValues = Record<string, FilterValue | undefined>;\n\n/** The relative presets a date-range chip lists when given none. */\nexport const FILTER_DATE_PRESETS: readonly DateRangePreset[] = [\n { label: \"Last hour\", hours: 1 },\n { label: \"Last 24 hours\", hours: 24 },\n { label: \"Last 7 days\", hours: 24 * 7 },\n { label: \"Last 30 days\", hours: 24 * 30 },\n];\n\n/** Past this many options a select chip shows a search field. */\nconst SEARCHABLE_PAST = 8;\n\n/** Whether a filter value counts as set — an empty string or list does not. */\nexport function isFilterSet(value: FilterValue | undefined): boolean {\n if (value == null) return false;\n if (typeof value === \"string\") return value !== \"\";\n if (Array.isArray(value)) return value.length > 0;\n return value.from != null || value.to != null;\n}\n\nconst DATE_ONLY = /^\\d{4}-\\d{2}-\\d{2}$/;\n\nfunction formatDay(value: string): string {\n // A plain date is a calendar day: format it in UTC so the day survives\n // every viewer timezone. A timestamp is a moment: format it locally.\n const dateOnly = DATE_ONLY.test(value);\n const date = new Date(dateOnly ? `${value}T00:00:00Z` : value);\n if (Number.isNaN(date.getTime())) return value;\n return new Intl.DateTimeFormat(\"en-US\", {\n month: \"short\",\n day: \"numeric\",\n ...(dateOnly ? { timeZone: \"UTC\" } : {}),\n }).format(date);\n}\n\n/** The words a set chip shows after its label. */\nexport function formatFilterValue(\n definition: FilterDefinition,\n value: FilterValue | undefined,\n): string {\n if (!isFilterSet(value) || value == null) return \"\";\n if (definition.type === \"date-range\") {\n const range = value as DateRangeValue;\n if (range.preset) return range.preset;\n if (range.from && range.to) {\n return `${formatDay(range.from)} to ${formatDay(range.to)}`;\n }\n if (range.from) return `From ${formatDay(range.from)}`;\n if (range.to) return `Until ${formatDay(range.to)}`;\n return \"\";\n }\n const labelOf = (item: string) =>\n definition.options.find((option) => option.value === item)?.label ?? item;\n if (Array.isArray(value)) return value.map(labelOf).join(\", \");\n return typeof value === \"string\" ? labelOf(value) : \"\";\n}\n\n/* --------------------------------- chips -------------------------------- */\n\nconst CHIP_TRIGGER =\n \"outline-pho-brand flex h-8 min-w-0 cursor-pointer items-center gap-1.5 rounded-full text-base transition-[color,background-color] duration-100 ease-linear focus-visible:outline-2 focus-visible:-outline-offset-2 [&>svg]:size-4 [&>svg]:shrink-0\";\n\nfunction FilterChip({\n definition,\n value,\n onChange,\n}: {\n definition: FilterDefinition;\n value: FilterValue | undefined;\n onChange: (next: FilterValue | undefined) => void;\n}) {\n const [open, setOpen] = useState(false);\n const focusRef = useRef<HTMLElement | null>(null);\n const set = isFilterSet(value);\n const display = formatFilterValue(definition, value);\n return (\n <Popover.Root open={open} onOpenChange={setOpen}>\n <span\n data-filter-chip={definition.key}\n data-set={set || undefined}\n className={cn(\n \"ring-pho-primary inline-flex max-w-full shrink-0 items-stretch rounded-full ring-1 ring-inset\",\n set ? \"bg-pho-secondary\" : \"bg-transparent\",\n )}\n >\n <Popover.Trigger\n aria-label={set ? undefined : `${definition.label} filter`}\n className={cn(\n CHIP_TRIGGER,\n \"hover:bg-pho-ghost-hover\",\n set ? \"pr-1.5 pl-3\" : \"pr-3 pl-2.5\",\n set ? \"rounded-r-none\" : \"\",\n )}\n >\n {set ? null : <IconPlus aria-hidden />}\n {set ? (\n <span className=\"flex min-w-0 items-baseline gap-1\">\n <span className=\"text-pho-description shrink-0\">\n {definition.label}:\n </span>\n <span className=\"text-pho-primary max-w-56 truncate font-medium\">\n {display}\n </span>\n </span>\n ) : (\n <span className=\"text-pho-secondary\">{definition.label}</span>\n )}\n </Popover.Trigger>\n {set ? (\n <button\n type=\"button\"\n aria-label={`Clear ${definition.label.toLowerCase()} filter`}\n onClick={() => onChange(undefined)}\n className={cn(\n CHIP_TRIGGER,\n \"text-pho-secondary hover:bg-pho-ghost-hover hover:text-pho-secondary-hover rounded-l-none pr-2.5 pl-1 [&>svg]:size-3.5\",\n )}\n >\n <IconX aria-hidden />\n </button>\n ) : null}\n </span>\n <Popover.Portal>\n <Popover.Positioner align=\"start\" sideOffset={6}>\n <Popover.Popup\n aria-label={`${definition.label} filter`}\n initialFocus={focusRef}\n className={cn(\n \"w-64 p-0\",\n definition.type === \"date-range\" && \"w-72\",\n )}\n >\n {definition.type === \"date-range\" ? (\n <DateRangePanel\n definition={definition}\n value={value as DateRangeValue | undefined}\n focusRef={focusRef}\n onChange={(next) => {\n onChange(next);\n if (next?.preset) setOpen(false);\n }}\n />\n ) : (\n <OptionsPanel\n definition={definition}\n value={value as string | string[] | undefined}\n focusRef={focusRef}\n onChange={(next) => {\n onChange(next);\n if (!definition.multiple) setOpen(false);\n }}\n />\n )}\n </Popover.Popup>\n </Popover.Positioner>\n </Popover.Portal>\n </Popover.Root>\n );\n}\n\nfunction OptionsPanel({\n definition,\n value,\n focusRef,\n onChange,\n}: {\n definition: SelectFilterDefinition;\n value: string | string[] | undefined;\n focusRef: RefObject<HTMLElement | null>;\n onChange: (next: string | string[] | undefined) => void;\n}) {\n const [query, setQuery] = useState(\"\");\n const searchable =\n definition.searchable ?? definition.options.length > SEARCHABLE_PAST;\n const chosen = new Set(\n value == null ? [] : Array.isArray(value) ? value : [value],\n );\n const needle = query.trim().toLowerCase();\n const options = needle\n ? definition.options.filter(\n (option) =>\n option.label.toLowerCase().includes(needle) ||\n option.value.toLowerCase().includes(needle),\n )\n : definition.options;\n const pick = (option: FilterOption) => {\n if (definition.multiple) {\n const next = new Set(chosen);\n if (next.has(option.value)) next.delete(option.value);\n else next.add(option.value);\n // Keep the definition's order, so the chip reads the same way\n // whatever order the values were chosen in.\n const ordered = definition.options\n .map((item) => item.value)\n .filter((item) => next.has(item));\n onChange(ordered.length > 0 ? ordered : undefined);\n return;\n }\n onChange(chosen.has(option.value) ? undefined : option.value);\n };\n return (\n <Command.Root\n label={`${definition.label} options`}\n // Without a search field the list itself takes focus, so the arrow\n // keys work from the first frame.\n ref={(node: HTMLDivElement | null) => {\n if (!searchable) focusRef.current = node;\n }}\n className=\"rounded-base\"\n >\n {searchable ? (\n <Command.InputGroup>\n <IconSearch\n aria-hidden\n className=\"text-pho-description pointer-events-none absolute left-3 size-4\"\n />\n <Command.Input\n ref={(node: HTMLInputElement | null) => {\n focusRef.current = node;\n }}\n value={query}\n onValueChange={setQuery}\n placeholder={definition.searchPlaceholder ?? \"Search\"}\n aria-label={`Search ${definition.label.toLowerCase()} options`}\n className=\"py-2 pr-3 pl-9 text-base\"\n />\n </Command.InputGroup>\n ) : null}\n <Command.List>\n <Command.Empty>Nothing matches.</Command.Empty>\n {options.map((option) => (\n <Command.Item\n key={option.value}\n value={option.value}\n title={option.label}\n aria-checked={chosen.has(option.value)}\n icon={chosen.has(option.value) ? <IconCheck aria-hidden /> : null}\n onSelect={() => pick(option)}\n />\n ))}\n </Command.List>\n </Command.Root>\n );\n}\n\nconst DATE_INPUT =\n \"bg-pho-primary ring-pho-primary text-pho-primary focus-visible:ring-pho-brand w-full min-w-0 rounded-sm px-2 py-1 text-base ring-1 transition-shadow duration-100 ease-linear outline-none ring-inset focus-visible:ring-2\";\n\nfunction DateRangePanel({\n definition,\n value,\n focusRef,\n onChange,\n}: {\n definition: DateRangeFilterDefinition;\n value: DateRangeValue | undefined;\n focusRef: RefObject<HTMLElement | null>;\n onChange: (next: DateRangeValue | undefined) => void;\n}) {\n const presets = definition.presets ?? FILTER_DATE_PRESETS;\n // A preset's `from` is a timestamp; the day field shows its date.\n const day = (side: string | null | undefined) =>\n side == null ? \"\" : DATE_ONLY.test(side) ? side : side.slice(0, 10);\n const setSide = (side: \"from\" | \"to\", next: string) => {\n // Editing either day leaves a preset behind: the pair is the value now,\n // and a preset's timestamp becomes the day it fell on.\n const range: DateRangeValue = {\n from: day(value?.from) || null,\n to: day(value?.to) || null,\n [side]: next || null,\n };\n onChange(range.from == null && range.to == null ? undefined : range);\n };\n return (\n <div className=\"flex flex-col\">\n <Command.Root\n label={`${definition.label} presets`}\n ref={(node: HTMLDivElement | null) => {\n focusRef.current = node;\n }}\n className=\"rounded-t-[inherit]\"\n >\n <Command.List>\n {presets.map((preset) => (\n <Command.Item\n key={preset.label}\n value={preset.label}\n title={preset.label}\n aria-checked={value?.preset === preset.label}\n icon={\n value?.preset === preset.label ? (\n <IconCheck aria-hidden />\n ) : null\n }\n onSelect={() =>\n onChange({\n from: new Date(\n Date.now() - preset.hours * 60 * 60 * 1000,\n ).toISOString(),\n to: null,\n preset: preset.label,\n })\n }\n />\n ))}\n </Command.List>\n </Command.Root>\n {/* The day fields stand outside the command list: its arrow-key\n handling would otherwise swallow the native date stepping. */}\n <div className=\"border-pho-secondary grid grid-cols-2 gap-2 border-t p-2\">\n <label className=\"text-pho-description flex min-w-0 flex-col gap-1 text-xs\">\n From\n <input\n type=\"date\"\n value={day(value?.from)}\n max={day(value?.to) || undefined}\n onChange={(event) => setSide(\"from\", event.target.value)}\n className={DATE_INPUT}\n />\n </label>\n <label className=\"text-pho-description flex min-w-0 flex-col gap-1 text-xs\">\n To\n <input\n type=\"date\"\n value={day(value?.to)}\n min={day(value?.from) || undefined}\n onChange={(event) => setSide(\"to\", event.target.value)}\n className={DATE_INPUT}\n />\n </label>\n </div>\n </div>\n );\n}\n\n/* ---------------------------------- bar --------------------------------- */\n\nexport interface FilterBarProps extends Omit<\n ComponentProps<\"div\">,\n \"children\"\n> {\n /** One chip per definition, in this order. */\n definitions: readonly FilterDefinition[];\n /** The values, keyed by definition key. Absent or empty means unset. */\n filters: FilterValues;\n /** The whole map, changed — set, cleared, or cleared all. */\n onFiltersChange: (next: FilterValues) => void;\n /** The search field's text. The field renders only with `onSearchChange`. */\n search?: string;\n onSearchChange?: (value: string) => void;\n /** A teaching phrase — \"Find an event by id\". @default \"Search\" */\n searchPlaceholder?: string;\n /** The field's accessible name when the placeholder is too short to be one. */\n searchLabel?: string;\n /**\n * Pin the bar to the top of the nearest scroll pane. It paints the\n * surface it sits on (`--pho-surface`) so rows scroll under it.\n */\n sticky?: boolean;\n /** Controls after the chips — a range picker, an export button. */\n children?: ReactNode;\n ref?: Ref<HTMLDivElement>;\n}\n\nexport function FilterBar({\n definitions,\n filters,\n onFiltersChange,\n search,\n onSearchChange,\n searchPlaceholder = \"Search\",\n searchLabel,\n sticky,\n className,\n children,\n ...props\n}: FilterBarProps) {\n const setCount = definitions.filter((definition) =>\n isFilterSet(filters[definition.key]),\n ).length;\n const change = (key: string, next: FilterValue | undefined) => {\n const rest = { ...filters };\n if (next === undefined) delete rest[key];\n else rest[key] = next;\n onFiltersChange(rest);\n };\n return (\n <div\n data-filter-bar=\"\"\n className={cn(\n \"flex min-w-0 flex-wrap items-center gap-2\",\n sticky &&\n \"sticky top-0 z-10 bg-[var(--pho-surface,var(--background-color-pho-page))] py-3\",\n className,\n )}\n {...props}\n >\n {onSearchChange != null ? (\n <InputGroup.Root size=\"sm\" className=\"w-64 max-w-full\">\n <InputGroup.Addon>\n <IconSearch />\n </InputGroup.Addon>\n <InputGroup.Input\n type=\"search\"\n value={search ?? \"\"}\n onChange={(event) => onSearchChange(event.target.value)}\n onKeyDown={(event) => {\n if (event.key !== \"Escape\") return;\n event.preventDefault();\n onSearchChange(\"\");\n }}\n placeholder={searchPlaceholder}\n aria-label={searchLabel ?? searchPlaceholder}\n />\n </InputGroup.Root>\n ) : null}\n {definitions.map((definition) => (\n <FilterChip\n key={definition.key}\n definition={definition}\n value={filters[definition.key]}\n onChange={(next) => change(definition.key, next)}\n />\n ))}\n {setCount > 1 ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => {\n const rest = { ...filters };\n for (const definition of definitions) delete rest[definition.key];\n onFiltersChange(rest);\n }}\n >\n Clear filters\n </Button>\n ) : null}\n {children}\n </div>\n );\n}\n\nFilterBar.displayName = \"FilterBar\";\n"],"x_google_ignoreList":[0],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { t as c } from "./cn-ChIgwEl3.js";
|
|
3
|
+
import "react";
|
|
4
|
+
import { jsx as n, jsxs as h } from "react/jsx-runtime";
|
|
5
|
+
var s = [
|
|
6
|
+
"chart-1",
|
|
7
|
+
"chart-2",
|
|
8
|
+
"chart-3",
|
|
9
|
+
"chart-4",
|
|
10
|
+
"chart-5",
|
|
11
|
+
"chart-6"
|
|
12
|
+
];
|
|
13
|
+
function p(a, e) {
|
|
14
|
+
const r = a ?? s[e % s.length];
|
|
15
|
+
return `var(--color-pho-${r.startsWith("chart-") ? r : `chart-${r}`})`;
|
|
16
|
+
}
|
|
17
|
+
function i({ color: a, kind: e = "rect", dashed: r }) {
|
|
18
|
+
return e === "line" ? /* @__PURE__ */ n("svg", {
|
|
19
|
+
"aria-hidden": !0,
|
|
20
|
+
width: "14",
|
|
21
|
+
height: "6",
|
|
22
|
+
className: "shrink-0",
|
|
23
|
+
children: /* @__PURE__ */ n("line", {
|
|
24
|
+
x1: "1",
|
|
25
|
+
x2: "13",
|
|
26
|
+
y1: "3",
|
|
27
|
+
y2: "3",
|
|
28
|
+
stroke: a,
|
|
29
|
+
strokeWidth: "2",
|
|
30
|
+
strokeLinecap: "round",
|
|
31
|
+
strokeDasharray: r ? "3 3" : void 0
|
|
32
|
+
})
|
|
33
|
+
}) : /* @__PURE__ */ n("span", {
|
|
34
|
+
"aria-hidden": !0,
|
|
35
|
+
className: "inline-block size-2 shrink-0 rounded-[2px]",
|
|
36
|
+
style: { background: a }
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function u({ items: a, className: e, ...r }) {
|
|
40
|
+
return /* @__PURE__ */ n("ul", {
|
|
41
|
+
className: c("text-pho-secondary flex flex-wrap gap-x-4 gap-y-1 text-xs", e),
|
|
42
|
+
...r,
|
|
43
|
+
children: a.map((t) => /* @__PURE__ */ h("li", {
|
|
44
|
+
className: "flex items-center gap-1.5",
|
|
45
|
+
children: [/* @__PURE__ */ n(i, {
|
|
46
|
+
color: t.color,
|
|
47
|
+
kind: t.kind,
|
|
48
|
+
dashed: t.dashed
|
|
49
|
+
}), t.label]
|
|
50
|
+
}, t.label))
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
p as i,
|
|
55
|
+
i as n,
|
|
56
|
+
s as r,
|
|
57
|
+
u as t
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
//# sourceMappingURL=legend-C0mU7dqd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legend-C0mU7dqd.js","names":[],"sources":["../../src/components/chart/color.ts","../../src/components/chart/legend.tsx"],"sourcesContent":["import type { ChartColor } from \"./types\";\n\n/** The categorical slots, in the order series take them. */\nexport const CHART_SLOTS = [\n \"chart-1\",\n \"chart-2\",\n \"chart-3\",\n \"chart-4\",\n \"chart-5\",\n \"chart-6\",\n] as const satisfies ReadonlyArray<ChartColor>;\n\n/**\n * The CSS value for a series colour: the named colour, or the categorical\n * slot at `index` when the series names none. Slots are an ordered,\n * validated set — a seventh series wraps, so fold a long tail into\n * \"Other\" before it gets there.\n */\nexport function chartColor(\n color: ChartColor | undefined,\n index: number,\n): string {\n const name = color ?? CHART_SLOTS[index % CHART_SLOTS.length]!;\n const token = name.startsWith(\"chart-\") ? name : `chart-${name}`;\n return `var(--color-pho-${token})`;\n}\n","import { type ComponentProps } from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** A legend or tooltip key mirrors its mark: a square for a bar, a stroke for a line. */\nexport type ChartSwatchKind = \"rect\" | \"line\";\n\nexport interface ChartSwatchProps {\n /** A CSS colour value — what `chartColor` returns. */\n color: string;\n kind?: ChartSwatchKind;\n dashed?: boolean;\n}\n\nexport function ChartSwatch({\n color,\n kind = \"rect\",\n dashed,\n}: ChartSwatchProps) {\n if (kind === \"line\") {\n return (\n <svg aria-hidden width=\"14\" height=\"6\" className=\"shrink-0\">\n <line\n x1=\"1\"\n x2=\"13\"\n y1=\"3\"\n y2=\"3\"\n stroke={color}\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeDasharray={dashed ? \"3 3\" : undefined}\n />\n </svg>\n );\n }\n return (\n <span\n aria-hidden\n className=\"inline-block size-2 shrink-0 rounded-[2px]\"\n style={{ background: color }}\n />\n );\n}\n\nexport interface ChartLegendItem {\n label: string;\n /** A CSS colour value — what `chartColor` returns. */\n color: string;\n kind?: ChartSwatchKind;\n dashed?: boolean;\n}\n\nexport interface ChartLegendProps extends Omit<\n ComponentProps<\"ul\">,\n \"children\"\n> {\n items: ReadonlyArray<ChartLegendItem>;\n}\n\n/**\n * The legend a chart of two or more series carries below the plot: a key\n * per series, in series order, wrapping when the row runs out. Text stays\n * in the UI ink; the swatch alone carries the colour.\n */\nexport function ChartLegend({ items, className, ...props }: ChartLegendProps) {\n return (\n <ul\n className={cn(\n \"text-pho-secondary flex flex-wrap gap-x-4 gap-y-1 text-xs\",\n className,\n )}\n {...props}\n >\n {items.map((item) => (\n <li key={item.label} className=\"flex items-center gap-1.5\">\n <ChartSwatch\n color={item.color}\n kind={item.kind}\n dashed={item.dashed}\n />\n {item.label}\n </li>\n ))}\n </ul>\n );\n}\n"],"mappings":";;;;AAGA,IAAa,IAAc;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAQA,SAAgB,EACd,GACA,GACQ;AACR,QAAM,IAAO,KAAS,EAAY,IAAQ,EAAY,MAAA;AAEtD,SAAO,mBADO,EAAK,WAAW,QAAQ,IAAI,IAAO,SAAS,CAAA,EAAA;AAE5D;ACZA,SAAgB,EAAY,EAC1B,OAAA,GACA,MAAA,IAAO,QACP,QAAA,EAAA,GACmB;AACnB,SAAI,MAAS,SAET,gBAAA,EAAC,OAAD;AAAA,IAAK,eAAA;AAAA,IAAY,OAAM;AAAA,IAAK,QAAO;AAAA,IAAI,WAAU;AAAA,IAC/C,UAAA,gBAAA,EAAC,QAAD;AAAA,MACE,IAAG;AAAA,MACH,IAAG;AAAA,MACH,IAAG;AAAA,MACH,IAAG;AAAA,MACH,QAAQ;AAAA,MACR,aAAY;AAAA,MACZ,eAAc;AAAA,MACd,iBAAiB,IAAS,QAAQ;AAAA,IACnC,CAAA;AAAA,EACE,CAAA,IAIP,gBAAA,EAAC,QAAD;AAAA,IACE,eAAA;AAAA,IACA,WAAU;AAAA,IACV,OAAO,EAAE,YAAY,EAAM;AAAA,EAC5B,CAAA;AAEL;AAsBA,SAAgB,EAAY,EAAE,OAAA,GAAO,WAAA,GAAW,GAAG,EAAA,GAA2B;AAC5E,SACE,gBAAA,EAAC,MAAD;AAAA,IACE,WAAW,EACT,6DACA,CACF;AAAA,IACA,GAAI;AAAA,IAEH,UAAA,EAAM,IAAA,CAAK,MACV,gBAAA,EAAC,MAAD;AAAA,MAAqB,WAAU;AAAA,MAA/B,UAAA,CACE,gBAAA,EAAC,GAAD;AAAA,QACE,OAAO,EAAK;AAAA,QACZ,MAAM,EAAK;AAAA,QACX,QAAQ,EAAK;AAAA,MACd,CAAA,GACA,EAAK,KACJ;AAAA,IAPK,GAAA,EAAK,KAOV,CACL;AAAA,EACC,CAAA;AAER"}
|