@protobase/ui 0.12.10 → 0.12.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/app/action-icon.js +3 -7
  2. package/dist/app/displays/cards-display.js +43 -10
  3. package/dist/app/displays/list-displays.js +5 -3
  4. package/dist/app/field-editor.js +4 -0
  5. package/dist/app/named-icon.js +13 -0
  6. package/dist/app/pages/field-value.js +4 -4
  7. package/dist/app/record-page.js +4 -3
  8. package/dist/app/value-badge.js +14 -0
  9. package/dist/data-grid/cells.js +9 -6
  10. package/dist/filter-panel/combobox-options.js +12 -0
  11. package/dist/filter-panel/facet-combobox.js +131 -0
  12. package/dist/filter-panel/facet-group.js +1 -1
  13. package/dist/filter-panel/filter-menu.js +7 -2
  14. package/dist/filter-panel/filter-panel.js +33 -24
  15. package/dist/filter-panel/filter-state.js +23 -11
  16. package/dist/filter-panel/range-filter.js +3 -3
  17. package/dist/index.js +1 -1
  18. package/dist/lib/readable-text.js +23 -0
  19. package/dist/live/columns-from-model.js +3 -0
  20. package/dist/live/display-value.js +6 -3
  21. package/dist/live/filter-string.js +18 -9
  22. package/dist/live/reference-options.js +14 -0
  23. package/dist/live/use-filter-config.js +64 -35
  24. package/dist/live/use-reference-search.js +53 -0
  25. package/dist/live/value-color.js +8 -0
  26. package/dist/live/view-fields.js +31 -14
  27. package/dist/package.js +1 -1
  28. package/dist/primitives/badge.js +12 -3
  29. package/dist/shell/assets/app-BKltU-WD.js +65 -0
  30. package/dist/shell/assets/{index-BIjqw3oN.css → index-CP0s3cFI.css} +1 -1
  31. package/dist/shell/assets/{index-CuBoFEnG.js → index-D1ngvDop.js} +1 -1
  32. package/dist/shell/assets/{shared-protobase-ui-CR4VI05-.js → shared-protobase-ui-Cv4rF2fz.js} +1 -1
  33. package/dist/shell/index.html +4 -4
  34. package/dist/shell/protobase.shell.json +1 -1
  35. package/package.json +4 -4
  36. package/dist/shell/assets/app-CUxGSH9m.js +0 -65
@@ -0,0 +1,23 @@
1
+ //#region src/lib/readable-text.ts
2
+ const luminance = (hex) => {
3
+ const digits = hex.slice(1);
4
+ const full = digits.length === 3 ? [...digits].map((digit) => digit + digit).join("") : digits;
5
+ const [r, g, b] = [
6
+ 0,
7
+ 2,
8
+ 4
9
+ ].map((at) => {
10
+ const channel = Number.parseInt(full.slice(at, at + 2), 16) / 255;
11
+ return channel <= .03928 ? channel / 12.92 : ((channel + .055) / 1.055) ** 2.4;
12
+ });
13
+ return .2126 * r + .7152 * g + .0722 * b;
14
+ };
15
+ const light = "#ffffff";
16
+ const dark = "#111827";
17
+ /** The text colour, white or near black, that reads best on a hex background. */
18
+ const readableOn = (background) => {
19
+ const shade = luminance(background);
20
+ return 1.05 / (shade + .05) >= (shade + .05) / (luminance(dark) + .05) ? light : dark;
21
+ };
22
+ //#endregion
23
+ export { readableOn };
@@ -82,6 +82,9 @@ const columnsFromModel = (resource, view, highlight = []) => {
82
82
  tones: Object.fromEntries((field.enumValues ?? []).map((value) => [value, toneFor(value)])),
83
83
  labels
84
84
  },
85
+ ...field.type === "enum" && viewField?.valueColors && { colors: viewField.valueColors },
86
+ ...field.type === "enum" && viewField?.badgeShape && { shape: viewField.badgeShape },
87
+ ...viewField?.suffix !== void 0 && { suffix: viewField.suffix },
85
88
  ...kind === "money" && {
86
89
  currency: "EUR",
87
90
  value: (row) => row[name] == null ? null : currencyCents(row[name])
@@ -2,7 +2,8 @@ import { formatDate, formatDateTime } from "../format/date.js";
2
2
  import { fileShown } from "./file-values.js";
3
3
  import { enumLabel } from "@protobase/schema";
4
4
  //#region src/live/display-value.ts
5
- /** A read-only rendering of a stored value, following the view's prefix, decimals, format and value label hints. */
5
+ const affixed = (text, hints) => `${hints.prefix ?? ""}${text}${hints.suffix ?? ""}`;
6
+ /** A read-only rendering of a stored value, following the view's prefix, suffix, decimals, format and value label hints. */
6
7
  const displayValue = (field, value, hints = {}) => {
7
8
  if (value === null || value === void 0 || value === "") return "—";
8
9
  switch (field.type) {
@@ -14,11 +15,13 @@ const displayValue = (field, value, hints = {}) => {
14
15
  case "decimal": {
15
16
  const text = hints.decimals === void 0 ? String(value) : Number(value).toFixed(hints.decimals);
16
17
  if (hints.format === "percent") return `${Number(value)}%`;
17
- return `${hints.prefix ?? ""}${Number(text).toLocaleString("en-IE", {
18
+ return affixed(Number(text).toLocaleString("en-IE", {
18
19
  minimumFractionDigits: hints.decimals ?? 0,
19
20
  maximumFractionDigits: hints.decimals ?? 20
20
- })}`;
21
+ }), hints);
21
22
  }
23
+ case "integer":
24
+ case "bigint": return affixed(String(value), hints);
22
25
  default: return typeof value === "object" ? JSON.stringify(value) : String(value);
23
26
  }
24
27
  };
@@ -62,12 +62,15 @@ const stateToClauses = (config, state, now = /* @__PURE__ */ new Date()) => {
62
62
  if (values.length === 0) return [];
63
63
  return [values.length === 1 ? w.eq(group.id, values[0]) : w.in(group.id, values)];
64
64
  });
65
- const range = config.range && state.range ? [w.gte(config.range.id, state.range[0]), w.lte(config.range.id, state.range[1])] : [];
65
+ const ranges = (config.ranges ?? []).flatMap((range) => {
66
+ const span = state.ranges?.[range.id];
67
+ return span ? [w.gte(range.id, span[0]), w.lte(range.id, span[1])] : [];
68
+ });
66
69
  const date = config.dateField && state.date ? dateClauses(config.dateField, state.date, now, config.dateKind) : [];
67
70
  const toggles = (config.toggles ?? []).filter((toggle) => state.toggles.includes(toggle.id)).map((toggle) => w.eq(toggle.id, true));
68
71
  return [
69
72
  ...facets,
70
- ...range,
73
+ ...ranges,
71
74
  ...date,
72
75
  ...toggles
73
76
  ];
@@ -103,7 +106,8 @@ const textToState = (config, text, now = /* @__PURE__ */ new Date()) => {
103
106
  const extras = [];
104
107
  const facetIds = new Set(config.facets.map((group) => group.id));
105
108
  const toggleIds = new Set((config.toggles ?? []).map((toggle) => toggle.id));
106
- const bounds = { clauses: [] };
109
+ const rangeIds = new Set((config.ranges ?? []).map((range) => range.id));
110
+ const bounds = /* @__PURE__ */ new Map();
107
111
  let dateClause;
108
112
  let todayEnd;
109
113
  for (const clause of parsed.ast ? flatten(parsed.ast) : []) if (clause.kind === "compare" && facetIds.has(fieldName(clause)) && clause.op === "=" && literalText(clause.value) !== void 0) state.facets[fieldName(clause)] = [...state.facets[fieldName(clause)] ?? [], literalText(clause.value)];
@@ -113,18 +117,23 @@ const textToState = (config, text, now = /* @__PURE__ */ new Date()) => {
113
117
  dateClause = clause;
114
118
  } else if (clause.kind === "compare" && config.dateField === fieldName(clause) && clause.op === "<" && (clause.value.kind === "timestamp" || clause.value.kind === "string") && periodStart("today", now).startsWith(clause.value.value.slice(0, 10))) todayEnd = clause;
115
119
  else if (clause.kind === "compare" && toggleIds.has(fieldName(clause)) && clause.op === "=" && clause.value.kind === "boolean" && clause.value.value) state.toggles.push(fieldName(clause));
116
- else if (clause.kind === "compare" && config.range?.id === fieldName(clause) && (clause.op === ">=" || clause.op === "<=") && clause.value.kind === "number") {
117
- if (clause.op === ">=") bounds.low = clause.value.value;
118
- else bounds.high = clause.value.value;
119
- bounds.clauses.push(clause);
120
+ else if (clause.kind === "compare" && rangeIds.has(fieldName(clause)) && (clause.op === ">=" || clause.op === "<=") && clause.value.kind === "number") {
121
+ const range = bounds.get(fieldName(clause)) ?? { clauses: [] };
122
+ if (clause.op === ">=") range.low = clause.value.value;
123
+ else range.high = clause.value.value;
124
+ range.clauses.push(clause);
125
+ bounds.set(fieldName(clause), range);
120
126
  } else extras.push(clause);
121
127
  if (state.date === "yesterday" && !todayEnd) {
122
128
  extras.push(dateClause);
123
129
  state.date = void 0;
124
130
  }
125
131
  if (todayEnd && state.date !== "yesterday") extras.push(todayEnd);
126
- if (bounds.low !== void 0 && bounds.high !== void 0) state.range = [bounds.low, bounds.high];
127
- else extras.push(...bounds.clauses);
132
+ for (const [id, range] of bounds) if (range.low !== void 0 && range.high !== void 0) state.ranges = {
133
+ ...state.ranges,
134
+ [id]: [range.low, range.high]
135
+ };
136
+ else extras.push(...range.clauses);
128
137
  return {
129
138
  ok: true,
130
139
  state,
@@ -0,0 +1,14 @@
1
+ //#region src/live/reference-options.ts
2
+ /** Records found by a search as options, with the count of each that the facet counts know. */
3
+ const matchOptions = (matches, counted) => {
4
+ const counts = new Map(counted.map((option) => [option.value, option.count]));
5
+ return matches.map(([value, label]) => ({
6
+ value,
7
+ label,
8
+ ...counts.get(value) !== void 0 && { count: counts.get(value) }
9
+ }));
10
+ };
11
+ /** The keys a facet needs names for: the values it counted and those chosen, which may lie outside the counted ones. */
12
+ const labelKeys = (counted, chosen) => [.../* @__PURE__ */ new Set([...counted, ...chosen])];
13
+ //#endregion
14
+ export { labelKeys, matchOptions };
@@ -3,21 +3,28 @@ import { keys } from "../data/query-keys.js";
3
3
  import { useClient } from "../data/api-provider.js";
4
4
  import { humanize } from "./naming.js";
5
5
  import { fetchLabels } from "./relation-labels.js";
6
+ import { setRange } from "../filter-panel/filter-state.js";
6
7
  import { stateToText } from "./filter-string.js";
7
- import { useHistogram } from "../data/use-aggregates.js";
8
- import { widgetFields } from "./view-fields.js";
8
+ import { labelKeys, matchOptions } from "./reference-options.js";
9
+ import { useReferenceSearch } from "./use-reference-search.js";
10
+ import { rangeHistogram, widgetFields } from "./view-fields.js";
9
11
  import { useQueries, useQueryClient } from "@tanstack/react-query";
10
12
  import { enumLabel } from "@protobase/schema";
11
13
  //#region src/live/use-filter-config.ts
14
+ const histogramBuckets = 24;
12
15
  /**
13
- * The panel's config for a view: facet counts come from `:facets`, each computed with every
14
- * other active filter applied but not its own, so choosing one value does not hide the rest.
16
+ * The panel's config for a view: facet counts come from `:facets` and each range's bounds from `:histogram`, each
17
+ * computed with every other active filter applied but not its own, so choosing one value does not hide the rest.
15
18
  */
16
19
  const useFilterConfig = ({ model, resources, views, view, state, extras }) => {
17
20
  const client = useClient();
18
21
  const queryClient = useQueryClient();
19
22
  const skeleton = widgetFields(view, model);
20
23
  const label = (name) => view?.fields[name]?.label ?? humanize(name);
24
+ const targetOf = (name) => {
25
+ const field = model.fields[name];
26
+ return field?.type === "relation" && field.relation ? resources[field.relation.resource] : void 0;
27
+ };
21
28
  const facetQueries = useQueries({ queries: skeleton.facets.map((group) => {
22
29
  const filter = stateToText(skeleton, {
23
30
  ...state,
@@ -35,44 +42,66 @@ const useFilterConfig = ({ model, resources, views, view, state, extras }) => {
35
42
  };
36
43
  }) });
37
44
  const labelQueries = useQueries({ queries: skeleton.facets.map((group, index) => {
38
- const field = model.fields[group.id];
39
- const target = field?.type === "relation" && field.relation ? resources[field.relation.resource] : void 0;
40
- const ids = (facetQueries[index]?.data ?? []).map((facet) => String(facet.value));
45
+ const target = targetOf(group.id);
46
+ const ids = labelKeys((facetQueries[index]?.data ?? []).flatMap((facet) => facet.value === null ? [] : [String(facet.value)]), state.facets[group.id] ?? []);
41
47
  return {
42
48
  queryKey: keys.labels(`facet:${model.name}.${group.id}`, ids.join(",")),
43
49
  enabled: Boolean(target) && ids.length > 0,
44
50
  queryFn: async () => [...await fetchLabels(queryClient, client, target, ids, views[target.name])]
45
51
  };
46
52
  }) });
47
- const rangeFilter = stateToText({
48
- ...skeleton,
49
- range: void 0
50
- }, state, extras);
51
- const histogram = useHistogram(model.name, skeleton.range?.id ?? "", rangeFilter, 24, Boolean(skeleton.range));
52
- const prefix = skeleton.range ? view?.fields[skeleton.range.id]?.prefix : void 0;
53
+ const searches = useReferenceSearch(skeleton.facets.map((group) => ({
54
+ id: group.id,
55
+ target: group.combobox ? targetOf(group.id) : void 0
56
+ })), views);
57
+ const ranges = skeleton.ranges ?? [];
58
+ const histogramQueries = useQueries({ queries: ranges.map((range) => {
59
+ const filter = stateToText(skeleton, setRange(state, range.id, void 0), extras);
60
+ const buckets = rangeHistogram(view, range.id) ? histogramBuckets : 1;
61
+ return {
62
+ queryKey: keys.histogram(model.name, range.id, buckets, filter),
63
+ queryFn: () => client.histogram(model.name, range.id, {
64
+ filter,
65
+ buckets
66
+ })
67
+ };
68
+ }) });
69
+ const facetGroup = (group, index) => {
70
+ const field = model.fields[group.id];
71
+ const names = new Map(labelQueries[index]?.data ?? []);
72
+ const counted = (facetQueries[index]?.data ?? []).flatMap((facet) => facet.value === null ? [] : [{
73
+ value: String(facet.value),
74
+ label: field?.type === "enum" ? enumLabel(String(facet.value), view?.fields[group.id]?.valueLabels) : names.get(String(facet.value)) ?? String(facet.value),
75
+ count: facet.count
76
+ }]);
77
+ const search = searches[index];
78
+ return {
79
+ ...group,
80
+ label: label(group.id),
81
+ options: search.matches ? matchOptions(search.matches, counted) : counted,
82
+ ...names.size > 0 && { labels: Object.fromEntries(names) },
83
+ ...search.onSearch && { onSearch: search.onSearch }
84
+ };
85
+ };
86
+ const rangeConfig = (range, index) => {
87
+ const data = histogramQueries[index]?.data;
88
+ if (!data || data.min === null || data.max === null) return [];
89
+ const hints = view?.fields[range.id];
90
+ const prefix = hints?.prefix;
91
+ const suffix = hints?.suffix ?? "";
92
+ return [{
93
+ ...range,
94
+ label: label(range.id),
95
+ min: Math.floor(data.min),
96
+ max: Math.ceil(data.max),
97
+ step: Math.max(1, Math.round((data.max - data.min) / 100)),
98
+ histogram: rangeHistogram(view, range.id) ? data.buckets.map((bucket) => bucket.count) : [],
99
+ format: (value) => `${prefix === "€" ? formatMoney(value * 100).replace(".00", "") : String(value)}${suffix}`
100
+ }];
101
+ };
53
102
  return {
54
- facets: skeleton.facets.map((group, index) => {
55
- const field = model.fields[group.id];
56
- const names = new Map(labelQueries[index]?.data ?? []);
57
- return {
58
- ...group,
59
- label: label(group.id),
60
- options: (facetQueries[index]?.data ?? []).flatMap((facet) => facet.value === null ? [] : [{
61
- value: String(facet.value),
62
- label: field?.type === "enum" ? enumLabel(String(facet.value), view?.fields[group.id]?.valueLabels) : names.get(String(facet.value)) ?? String(facet.value),
63
- count: facet.count
64
- }])
65
- };
66
- }),
67
- ...skeleton.range && histogram.data && histogram.data.min !== null && histogram.data.max !== null && { range: {
68
- ...skeleton.range,
69
- label: label(skeleton.range.id),
70
- min: Math.floor(histogram.data.min),
71
- max: Math.ceil(histogram.data.max),
72
- step: Math.max(1, Math.round((histogram.data.max - histogram.data.min) / 100)),
73
- histogram: histogram.data.buckets.map((bucket) => bucket.count),
74
- format: (value) => prefix === "€" ? formatMoney(value * 100).replace(".00", "") : String(value)
75
- } },
103
+ facets: skeleton.facets.map(facetGroup),
104
+ ranges: ranges.flatMap(rangeConfig),
76
105
  ...skeleton.dateField && {
77
106
  datePresets: skeleton.datePresets ?? true,
78
107
  dateField: skeleton.dateField,
@@ -0,0 +1,53 @@
1
+ import { useClient } from "../data/api-provider.js";
2
+ import { labelFieldOf } from "./model-helpers.js";
3
+ import { useState } from "react";
4
+ import { keepPreviousData, useQueries } from "@tanstack/react-query";
5
+ import { createWhere, printFilter } from "@protobase/schema";
6
+ //#region src/live/use-reference-search.ts
7
+ const where = createWhere();
8
+ /**
9
+ * The search text typed into each facet's combobox and the records of its target resource that match it. A facet
10
+ * without a target, or whose target has no search fields or a composite key, gets nothing: its options are filtered
11
+ * by name instead.
12
+ */
13
+ const useReferenceSearch = (targets, views) => {
14
+ const client = useClient();
15
+ const [texts, setTexts] = useState({});
16
+ const searchable = (target) => Boolean(target?.search?.length) && target.primaryKey.length === 1;
17
+ const results = useQueries({ queries: targets.map(({ id, target }) => {
18
+ const text = (texts[id] ?? "").trim();
19
+ return {
20
+ queryKey: [
21
+ "reference-search",
22
+ id,
23
+ target?.name ?? "",
24
+ text
25
+ ],
26
+ enabled: searchable(target) && text !== "",
27
+ placeholderData: keepPreviousData,
28
+ queryFn: async () => {
29
+ const keyField = target.primaryKey[0];
30
+ const labelField = labelFieldOf(target, views[target.name]);
31
+ return (await client.list(target.name, {
32
+ filter: printFilter(where.search(text)),
33
+ fields: [keyField, labelField],
34
+ pageSize: 20
35
+ })).items.map((item) => [String(item[keyField]), String(item[labelField])]);
36
+ }
37
+ };
38
+ }) });
39
+ return targets.map(({ id, target }, index) => {
40
+ if (!searchable(target)) return {};
41
+ const onSearch = (text) => setTexts((current) => ({
42
+ ...current,
43
+ [id]: text
44
+ }));
45
+ if ((texts[id] ?? "").trim() === "") return { onSearch };
46
+ return {
47
+ onSearch,
48
+ matches: results[index]?.data ?? []
49
+ };
50
+ });
51
+ };
52
+ //#endregion
53
+ export { useReferenceSearch };
@@ -0,0 +1,8 @@
1
+ //#region src/live/value-color.ts
2
+ /** The colour a view gives a value's badge, if any. */
3
+ const valueColor = (colors, value) => {
4
+ const key = String(value);
5
+ return colors && Object.hasOwn(colors, key) ? colors[key] : void 0;
6
+ };
7
+ //#endregion
8
+ export { valueColor };
@@ -1,25 +1,40 @@
1
1
  //#region src/live/view-fields.ts
2
- /** The widgets of a view in the shape `filter-string` talks to; options and counts are filled in later. */
2
+ const numberTypes = [
3
+ "integer",
4
+ "bigint",
5
+ "decimal"
6
+ ];
7
+ const isNumber = (model, field) => {
8
+ const type = model?.fields[field]?.type;
9
+ return type !== void 0 && numberTypes.includes(type);
10
+ };
11
+ /** A filter on a number field is a range slider, whichever widget the view asked for. */
12
+ const rangeOf = (widget, model) => widget.kind === "range" || widget.kind === "facets" && isNumber(model, widget.field);
13
+ const emptyRange = (field) => ({
14
+ id: field,
15
+ label: field,
16
+ min: 0,
17
+ max: 0,
18
+ step: 1,
19
+ histogram: [],
20
+ format: String
21
+ });
22
+ /**
23
+ * The widgets of a view in the shape `filter-string` talks to; options and counts are filled in later. Facets on a
24
+ * reference to another resource's records are a combobox, which searches those records.
25
+ */
3
26
  const widgetFields = (view, model) => {
4
27
  const widgets = view?.filters ?? [];
5
- const range = widgets.find((widget) => widget.kind === "range");
6
28
  const date = widgets.find((widget) => widget.kind === "dateRange");
7
29
  return {
8
- facets: widgets.flatMap((widget) => widget.kind === "facets" ? [{
30
+ facets: widgets.flatMap((widget) => widget.kind === "facets" && !rangeOf(widget, model) ? [{
9
31
  id: widget.field,
10
32
  label: widget.field,
11
33
  options: [],
12
- searchable: widget.search
34
+ searchable: widget.search,
35
+ ...model?.fields[widget.field]?.type === "relation" && { combobox: true }
13
36
  }] : []),
14
- ...range && { range: {
15
- id: range.field,
16
- label: range.field,
17
- min: 0,
18
- max: 0,
19
- step: 1,
20
- histogram: [],
21
- format: String
22
- } },
37
+ ranges: widgets.flatMap((widget) => rangeOf(widget, model) ? [emptyRange(widget.field)] : []),
23
38
  ...date && {
24
39
  dateField: date.field,
25
40
  dateKind: model?.fields[date.field]?.type === "date" ? "date" : "timestamp",
@@ -31,5 +46,7 @@ const widgetFields = (view, model) => {
31
46
  }] : [])
32
47
  };
33
48
  };
49
+ /** Whether a range draws its histogram: only a `range` widget that asks for one. */
50
+ const rangeHistogram = (view, field) => (view?.filters ?? []).some((widget) => widget.kind === "range" && widget.field === field && widget.histogram);
34
51
  //#endregion
35
- export { widgetFields };
52
+ export { rangeHistogram, widgetFields };
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.12.10";
2
+ var version = "0.12.12";
3
3
  var protobase = { "pluginApi": 1 };
4
4
  //#endregion
5
5
  export { protobase, version };
@@ -1,4 +1,5 @@
1
1
  import { cn } from "../lib/cn.js";
2
+ import { readableOn } from "../lib/readable-text.js";
2
3
  import { jsx, jsxs } from "react/jsx-runtime";
3
4
  //#region src/primitives/badge.tsx
4
5
  const tones = {
@@ -17,9 +18,17 @@ const dots = {
17
18
  red: "bg-danger",
18
19
  violet: "bg-violet"
19
20
  };
20
- const Badge = ({ tone = "neutral", dot = true, children, className }) => /* @__PURE__ */ jsxs("span", {
21
- className: cn("inline-flex h-5 items-center gap-1.5 rounded-full px-2 text-xs font-medium whitespace-nowrap", tones[tone], className),
22
- children: [dot && /* @__PURE__ */ jsx("span", { className: cn("size-1.5 rounded-full", dots[tone]) }), children]
21
+ const arrow = "polygon(0 0, calc(100% - 0.625rem) 0, 100% 50%, calc(100% - 0.625rem) 100%, 0 100%)";
22
+ const Badge = ({ tone = "neutral", dot = true, color, shape, children, className }) => /* @__PURE__ */ jsxs("span", {
23
+ style: {
24
+ ...color !== void 0 && {
25
+ backgroundColor: color,
26
+ color: readableOn(color)
27
+ },
28
+ ...shape === "arrow" && { clipPath: arrow }
29
+ },
30
+ className: cn(shape === "arrow" ? "inline-flex h-5 items-center gap-1.5 rounded-l-sm pr-4 pl-2 text-xs font-semibold whitespace-nowrap" : "inline-flex h-5 items-center gap-1.5 rounded-full px-2 text-xs font-medium whitespace-nowrap", color === void 0 && tones[tone], className),
31
+ children: [dot && color === void 0 && shape === void 0 && /* @__PURE__ */ jsx("span", { className: cn("size-1.5 rounded-full", dots[tone]) }), children]
23
32
  });
24
33
  //#endregion
25
34
  export { Badge };