@protobase/ui 0.12.7 → 0.12.9
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/action-card/action-card.d.ts +3 -1
- package/dist/action-card/action-card.js +4 -4
- package/dist/app/displays/display-controls.js +2 -1
- package/dist/app/list-page.js +23 -14
- package/dist/app/list-search.js +1 -1
- package/dist/filter-panel/filter-panel.js +36 -28
- package/dist/filter-panel/toolbar-row.js +22 -0
- package/dist/package.js +1 -1
- package/dist/primitives/segmented.js +13 -3
- package/dist/shell/assets/{app-W6ms0mEo.js → app-Ds2CYFLS.js} +39 -39
- package/dist/shell/assets/{index-BVOqIEYd.css → index-BMIEmNDF.css} +1 -1
- package/dist/shell/assets/{index-tqpbOKxn.js → index-CGPHR8-j.js} +1 -1
- package/dist/shell/assets/{shared-protobase-ui-DEslmnKe.js → shared-protobase-ui-oolw3GIu.js} +1 -1
- package/dist/shell/index.html +4 -4
- package/dist/shell/protobase.shell.json +1 -1
- package/package.json +4 -4
|
@@ -20,7 +20,9 @@ export type ActionCardProps = {
|
|
|
20
20
|
actions?: CardAction[];
|
|
21
21
|
onAction?: (id: string) => void;
|
|
22
22
|
children?: ReactNode;
|
|
23
|
+
/** Without the bordered card and the title row, for a page that is already framed; the title still labels the section. */
|
|
24
|
+
frameless?: boolean;
|
|
23
25
|
};
|
|
24
26
|
/** A card with a toned title row, any content, a note and buttons: the building block of rich chat messages. */
|
|
25
|
-
export declare const ActionCard: ({ title, tone, badge, note, actions, onAction, children }: ActionCardProps) => import("react").JSX.Element;
|
|
27
|
+
export declare const ActionCard: ({ title, tone, badge, note, actions, onAction, children, frameless }: ActionCardProps) => import("react").JSX.Element;
|
|
26
28
|
//#endregion
|
|
@@ -27,15 +27,15 @@ const frames = {
|
|
|
27
27
|
}
|
|
28
28
|
};
|
|
29
29
|
/** A card with a toned title row, any content, a note and buttons: the building block of rich chat messages. */
|
|
30
|
-
const ActionCard = ({ title, tone = "neutral", badge, note, actions = [], onAction, children }) => {
|
|
30
|
+
const ActionCard = ({ title, tone = "neutral", badge, note, actions = [], onAction, children, frameless = false }) => {
|
|
31
31
|
const [confirming, setConfirming] = useState();
|
|
32
32
|
const hints = actions.flatMap((action) => action.hint ? [action.hint] : []);
|
|
33
33
|
const click = (action) => action.confirm ? setConfirming(action) : onAction?.(action.id);
|
|
34
34
|
return /* @__PURE__ */ jsxs("section", {
|
|
35
35
|
"aria-label": title,
|
|
36
|
-
className: cn("shrink-0 overflow-hidden rounded-lg border bg-background whitespace-normal text-foreground shadow-sm", frames[tone].border),
|
|
36
|
+
className: frameless ? "whitespace-normal text-foreground" : cn("shrink-0 overflow-hidden rounded-lg border bg-background whitespace-normal text-foreground shadow-sm", frames[tone].border),
|
|
37
37
|
children: [
|
|
38
|
-
/* @__PURE__ */ jsxs("header", {
|
|
38
|
+
!frameless && /* @__PURE__ */ jsxs("header", {
|
|
39
39
|
className: cn("flex items-center gap-2 px-3 py-2", frames[tone].header),
|
|
40
40
|
children: [/* @__PURE__ */ jsx("h3", {
|
|
41
41
|
className: "min-w-0 flex-1 text-[13px] font-semibold",
|
|
@@ -46,7 +46,7 @@ const ActionCard = ({ title, tone = "neutral", badge, note, actions = [], onActi
|
|
|
46
46
|
})]
|
|
47
47
|
}),
|
|
48
48
|
(children || note || actions.length > 0) && /* @__PURE__ */ jsxs("div", {
|
|
49
|
-
className: "space-y-2.5 px-3 py-2.5",
|
|
49
|
+
className: cn("space-y-2.5", !frameless && "px-3 py-2.5"),
|
|
50
50
|
children: [
|
|
51
51
|
children,
|
|
52
52
|
note && /* @__PURE__ */ jsx("p", {
|
|
@@ -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-
|
|
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: [
|
package/dist/app/list-page.js
CHANGED
|
@@ -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
|
-
|
|
221
|
-
|
|
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
|
-
|
|
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]
|
package/dist/app/list-search.js
CHANGED
|
@@ -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-
|
|
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__ */
|
|
82
|
-
|
|
83
|
-
|
|
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,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
|
|
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
|
|
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
|
|
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
|
});
|