@protobase/ui 0.12.7 → 0.12.8

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.
@@ -20,6 +20,7 @@ const DisplaySwitch = ({ view, value, onChange }) => {
20
20
  if (choices.length < 2) return null;
21
21
  return /* @__PURE__ */ jsx(Segmented, {
22
22
  label: "Display",
23
+ size: "md",
23
24
  value,
24
25
  onChange,
25
26
  options: choices.map((choice) => ({
@@ -40,7 +41,7 @@ const SortMenu = ({ model, view, display, orderBy, onChange }) => {
40
41
  "aria-label": "Sort",
41
42
  value: `${current.columnId} ${current.direction}`,
42
43
  onChange: (event) => onChange(event.target.value),
43
- className: "h-7 rounded-md border border-border-strong bg-background px-2 text-xs shadow-sm focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20",
44
+ className: "h-8 rounded-md border border-border-strong bg-background px-2.5 text-[13px] shadow-sm focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20",
44
45
  children: names.flatMap((name) => directions.map(([direction, word]) => /* @__PURE__ */ jsxs("option", {
45
46
  value: `${name} ${direction}`,
46
47
  children: [
@@ -15,6 +15,7 @@ import { pinnedStorageKey } from "../data-grid/pinned-storage.js";
15
15
  import { DataGrid } from "../data-grid/data-grid.js";
16
16
  import { ActiveFilterChips } from "../filter-panel/active-filter-chips.js";
17
17
  import { activeChips, emptyFilterState, removeChip } from "../filter-panel/filter-state.js";
18
+ import { ToolbarRow } from "../filter-panel/toolbar-row.js";
18
19
  import { FilterPanel } from "../filter-panel/filter-panel.js";
19
20
  import { columnsFromModel } from "../live/columns-from-model.js";
20
21
  import { stateToText, textToState } from "../live/filter-string.js";
@@ -124,11 +125,29 @@ const LoadedList = ({ model, address }) => {
124
125
  setFilter(state, text ? [...others, where.search(text)] : others);
125
126
  };
126
127
  const plural = view?.names?.plural ?? humanize(resource);
128
+ const sortMenu = display && /* @__PURE__ */ jsx(SortMenu, {
129
+ model,
130
+ view,
131
+ display,
132
+ orderBy,
133
+ onChange: (next) => set({ orderBy: next })
134
+ });
135
+ const searchBox = model.search?.length ? /* @__PURE__ */ jsx(ListSearch, {
136
+ value: search?.kind === "search" ? search.text : "",
137
+ onCommit: commitSearch,
138
+ placeholder: `Search ${plural.toLowerCase()}`
139
+ }) : null;
127
140
  const panel = /* @__PURE__ */ jsx(FilterPanel, {
128
141
  layout,
129
142
  config,
130
143
  value: state,
131
- onChange: setFilter
144
+ onChange: setFilter,
145
+ leading: sortMenu,
146
+ trailing: searchBox
147
+ });
148
+ const desktopToolbar = layout === "bar" ? panel : /* @__PURE__ */ jsx(ToolbarRow, {
149
+ groups: [sortMenu ? [sortMenu] : []],
150
+ trailing: searchBox
132
151
  });
133
152
  const chips = activeChips(config, state);
134
153
  const phoneFilters = /* @__PURE__ */ jsxs("div", {
@@ -217,18 +236,8 @@ const LoadedList = ({ model, address }) => {
217
236
  value: display?.name ?? tableDisplay,
218
237
  onChange: (name) => set({ display: name })
219
238
  }),
220
- display && /* @__PURE__ */ jsx(SortMenu, {
221
- model,
222
- view,
223
- display,
224
- orderBy,
225
- onChange: (next) => set({ orderBy: next })
226
- }),
227
- model.search?.length ? /* @__PURE__ */ jsx(ListSearch, {
228
- value: search?.kind === "search" ? search.text : "",
229
- onCommit: commitSearch,
230
- placeholder: `Search ${plural.toLowerCase()}`
231
- }) : null,
239
+ !desktop && sortMenu,
240
+ !desktop && searchBox,
232
241
  can(permissions, "create") && /* @__PURE__ */ jsxs(Button, {
233
242
  variant: "primary",
234
243
  onClick: () => go({
@@ -258,7 +267,7 @@ const LoadedList = ({ model, address }) => {
258
267
  noun: plural.toLowerCase(),
259
268
  filter: parsed.ok ? filterText : ""
260
269
  }),
261
- !desktop ? phoneFilters : layout === "bar" && panel,
270
+ desktop ? desktopToolbar : phoneFilters,
262
271
  desktop && layout === "panel" ? /* @__PURE__ */ jsxs("div", {
263
272
  className: "flex min-h-0 flex-1 gap-4",
264
273
  children: [panel, shown]
@@ -17,7 +17,7 @@ const ListSearch = ({ value, onCommit, placeholder }) => {
17
17
  onCommit
18
18
  ]);
19
19
  return /* @__PURE__ */ jsx(Input, {
20
- wrapperClassName: "min-w-0 flex-1 sm:w-72 sm:flex-none",
20
+ wrapperClassName: "min-w-0 flex-1 sm:w-72 sm:flex-initial",
21
21
  leading: /* @__PURE__ */ jsx(Search, { className: "size-4" }),
22
22
  placeholder,
23
23
  "aria-label": "Search",
@@ -6,10 +6,12 @@ import { FilterMenu } from "./filter-menu.js";
6
6
  import { FilterSection } from "./filter-section.js";
7
7
  import { RangeFilter } from "./range-filter.js";
8
8
  import { ToggleRow } from "./toggle-row.js";
9
+ import { ToolbarRow, toolbarShows } from "./toolbar-row.js";
9
10
  import { useFilterState } from "./use-filter-state.js";
11
+ import { Fragment } from "react";
10
12
  import { jsx, jsxs } from "react/jsx-runtime";
11
13
  //#region src/filter-panel/filter-panel.tsx
12
- const FilterPanel = ({ config, layout, value, defaultValue, onChange, lockedLabels, defaultOpenMenu, fullWidth }) => {
14
+ const FilterPanel = ({ config, layout, value, defaultValue, onChange, lockedLabels, defaultOpenMenu, fullWidth, leading, trailing }) => {
13
15
  const [state, setState] = useFilterState(value, defaultValue, onChange);
14
16
  const chips = activeChips(config, state);
15
17
  const chipsView = /* @__PURE__ */ jsx(ActiveFilterChips, {
@@ -76,35 +78,41 @@ const FilterPanel = ({ config, layout, value, defaultValue, onChange, lockedLabe
76
78
  })
77
79
  ]
78
80
  });
81
+ const menus = [
82
+ ...config.facets.map((group) => /* @__PURE__ */ jsx(FilterMenu, {
83
+ label: group.label,
84
+ activeCount: (state.facets[group.id] ?? []).length,
85
+ defaultOpen: defaultOpenMenu === group.id,
86
+ children: facetView(group)
87
+ }, group.id)),
88
+ ...config.range ? [/* @__PURE__ */ jsx(FilterMenu, {
89
+ label: config.range.label,
90
+ activeCount: state.range ? 1 : 0,
91
+ width: 280,
92
+ defaultOpen: defaultOpenMenu === config.range.id,
93
+ children: rangeView
94
+ }, "range")] : [],
95
+ ...dateView ? [/* @__PURE__ */ jsx(Fragment, { children: dateView }, "date")] : []
96
+ ];
97
+ const toggles = (config.toggles ?? []).map((toggle) => /* @__PURE__ */ jsx("div", {
98
+ className: "flex items-center gap-2 text-[13px] text-muted-foreground",
99
+ children: /* @__PURE__ */ jsx(ToggleRow, {
100
+ label: toggle.label,
101
+ checked: state.toggles.includes(toggle.id),
102
+ onChange: () => setState(toggleFlag(state, toggle.id))
103
+ })
104
+ }, toggle.id));
105
+ const groups = [
106
+ leading ? [/* @__PURE__ */ jsx(Fragment, { children: leading }, "leading")] : [],
107
+ menus,
108
+ toggles
109
+ ];
110
+ if (!toolbarShows(groups, trailing) && chips.length === 0 && !lockedLabels?.length) return null;
79
111
  return /* @__PURE__ */ jsxs("div", {
80
112
  className: "flex flex-col gap-2",
81
- children: [/* @__PURE__ */ jsxs("div", {
82
- className: "flex flex-wrap items-center gap-2",
83
- children: [
84
- config.facets.map((group) => /* @__PURE__ */ jsx(FilterMenu, {
85
- label: group.label,
86
- activeCount: (state.facets[group.id] ?? []).length,
87
- defaultOpen: defaultOpenMenu === group.id,
88
- children: facetView(group)
89
- }, group.id)),
90
- config.range && /* @__PURE__ */ jsx(FilterMenu, {
91
- label: config.range.label,
92
- activeCount: state.range ? 1 : 0,
93
- width: 280,
94
- defaultOpen: defaultOpenMenu === config.range.id,
95
- children: rangeView
96
- }),
97
- dateView,
98
- config.toggles && /* @__PURE__ */ jsx("span", { className: "mx-1 h-5 w-px bg-border" }),
99
- config.toggles?.map((toggle) => /* @__PURE__ */ jsx("div", {
100
- className: "flex items-center gap-2 text-[13px] text-muted-foreground",
101
- children: /* @__PURE__ */ jsx(ToggleRow, {
102
- label: toggle.label,
103
- checked: state.toggles.includes(toggle.id),
104
- onChange: () => setState(toggleFlag(state, toggle.id))
105
- })
106
- }, toggle.id))
107
- ]
113
+ children: [/* @__PURE__ */ jsx(ToolbarRow, {
114
+ groups,
115
+ trailing
108
116
  }), chipsView]
109
117
  });
110
118
  };
@@ -0,0 +1,22 @@
1
+ import { Fragment } from "react";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/filter-panel/toolbar-row.tsx
4
+ /** Whether a toolbar row with these groups and trailing control shows anything. */
5
+ const toolbarShows = (groups, trailing) => groups.some((group) => group.length > 0) || Boolean(trailing);
6
+ /**
7
+ * One row of list controls: the non-empty groups in order with a divider between each two, then the trailing control
8
+ * at the right end, narrowing before it wraps. Nothing at all when it would show nothing.
9
+ */
10
+ const ToolbarRow = ({ groups, trailing }) => {
11
+ if (!toolbarShows(groups, trailing)) return null;
12
+ const shown = groups.filter((group) => group.length > 0);
13
+ return /* @__PURE__ */ jsxs("div", {
14
+ className: "flex flex-wrap items-center gap-2",
15
+ children: [shown.map((group, index) => /* @__PURE__ */ jsxs(Fragment, { children: [index > 0 && /* @__PURE__ */ jsx("span", { className: "mx-1 h-5 w-px bg-border" }), group] }, index)), trailing && /* @__PURE__ */ jsx("div", {
16
+ className: "flex min-w-32 flex-1 justify-end",
17
+ children: trailing
18
+ })]
19
+ });
20
+ };
21
+ //#endregion
22
+ export { ToolbarRow, toolbarShows };
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.12.7";
2
+ var version = "0.12.8";
3
3
  var protobase = { "pluginApi": 1 };
4
4
  //#endregion
5
5
  export { protobase, version };
@@ -1,15 +1,25 @@
1
1
  import { cn } from "../lib/cn.js";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  //#region src/primitives/segmented.tsx
4
- const Segmented = ({ value, options, onChange, label }) => /* @__PURE__ */ jsx("span", {
4
+ const sizes = {
5
+ sm: {
6
+ group: "h-7",
7
+ option: "px-2.5 text-xs"
8
+ },
9
+ md: {
10
+ group: "h-8",
11
+ option: "px-3 text-[13px]"
12
+ }
13
+ };
14
+ const Segmented = ({ value, options, onChange, label, size = "sm" }) => /* @__PURE__ */ jsx("span", {
5
15
  role: "group",
6
16
  "aria-label": label,
7
- className: "inline-flex h-7 rounded-md bg-muted p-0.5",
17
+ className: cn("inline-flex", sizes[size].group, "rounded-md bg-muted p-0.5"),
8
18
  children: options.map((option) => /* @__PURE__ */ jsxs("button", {
9
19
  type: "button",
10
20
  "aria-pressed": option.value === value,
11
21
  onClick: () => onChange(option.value),
12
- className: cn("inline-flex items-center gap-1.5 rounded px-2.5 text-xs font-medium transition-colors", option.value === value ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"),
22
+ className: cn("inline-flex items-center gap-1.5 rounded", sizes[size].option, "font-medium transition-colors", option.value === value ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"),
13
23
  children: [option.icon && /* @__PURE__ */ jsx(option.icon, { className: "size-3.5" }), option.label]
14
24
  }, option.value))
15
25
  });