@panneau/lists 5.0.0-alpha.0 → 5.0.0-alpha.2
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/README.md +5 -1
- package/dist/ListView.css +16 -16
- package/dist/ListView.d.ts +7 -1
- package/dist/ListView.d.ts.map +1 -1
- package/dist/ListView.js +8 -5
- package/dist/ListView.js.map +1 -1
- package/dist/ListView.module.js +10 -10
- package/dist/actions/ActionButton.d.ts +4 -1
- package/dist/actions/ActionButton.d.ts.map +1 -1
- package/dist/actions/ActionButton.js +3 -3
- package/dist/actions/ActionButton.js.map +1 -1
- package/dist/actions/Actions.css +9 -9
- package/dist/actions/Actions.module.js +6 -6
- package/dist/actions/RowActions.d.ts +4 -1
- package/dist/actions/RowActions.d.ts.map +1 -1
- package/dist/actions/RowActions.js +37 -33
- package/dist/actions/RowActions.js.map +1 -1
- package/dist/actions/icons.d.ts +8 -3
- package/dist/actions/icons.d.ts.map +1 -1
- package/dist/actions/icons.js +11 -7
- package/dist/actions/icons.js.map +1 -1
- package/dist/displays/Displays.css +13 -13
- package/dist/displays/Displays.module.js +12 -12
- package/dist/displays/Displays.module.js.map +1 -1
- package/dist/filters/Filters.css +13 -13
- package/dist/filters/Filters.module.js +8 -8
- package/dist/filters/controls.d.ts +2 -2
- package/dist/filters/controls.d.ts.map +1 -1
- package/dist/filters/controls.js +145 -119
- package/dist/filters/controls.js.map +1 -1
- package/dist/filters/format.d.ts.map +1 -1
- package/dist/filters/format.js +4 -1
- package/dist/filters/format.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/layouts/CardsLayout.css +17 -11
- package/dist/layouts/CardsLayout.module.js +8 -8
- package/dist/layouts/CardsLayout.module.js.map +1 -1
- package/dist/layouts/ItemTitle.d.ts +1 -1
- package/dist/layouts/TableLayout.css +35 -35
- package/dist/layouts/TableLayout.module.js +12 -12
- package/dist/layouts/TableLayout.module.js.map +1 -1
- package/dist/layouts/link.css +3 -3
- package/dist/layouts/link.module.js +1 -1
- package/dist/query.d.ts +2 -2
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +7 -2
- package/dist/query.js.map +1 -1
- package/dist/toolbar/Toolbar.css +12 -12
- package/dist/toolbar/Toolbar.module.js +7 -7
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -83,14 +83,18 @@ const { data, isFetching, error } = useResourceList('pages', query); // @panneau
|
|
|
83
83
|
| Displays | `text`, `textarea`, `slug`, `richtext`, `number`, `boolean`, `date`, `datetime`, `time`, `select`, `relation`, `url`, `email`, `color`, `media` |
|
|
84
84
|
| Layouts | `table` and `cards` (`layouts`, the first is the default). On narrow lists, table rows stack and show the label of each value |
|
|
85
85
|
| Sorting | Sortable column headers (`aria-sort`), and a select for cards and phones |
|
|
86
|
-
| Filters | `text`, `select`, `toggle`, `date`, `date-range`, `relation`, with chips for the active filters and the search
|
|
86
|
+
| Filters | `text`, `select`, `toggle`, `date`, `date-range` (sent as bounds: `filter[id][from]`, `filter[id][to]`), `relation`, with chips for the active filters and the search |
|
|
87
87
|
| Pagination | With `total`: the range, previous and next pages, and the page size (`perPageOptions`) |
|
|
88
88
|
| Selection | Checkboxes, the page, then "select all N items", which selects every item matching the query, with exceptions |
|
|
89
89
|
| Actions | `row` (buttons), `menu` (the row's menu), `toolbar` (selection and resource actions), with `confirm`, hidden by abilities ([0011](../../docs/decisions/0011-permissions.md)) |
|
|
90
90
|
| States | A spinner, an error with a retry, an empty message, and "no results" with a button that clears the filters |
|
|
91
91
|
|
|
92
|
+
An action shows the Lucide icon it names (`"icon": "send"`, through `getNamedIcon()` of `@panneau/ui`), or the icon of a built-in action (`edit`, `delete`…). Actions written in code can get icon components by id instead, which win over names: `actionIcons={{ archive: Archive }}`.
|
|
93
|
+
|
|
92
94
|
Lists confirm an action when it has `confirm` (its title and message are ICU messages with `{count}`), or when some selected items don't allow it, then call `onAction` with the action, its scope, the item or the `Selection` of the protocol, the count and the skipped items. A promise shows the action as pending; when it rejects, a toast says that it failed. After a selection action, the selection is cleared.
|
|
93
95
|
|
|
96
|
+
A `relation` filter keeps ids in the query (`filter[author]=7`). Its chip and picker show the labels of the items it picked, and those of ids from the URL once the relation source's `resolve()` gives them.
|
|
97
|
+
|
|
94
98
|
## Plugins
|
|
95
99
|
|
|
96
100
|
Displays and layouts are registry entries. Plugins of `PanneauProvider` add or replace them, and so do the `displays` and `layoutComponents` props:
|
package/dist/ListView.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.panneau-list-
|
|
1
|
+
.panneau-list-view-list {
|
|
2
2
|
container: panneau-list / inline-size;
|
|
3
3
|
display: flex;
|
|
4
4
|
flex-direction: column;
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
min-width: 0;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
.panneau-toolbar
|
|
9
|
+
.panneau-list-view-toolbar {
|
|
10
10
|
display: flex;
|
|
11
11
|
flex-wrap: wrap;
|
|
12
12
|
align-items: flex-end;
|
|
13
13
|
gap: var(--panneau-space-3);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
.panneau-
|
|
16
|
+
.panneau-list-view-toolbar-start {
|
|
17
17
|
display: flex;
|
|
18
18
|
flex: 1 1 auto;
|
|
19
19
|
flex-wrap: wrap;
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
min-width: 0;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
.panneau-
|
|
25
|
+
.panneau-list-view-toolbar-end {
|
|
26
26
|
display: flex;
|
|
27
27
|
flex-wrap: wrap;
|
|
28
28
|
align-items: flex-end;
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/* With the table layout, the column headers sort, until the list is too narrow to show them. */
|
|
34
|
-
.panneau-
|
|
34
|
+
.panneau-list-view-table-sort {
|
|
35
35
|
display: none;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
.panneau-selection
|
|
38
|
+
.panneau-list-view-selection {
|
|
39
39
|
display: flex;
|
|
40
40
|
flex-wrap: wrap;
|
|
41
41
|
align-items: center;
|
|
@@ -47,23 +47,23 @@
|
|
|
47
47
|
border-radius: var(--panneau-radius-lg);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
.panneau-
|
|
50
|
+
.panneau-list-view-selection-count {
|
|
51
51
|
font-weight: var(--panneau-font-weight-medium);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
.panneau-
|
|
54
|
+
.panneau-list-view-selection-actions {
|
|
55
55
|
display: flex;
|
|
56
56
|
flex-wrap: wrap;
|
|
57
57
|
gap: var(--panneau-space-2);
|
|
58
58
|
margin-inline-start: auto;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
.panneau-body
|
|
61
|
+
.panneau-list-view-body[aria-busy='true'] {
|
|
62
62
|
opacity: 0.6;
|
|
63
63
|
transition: opacity var(--panneau-duration);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
.panneau-state
|
|
66
|
+
.panneau-list-view-state {
|
|
67
67
|
display: flex;
|
|
68
68
|
flex-direction: column;
|
|
69
69
|
align-items: center;
|
|
@@ -76,26 +76,26 @@
|
|
|
76
76
|
border-radius: var(--panneau-radius-lg);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
.panneau-state
|
|
79
|
+
.panneau-list-view-state p {
|
|
80
80
|
margin: 0;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
@container panneau-list (max-width: 40rem) {
|
|
84
|
-
.panneau-
|
|
85
|
-
.panneau-
|
|
84
|
+
.panneau-list-view-toolbar-start,
|
|
85
|
+
.panneau-list-view-toolbar-end {
|
|
86
86
|
flex-basis: 100%;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
.panneau-
|
|
89
|
+
.panneau-list-view-toolbar-end {
|
|
90
90
|
margin-inline-start: 0;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
.panneau-
|
|
93
|
+
.panneau-list-view-table-sort {
|
|
94
94
|
display: block;
|
|
95
95
|
width: 100%;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
.panneau-
|
|
98
|
+
.panneau-list-view-selection-actions {
|
|
99
99
|
margin-inline-start: 0;
|
|
100
100
|
}
|
|
101
101
|
}
|
package/dist/ListView.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type ReactNode } from 'react';
|
|
|
2
2
|
import type { List } from '@panneau/core';
|
|
3
3
|
import type { Field, Filter } from '@panneau/protocol';
|
|
4
4
|
import { type ListActions } from './actions/actions';
|
|
5
|
+
import type { ActionIcons } from './actions/icons';
|
|
5
6
|
import type { DisplayEntry, FilterEntry, ListActionHandler, ListColumn, ListItem, ListLayoutEntry } from './types';
|
|
6
7
|
export interface ListViewProps {
|
|
7
8
|
/** The list state of `@panneau/core` (`useList()`): query and selection. */
|
|
@@ -41,6 +42,11 @@ export interface ListViewProps {
|
|
|
41
42
|
actions?: ListActions;
|
|
42
43
|
/** Runs an action, after its confirmation. */
|
|
43
44
|
onAction?: ListActionHandler;
|
|
45
|
+
/**
|
|
46
|
+
* Icons of actions by action id (`{ archive: Archive }`), for actions written in code. They
|
|
47
|
+
* win over the Lucide icon an action names (`"icon": "archive"`).
|
|
48
|
+
*/
|
|
49
|
+
actionIcons?: ActionIcons;
|
|
44
50
|
/** The field that titles items, for their label (`itemTitle` of the resource). */
|
|
45
51
|
itemTitle?: string;
|
|
46
52
|
/** The label of an item, for checkboxes and actions. Defaults to its title, then its id. */
|
|
@@ -66,5 +72,5 @@ export interface ListViewProps {
|
|
|
66
72
|
* cards), with sorting, search, filters and their chips, pagination, selection and actions. It
|
|
67
73
|
* fetches nothing: the caller gives the items of the list's query and their total.
|
|
68
74
|
*/
|
|
69
|
-
export declare function ListView({ list, items, columns, total, isLoading, error, onRetry, filters, fields, search, layouts: layoutsProp, layout: controlledLayout, onLayoutChange, perPageOptions, selectable: selectableProp, actions: actionsProp, onAction, itemTitle, getItemLabel: getItemLabelProp, getItemHref, displays, layoutComponents, filterControls, toolbar, emptyMessage, 'aria-label': ariaLabel, className, }: ListViewProps): import("react").JSX.Element;
|
|
75
|
+
export declare function ListView({ list, items, columns, total, isLoading, error, onRetry, filters, fields, search, layouts: layoutsProp, layout: controlledLayout, onLayoutChange, perPageOptions, selectable: selectableProp, actions: actionsProp, onAction, actionIcons, itemTitle, getItemLabel: getItemLabelProp, getItemHref, displays, layoutComponents, filterControls, toolbar, emptyMessage, 'aria-label': ariaLabel, className, }: ListViewProps): import("react").JSX.Element;
|
|
70
76
|
//# sourceMappingURL=ListView.d.ts.map
|
package/dist/ListView.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListView.d.ts","sourceRoot":"","sources":["../src/ListView.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,OAAO,CAAC;AAEjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAE1C,OAAO,KAAK,EAAU,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAO/D,OAAO,EACH,KAAK,WAAW,EAKnB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"ListView.d.ts","sourceRoot":"","sources":["../src/ListView.tsx"],"names":[],"mappings":"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"}
|
package/dist/ListView.js
CHANGED
|
@@ -28,7 +28,7 @@ function keyOf(action, item) {
|
|
|
28
28
|
* cards), with sorting, search, filters and their chips, pagination, selection and actions. It
|
|
29
29
|
* fetches nothing: the caller gives the items of the list's query and their total.
|
|
30
30
|
*/
|
|
31
|
-
function ListView({ list, items, columns, total, isLoading = false, error = null, onRetry, filters = [], fields = [], search = false, layouts: layoutsProp = ["table"], layout: controlledLayout, onLayoutChange, perPageOptions, selectable: selectableProp, actions: actionsProp, onAction, itemTitle, getItemLabel: getItemLabelProp, getItemHref, displays, layoutComponents, filterControls, toolbar, emptyMessage, "aria-label": ariaLabel, className }) {
|
|
31
|
+
function ListView({ list, items, columns, total, isLoading = false, error = null, onRetry, filters = [], fields = [], search = false, layouts: layoutsProp = ["table"], layout: controlledLayout, onLayoutChange, perPageOptions, selectable: selectableProp, actions: actionsProp, onAction, actionIcons, itemTitle, getItemLabel: getItemLabelProp, getItemHref, displays, layoutComponents, filterControls, toolbar, emptyMessage, "aria-label": ariaLabel, className }) {
|
|
32
32
|
const intl = usePanneauIntl();
|
|
33
33
|
const toasts = useToast();
|
|
34
34
|
const plugins = usePlugins();
|
|
@@ -88,8 +88,8 @@ function ListView({ list, items, columns, total, isLoading = false, error = null
|
|
|
88
88
|
setConfirming(null);
|
|
89
89
|
setPending(keyOf(event.action, event.item));
|
|
90
90
|
try {
|
|
91
|
-
await onAction?.(event);
|
|
92
|
-
if (event.scope === "selection") list.clearSelection();
|
|
91
|
+
const outcome = await onAction?.(event);
|
|
92
|
+
if (event.scope === "selection" && outcome !== false) list.clearSelection();
|
|
93
93
|
} catch (caught) {
|
|
94
94
|
toasts.show({
|
|
95
95
|
title: intl.formatMessage(messages.actionFailed, { action: event.action.label }),
|
|
@@ -139,7 +139,8 @@ function ListView({ list, items, columns, total, isLoading = false, error = null
|
|
|
139
139
|
actions: allowed,
|
|
140
140
|
menu,
|
|
141
141
|
pending: running ?? null,
|
|
142
|
-
onRun: (action_9) => runOnItem(action_9, item_4)
|
|
142
|
+
onRun: (action_9) => runOnItem(action_9, item_4),
|
|
143
|
+
icons: actionIcons
|
|
143
144
|
});
|
|
144
145
|
};
|
|
145
146
|
const pageCheckbox = /* @__PURE__ */ jsx(Checkbox, {
|
|
@@ -154,7 +155,7 @@ function ListView({ list, items, columns, total, isLoading = false, error = null
|
|
|
154
155
|
}
|
|
155
156
|
});
|
|
156
157
|
const sortable = columns.some((column_1) => column_1.sortable === true);
|
|
157
|
-
const showToolbar = search || filters.length > 0 || sortable || layouts.length > 1 || toolbar !== void 0;
|
|
158
|
+
const showToolbar = search || filters.length > 0 || sortable || layouts.length > 1 || resourceActions.length > 0 || toolbar !== void 0;
|
|
158
159
|
const body = (() => {
|
|
159
160
|
if (error !== null) return /* @__PURE__ */ jsx(Alert, {
|
|
160
161
|
tone: "danger",
|
|
@@ -239,6 +240,7 @@ function ListView({ list, items, columns, total, isLoading = false, error = null
|
|
|
239
240
|
}) : null,
|
|
240
241
|
resourceActions.filter((action_10) => selectedCount === 0 || !hasScope(action_10, "selection")).map((action_11) => /* @__PURE__ */ jsx(ActionButton, {
|
|
241
242
|
action: action_11,
|
|
243
|
+
icons: actionIcons,
|
|
242
244
|
size: "md",
|
|
243
245
|
href: getActionHref(action_11),
|
|
244
246
|
isPending: pending === keyOf(action_11),
|
|
@@ -285,6 +287,7 @@ function ListView({ list, items, columns, total, isLoading = false, error = null
|
|
|
285
287
|
const skipped = selectedItems.filter((item_8) => !isAllowedOn(action_12, item_8)).length;
|
|
286
288
|
return /* @__PURE__ */ jsx(ActionButton, {
|
|
287
289
|
action: action_12,
|
|
290
|
+
icons: actionIcons,
|
|
288
291
|
size: "md",
|
|
289
292
|
href: null,
|
|
290
293
|
isPending: pending === keyOf(action_12),
|
package/dist/ListView.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListView.js","names":["action","item","column","event","id","next","styles"],"sources":["../src/ListView.tsx"],"sourcesContent":["import { type ReactNode, useState } from 'react';\n\nimport type { List } from '@panneau/core';\nimport { usePlugins } from '@panneau/core';\nimport type { Action, Field, Filter } from '@panneau/protocol';\nimport { Alert, Button, Checkbox, Spinner, usePanneauIntl, useToast } from '@panneau/ui';\n\nimport styles from './ListView.module.css';\nimport { ActionButton } from './actions/ActionButton';\nimport { ConfirmDialog, needsConfirmation } from './actions/ConfirmDialog';\nimport { RowActions } from './actions/RowActions';\nimport {\n type ListActions,\n getActionHref,\n groupListActions,\n hasScope,\n isAllowedOn,\n} from './actions/actions';\nimport { cx } from './cx';\nimport { builtInDisplays } from './displays/displays';\nimport { FilterChips, FilterControls, useFilterLabels } from './filters/Filters';\nimport { builtInLayouts } from './layouts/builtIn';\nimport { messages } from './messages';\nimport { countSelected, getPageSelection, isItemSelected } from './selection';\nimport { LayoutSwitch } from './toolbar/LayoutSwitch';\nimport { Pagination } from './toolbar/Pagination';\nimport { SearchField } from './toolbar/SearchField';\nimport { SortSelect } from './toolbar/SortSelect';\nimport type {\n DisplayEntry,\n FilterEntry,\n ListActionEvent,\n ListActionHandler,\n ListColumn,\n ListItem,\n ListLayoutEntry,\n} from './types';\nimport { useListState } from './useListState';\nimport { getColumnValue, toText } from './values';\n\nexport interface ListViewProps {\n /** The list state of `@panneau/core` (`useList()`): query and selection. */\n list: List;\n /** The items of the current page. `undefined` while they load for the first time. */\n items: readonly ListItem[] | undefined;\n columns: readonly ListColumn[];\n /**\n * How many items match the query, on every page (`meta.total`). With it, the list shows\n * pagination and offers to select every matching item. Without it, `items` is the whole list.\n */\n total?: number | undefined;\n /** Shows that items are loading. Items already shown stay, dimmed. */\n isLoading?: boolean;\n /** An error while loading: an alert with a retry replaces the items. */\n error?: Error | null;\n onRetry?: () => void;\n /** Filter controls, with chips for the active filters. */\n filters?: readonly Filter[];\n /** Fields of the items, for the labels and options of filters that are not columns. */\n fields?: readonly Field[];\n /** Shows a search field (`q`). */\n search?: boolean;\n /** Layouts to choose from, the first one being the default. Defaults to `['table']`. */\n layouts?: readonly string[];\n /** The layout, controlled. */\n layout?: string;\n onLayoutChange?: (layout: string) => void;\n /** Page sizes to choose from. */\n perPageOptions?: readonly number[];\n /** Shows checkboxes. Defaults to whether there are selection actions. */\n selectable?: boolean;\n /**\n * Actions: a list (placed by their `placement`, or by scope), or grouped by placement as\n * `resolveList()` does. Those the user can't run (`abilities.run`) are hidden.\n */\n actions?: ListActions;\n /** Runs an action, after its confirmation. */\n onAction?: ListActionHandler;\n /** The field that titles items, for their label (`itemTitle` of the resource). */\n itemTitle?: string;\n /** The label of an item, for checkboxes and actions. Defaults to its title, then its id. */\n getItemLabel?: (item: ListItem) => string;\n /** A link to an item, on its title. */\n getItemHref?: (item: ListItem) => string | null;\n /** Displays by name, over the built-in ones and those of the plugins. */\n displays?: Readonly<Record<string, DisplayEntry>>;\n /** Layouts by id, over the built-in ones and those of the plugins. */\n layoutComponents?: Readonly<Record<string, ListLayoutEntry>>;\n /** Filter controls by filter type, over the built-in ones. */\n filterControls?: Readonly<Record<string, FilterEntry>>;\n /** Content at the end of the toolbar, such as a button to create an item. */\n toolbar?: ReactNode;\n /** Shown when the list has no items, without filters or search. */\n emptyMessage?: ReactNode;\n /** Accessible name of the list. Defaults to \"Items\". */\n 'aria-label'?: string;\n className?: string;\n}\n\nfunction keyOf(action: Action, item?: ListItem): string {\n return item !== undefined ? `${action.id}\\u0000${item.id}` : action.id;\n}\n\n/**\n * Renders the list state of `@panneau/core`: the items of the current page in a layout (table or\n * cards), with sorting, search, filters and their chips, pagination, selection and actions. It\n * fetches nothing: the caller gives the items of the list's query and their total.\n */\nexport function ListView({\n list,\n items,\n columns,\n total,\n isLoading = false,\n error = null,\n onRetry,\n filters = [],\n fields = [],\n search = false,\n layouts: layoutsProp = ['table'],\n layout: controlledLayout,\n onLayoutChange,\n perPageOptions,\n selectable: selectableProp,\n actions: actionsProp,\n onAction,\n itemTitle,\n getItemLabel: getItemLabelProp,\n getItemHref,\n displays,\n layoutComponents,\n filterControls,\n toolbar,\n emptyMessage,\n 'aria-label': ariaLabel,\n className,\n}: ListViewProps) {\n const intl = usePanneauIntl();\n const toasts = useToast();\n const plugins = usePlugins();\n const { query, selection } = useListState(list);\n const filterLabels = useFilterLabels();\n const [uncontrolledLayout, setLayout] = useState<string | null>(null);\n const [confirming, setConfirming] = useState<ListActionEvent | null>(null);\n const [pending, setPending] = useState<string | null>(null);\n\n const label = ariaLabel ?? intl.formatMessage(messages.list);\n const pageItems = items ?? [];\n const count = total ?? pageItems.length;\n const sort = query.sort ?? [];\n const filterValues = query.filter ?? {};\n const hasQuery = Object.keys(filterValues).length > 0 || (query.q ?? '') !== '';\n\n const allDisplays: Record<string, DisplayEntry> = {\n ...builtInDisplays,\n ...(Object.fromEntries(plugins.displays) as Record<string, DisplayEntry>),\n ...displays,\n };\n const allLayouts: Record<string, ListLayoutEntry> = {\n ...builtInLayouts,\n ...(Object.fromEntries(plugins.listLayouts) as Record<string, ListLayoutEntry>),\n ...layoutComponents,\n };\n // Layouts without a component (a plugin's that isn't installed) are left out.\n const available = layoutsProp.filter((id) => allLayouts[id] !== undefined);\n const layouts = available.length > 0 ? available : ['table'];\n const layout = controlledLayout ?? uncontrolledLayout ?? layouts[0] ?? 'table';\n const Layout = allLayouts[layout] ?? builtInLayouts.table;\n\n const grouped = groupListActions(actionsProp);\n const rowActions = grouped.row.filter((action) => hasScope(action, 'item'));\n const rowMenu = grouped.menu.filter((action) => hasScope(action, 'item'));\n // Actions placed in the menu also apply to selections, from the toolbar (once each).\n const toolbarActions = [...grouped.toolbar, ...grouped.menu].filter(\n (action, index, all) =>\n (hasScope(action, 'selection') || hasScope(action, 'resource')) &&\n all.findIndex((candidate) => candidate.id === action.id) === index,\n );\n const selectionActions = toolbarActions.filter((action) => hasScope(action, 'selection'));\n const resourceActions = toolbarActions.filter((action) => hasScope(action, 'resource'));\n const selectable = selectableProp ?? selectionActions.length > 0;\n\n const selectedCount = selectable ? countSelected(selection, count) : 0;\n const selectedItems = pageItems.filter((item) => isItemSelected(selection, item.id));\n const pageSelection = getPageSelection(selection, pageItems);\n\n const getItemLabel = (item: ListItem): string => {\n if (getItemLabelProp !== undefined) {\n return getItemLabelProp(item);\n }\n const titleColumn =\n columns.find((column) => column.id === itemTitle) ??\n (itemTitle === undefined ? columns[0] : undefined);\n const title =\n titleColumn !== undefined\n ? toText(getColumnValue(item, titleColumn, intl.locale), intl.locale)\n : itemTitle !== undefined\n ? toText(item[itemTitle], intl.locale)\n : '';\n return title !== '' ? title : item.id;\n };\n\n const renderValue = (item: ListItem, column: ListColumn): ReactNode => {\n if (column.render !== undefined) {\n return column.render(item);\n }\n const Display =\n (column.display !== undefined && column.display !== null\n ? allDisplays[column.display]\n : undefined) ?? builtInDisplays.text;\n if (Display === undefined) {\n return null;\n }\n return (\n <Display\n value={getColumnValue(item, column, intl.locale)}\n item={item}\n column={column}\n field={column.field ?? null}\n />\n );\n };\n\n const execute = async (event: ListActionEvent) => {\n setConfirming(null);\n setPending(keyOf(event.action, event.item));\n try {\n await onAction?.(event);\n if (event.scope === 'selection') {\n list.clearSelection();\n }\n } catch (caught) {\n toasts.show({\n title: intl.formatMessage(messages.actionFailed, { action: event.action.label }),\n ...(caught instanceof Error && caught.message !== ''\n ? { description: caught.message }\n : {}),\n tone: 'danger',\n });\n } finally {\n setPending(null);\n }\n };\n\n const run = (event: ListActionEvent) => {\n if (needsConfirmation(event)) {\n setConfirming(event);\n } else {\n void execute(event);\n }\n };\n\n const runOnItem = (action: Action, item: ListItem) =>\n run({ action, scope: 'item', item, count: 1, skipped: [] });\n\n const runOnSelection = (action: Action) => {\n const selected = list.getSelection();\n if (selected === null) {\n return;\n }\n run({\n action,\n scope: 'selection',\n selection: selected,\n count: selectedCount,\n skipped: selectedItems.filter((item) => !isAllowedOn(action, item)),\n });\n };\n\n const runOnResource = (action: Action) =>\n run({ action, scope: 'resource', count: count, skipped: [] });\n\n const renderActions = (item: ListItem): ReactNode => {\n const allowed = rowActions.filter((action) => isAllowedOn(action, item));\n const menu = rowMenu.filter((action) => isAllowedOn(action, item));\n if (allowed.length === 0 && menu.length === 0) {\n return null;\n }\n const running =\n pending?.endsWith(`\\u0000${item.id}`) === true ? pending.split('\\u0000')[0] : null;\n return (\n <RowActions\n item={item}\n itemLabel={getItemLabel(item)}\n actions={allowed}\n menu={menu}\n pending={running ?? null}\n onRun={(action) => runOnItem(action, item)}\n />\n );\n };\n\n const pageCheckbox = (\n <Checkbox\n aria-label={intl.formatMessage(messages.selectPage)}\n isSelected={pageSelection === 'all'}\n isIndeterminate={pageSelection === 'some'}\n isDisabled={pageItems.length === 0}\n onChange={() => {\n const ids = pageItems.map((item) => item.id);\n if (pageSelection === 'all') {\n list.deselect(ids);\n } else {\n list.select(ids);\n }\n }}\n />\n );\n\n const sortable = columns.some((column) => column.sortable === true);\n const showToolbar =\n search || filters.length > 0 || sortable || layouts.length > 1 || toolbar !== undefined;\n\n const body = (() => {\n if (error !== null) {\n return (\n <Alert\n tone=\"danger\"\n title={intl.formatMessage(messages.loadFailed)}\n actions={\n onRetry !== undefined ? (\n <Button size=\"sm\" onPress={onRetry}>\n {intl.formatMessage(messages.retry)}\n </Button>\n ) : undefined\n }\n >\n {error.message}\n </Alert>\n );\n }\n if (items === undefined) {\n return (\n <div className={styles.state}>\n <Spinner showLabel />\n </div>\n );\n }\n if (pageItems.length === 0) {\n return (\n <div className={styles.state}>\n <p>\n {hasQuery\n ? intl.formatMessage(messages.noResults)\n : (emptyMessage ?? intl.formatMessage(messages.empty))}\n </p>\n {hasQuery ? (\n <Button size=\"sm\" onPress={() => clearFilters()}>\n {intl.formatMessage(messages.clearFilters)}\n </Button>\n ) : null}\n </div>\n );\n }\n return (\n <Layout\n items={pageItems}\n columns={columns}\n renderValue={renderValue}\n getItemLabel={getItemLabel}\n getItemHref={getItemHref}\n selectable={selectable}\n isSelected={(item) => isItemSelected(selection, item.id)}\n onToggle={(item) => list.toggle(item.id)}\n renderActions={renderActions}\n sort={sort}\n onSort={(column) => list.toggleSort(column.id)}\n label={label}\n pageSelection={pageCheckbox}\n />\n );\n })();\n\n function clearFilters() {\n Object.keys(filterValues).forEach((id) => list.setFilter(id, undefined));\n if ((query.q ?? '') !== '') {\n list.setSearch('');\n }\n }\n\n return (\n <div className={cx(styles.list, className)}>\n {showToolbar ? (\n <div className={styles.toolbar}>\n <div className={styles.toolbarStart}>\n {search ? (\n <SearchField\n value={query.q ?? ''}\n onChange={(q) => list.setSearch(q)}\n />\n ) : null}\n {filters.length > 0 ? (\n <FilterControls\n filters={filters}\n values={filterValues}\n onChange={(id, value) => list.setFilter(id, value)}\n onLabel={filterLabels.remember}\n controls={filterControls}\n columns={columns}\n fields={fields}\n labels={filterLabels.labels}\n />\n ) : null}\n </div>\n <div className={styles.toolbarEnd}>\n {sortable ? (\n <div className={layout === 'table' ? styles.tableSort : undefined}>\n <SortSelect\n columns={columns}\n sort={sort}\n onChange={(next) => list.setSort(next)}\n />\n </div>\n ) : null}\n {layouts.length > 1 ? (\n <LayoutSwitch\n layouts={layouts}\n value={layout}\n onChange={(next) => {\n setLayout(next);\n onLayoutChange?.(next);\n }}\n />\n ) : null}\n {resourceActions\n .filter(\n (action) => selectedCount === 0 || !hasScope(action, 'selection'),\n )\n .map((action) => (\n <ActionButton\n key={action.id}\n action={action}\n size=\"md\"\n href={getActionHref(action)}\n isPending={pending === keyOf(action)}\n onPress={() => runOnResource(action)}\n />\n ))}\n {toolbar}\n </div>\n </div>\n ) : null}\n <FilterChips\n filters={filters}\n values={filterValues}\n q={query.q}\n onRemove={(id) => list.setFilter(id, undefined)}\n onClearSearch={() => list.setSearch('')}\n onClear={clearFilters}\n columns={columns}\n fields={fields}\n labels={filterLabels.labels}\n />\n {selectedCount > 0 ? (\n <div className={styles.selection}>\n <span className={styles.selectionCount} role=\"status\">\n {selection.mode === 'all'\n ? intl.formatMessage(messages.allMatchingSelected, {\n total: selectedCount,\n })\n : intl.formatMessage(messages.selectedCount, { count: selectedCount })}\n </span>\n {selection.mode === 'ids' &&\n pageSelection === 'all' &&\n count > pageItems.length ? (\n <Button size=\"sm\" variant=\"ghost\" onPress={() => list.selectAllMatching()}>\n {intl.formatMessage(messages.selectAllMatching, { total: count })}\n </Button>\n ) : null}\n <Button size=\"sm\" variant=\"ghost\" onPress={() => list.clearSelection()}>\n {intl.formatMessage(messages.clearSelection)}\n </Button>\n {selectionActions.length > 0 ? (\n <div className={styles.selectionActions}>\n {selectionActions.map((action) => {\n const skipped = selectedItems.filter(\n (item) => !isAllowedOn(action, item),\n ).length;\n return (\n <ActionButton\n key={action.id}\n action={action}\n size=\"md\"\n href={null}\n isPending={pending === keyOf(action)}\n isDisabled={\n (pending !== null && pending !== keyOf(action)) ||\n skipped >= selectedCount\n }\n onPress={() => runOnSelection(action)}\n />\n );\n })}\n </div>\n ) : null}\n </div>\n ) : null}\n <div className={styles.body} aria-busy={isLoading && items !== undefined}>\n {body}\n </div>\n {total !== undefined && error === null && total > 0 ? (\n <Pagination\n page={query.page ?? 1}\n perPage={query.perPage ?? pageItems.length}\n total={total}\n onPageChange={(page) => list.setPage(page)}\n perPageOptions={perPageOptions}\n onPerPageChange={(perPage) => list.setPerPage(perPage)}\n />\n ) : null}\n <ConfirmDialog\n event={confirming}\n onConfirm={(event) => void execute(event)}\n onCancel={() => setConfirming(null)}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ListView.js","names":["action","item","column","event","id","next","styles"],"sources":["../src/ListView.tsx"],"sourcesContent":["import { type ReactNode, useState } from 'react';\n\nimport type { List } from '@panneau/core';\nimport { usePlugins } from '@panneau/core';\nimport type { Action, Field, Filter } from '@panneau/protocol';\nimport { Alert, Button, Checkbox, Spinner, usePanneauIntl, useToast } from '@panneau/ui';\n\nimport styles from './ListView.module.css';\nimport { ActionButton } from './actions/ActionButton';\nimport { ConfirmDialog, needsConfirmation } from './actions/ConfirmDialog';\nimport { RowActions } from './actions/RowActions';\nimport {\n type ListActions,\n getActionHref,\n groupListActions,\n hasScope,\n isAllowedOn,\n} from './actions/actions';\nimport type { ActionIcons } from './actions/icons';\nimport { cx } from './cx';\nimport { builtInDisplays } from './displays/displays';\nimport { FilterChips, FilterControls, useFilterLabels } from './filters/Filters';\nimport { builtInLayouts } from './layouts/builtIn';\nimport { messages } from './messages';\nimport { countSelected, getPageSelection, isItemSelected } from './selection';\nimport { LayoutSwitch } from './toolbar/LayoutSwitch';\nimport { Pagination } from './toolbar/Pagination';\nimport { SearchField } from './toolbar/SearchField';\nimport { SortSelect } from './toolbar/SortSelect';\nimport type {\n DisplayEntry,\n FilterEntry,\n ListActionEvent,\n ListActionHandler,\n ListColumn,\n ListItem,\n ListLayoutEntry,\n} from './types';\nimport { useListState } from './useListState';\nimport { getColumnValue, toText } from './values';\n\nexport interface ListViewProps {\n /** The list state of `@panneau/core` (`useList()`): query and selection. */\n list: List;\n /** The items of the current page. `undefined` while they load for the first time. */\n items: readonly ListItem[] | undefined;\n columns: readonly ListColumn[];\n /**\n * How many items match the query, on every page (`meta.total`). With it, the list shows\n * pagination and offers to select every matching item. Without it, `items` is the whole list.\n */\n total?: number | undefined;\n /** Shows that items are loading. Items already shown stay, dimmed. */\n isLoading?: boolean;\n /** An error while loading: an alert with a retry replaces the items. */\n error?: Error | null;\n onRetry?: () => void;\n /** Filter controls, with chips for the active filters. */\n filters?: readonly Filter[];\n /** Fields of the items, for the labels and options of filters that are not columns. */\n fields?: readonly Field[];\n /** Shows a search field (`q`). */\n search?: boolean;\n /** Layouts to choose from, the first one being the default. Defaults to `['table']`. */\n layouts?: readonly string[];\n /** The layout, controlled. */\n layout?: string;\n onLayoutChange?: (layout: string) => void;\n /** Page sizes to choose from. */\n perPageOptions?: readonly number[];\n /** Shows checkboxes. Defaults to whether there are selection actions. */\n selectable?: boolean;\n /**\n * Actions: a list (placed by their `placement`, or by scope), or grouped by placement as\n * `resolveList()` does. Those the user can't run (`abilities.run`) are hidden.\n */\n actions?: ListActions;\n /** Runs an action, after its confirmation. */\n onAction?: ListActionHandler;\n /**\n * Icons of actions by action id (`{ archive: Archive }`), for actions written in code. They\n * win over the Lucide icon an action names (`\"icon\": \"archive\"`).\n */\n actionIcons?: ActionIcons;\n /** The field that titles items, for their label (`itemTitle` of the resource). */\n itemTitle?: string;\n /** The label of an item, for checkboxes and actions. Defaults to its title, then its id. */\n getItemLabel?: (item: ListItem) => string;\n /** A link to an item, on its title. */\n getItemHref?: (item: ListItem) => string | null;\n /** Displays by name, over the built-in ones and those of the plugins. */\n displays?: Readonly<Record<string, DisplayEntry>>;\n /** Layouts by id, over the built-in ones and those of the plugins. */\n layoutComponents?: Readonly<Record<string, ListLayoutEntry>>;\n /** Filter controls by filter type, over the built-in ones. */\n filterControls?: Readonly<Record<string, FilterEntry>>;\n /** Content at the end of the toolbar, such as a button to create an item. */\n toolbar?: ReactNode;\n /** Shown when the list has no items, without filters or search. */\n emptyMessage?: ReactNode;\n /** Accessible name of the list. Defaults to \"Items\". */\n 'aria-label'?: string;\n className?: string;\n}\n\nfunction keyOf(action: Action, item?: ListItem): string {\n return item !== undefined ? `${action.id}\\u0000${item.id}` : action.id;\n}\n\n/**\n * Renders the list state of `@panneau/core`: the items of the current page in a layout (table or\n * cards), with sorting, search, filters and their chips, pagination, selection and actions. It\n * fetches nothing: the caller gives the items of the list's query and their total.\n */\nexport function ListView({\n list,\n items,\n columns,\n total,\n isLoading = false,\n error = null,\n onRetry,\n filters = [],\n fields = [],\n search = false,\n layouts: layoutsProp = ['table'],\n layout: controlledLayout,\n onLayoutChange,\n perPageOptions,\n selectable: selectableProp,\n actions: actionsProp,\n onAction,\n actionIcons,\n itemTitle,\n getItemLabel: getItemLabelProp,\n getItemHref,\n displays,\n layoutComponents,\n filterControls,\n toolbar,\n emptyMessage,\n 'aria-label': ariaLabel,\n className,\n}: ListViewProps) {\n const intl = usePanneauIntl();\n const toasts = useToast();\n const plugins = usePlugins();\n const { query, selection } = useListState(list);\n const filterLabels = useFilterLabels();\n const [uncontrolledLayout, setLayout] = useState<string | null>(null);\n const [confirming, setConfirming] = useState<ListActionEvent | null>(null);\n const [pending, setPending] = useState<string | null>(null);\n\n const label = ariaLabel ?? intl.formatMessage(messages.list);\n const pageItems = items ?? [];\n const count = total ?? pageItems.length;\n const sort = query.sort ?? [];\n const filterValues = query.filter ?? {};\n const hasQuery = Object.keys(filterValues).length > 0 || (query.q ?? '') !== '';\n\n const allDisplays: Record<string, DisplayEntry> = {\n ...builtInDisplays,\n ...(Object.fromEntries(plugins.displays) as Record<string, DisplayEntry>),\n ...displays,\n };\n const allLayouts: Record<string, ListLayoutEntry> = {\n ...builtInLayouts,\n ...(Object.fromEntries(plugins.listLayouts) as Record<string, ListLayoutEntry>),\n ...layoutComponents,\n };\n // Layouts without a component (a plugin's that isn't installed) are left out.\n const available = layoutsProp.filter((id) => allLayouts[id] !== undefined);\n const layouts = available.length > 0 ? available : ['table'];\n const layout = controlledLayout ?? uncontrolledLayout ?? layouts[0] ?? 'table';\n const Layout = allLayouts[layout] ?? builtInLayouts.table;\n\n const grouped = groupListActions(actionsProp);\n const rowActions = grouped.row.filter((action) => hasScope(action, 'item'));\n const rowMenu = grouped.menu.filter((action) => hasScope(action, 'item'));\n // Actions placed in the menu also apply to selections, from the toolbar (once each).\n const toolbarActions = [...grouped.toolbar, ...grouped.menu].filter(\n (action, index, all) =>\n (hasScope(action, 'selection') || hasScope(action, 'resource')) &&\n all.findIndex((candidate) => candidate.id === action.id) === index,\n );\n const selectionActions = toolbarActions.filter((action) => hasScope(action, 'selection'));\n const resourceActions = toolbarActions.filter((action) => hasScope(action, 'resource'));\n const selectable = selectableProp ?? selectionActions.length > 0;\n\n const selectedCount = selectable ? countSelected(selection, count) : 0;\n const selectedItems = pageItems.filter((item) => isItemSelected(selection, item.id));\n const pageSelection = getPageSelection(selection, pageItems);\n\n const getItemLabel = (item: ListItem): string => {\n if (getItemLabelProp !== undefined) {\n return getItemLabelProp(item);\n }\n const titleColumn =\n columns.find((column) => column.id === itemTitle) ??\n (itemTitle === undefined ? columns[0] : undefined);\n const title =\n titleColumn !== undefined\n ? toText(getColumnValue(item, titleColumn, intl.locale), intl.locale)\n : itemTitle !== undefined\n ? toText(item[itemTitle], intl.locale)\n : '';\n return title !== '' ? title : item.id;\n };\n\n const renderValue = (item: ListItem, column: ListColumn): ReactNode => {\n if (column.render !== undefined) {\n return column.render(item);\n }\n const Display =\n (column.display !== undefined && column.display !== null\n ? allDisplays[column.display]\n : undefined) ?? builtInDisplays.text;\n if (Display === undefined) {\n return null;\n }\n return (\n <Display\n value={getColumnValue(item, column, intl.locale)}\n item={item}\n column={column}\n field={column.field ?? null}\n />\n );\n };\n\n const execute = async (event: ListActionEvent) => {\n setConfirming(null);\n setPending(keyOf(event.action, event.item));\n try {\n const outcome = await onAction?.(event);\n // `false`: the action didn't run (its parameters were cancelled).\n if (event.scope === 'selection' && outcome !== false) {\n list.clearSelection();\n }\n } catch (caught) {\n toasts.show({\n title: intl.formatMessage(messages.actionFailed, { action: event.action.label }),\n ...(caught instanceof Error && caught.message !== ''\n ? { description: caught.message }\n : {}),\n tone: 'danger',\n });\n } finally {\n setPending(null);\n }\n };\n\n const run = (event: ListActionEvent) => {\n if (needsConfirmation(event)) {\n setConfirming(event);\n } else {\n void execute(event);\n }\n };\n\n const runOnItem = (action: Action, item: ListItem) =>\n run({ action, scope: 'item', item, count: 1, skipped: [] });\n\n const runOnSelection = (action: Action) => {\n const selected = list.getSelection();\n if (selected === null) {\n return;\n }\n run({\n action,\n scope: 'selection',\n selection: selected,\n count: selectedCount,\n skipped: selectedItems.filter((item) => !isAllowedOn(action, item)),\n });\n };\n\n const runOnResource = (action: Action) =>\n run({ action, scope: 'resource', count: count, skipped: [] });\n\n const renderActions = (item: ListItem): ReactNode => {\n const allowed = rowActions.filter((action) => isAllowedOn(action, item));\n const menu = rowMenu.filter((action) => isAllowedOn(action, item));\n if (allowed.length === 0 && menu.length === 0) {\n return null;\n }\n const running =\n pending?.endsWith(`\\u0000${item.id}`) === true ? pending.split('\\u0000')[0] : null;\n return (\n <RowActions\n item={item}\n itemLabel={getItemLabel(item)}\n actions={allowed}\n menu={menu}\n pending={running ?? null}\n onRun={(action) => runOnItem(action, item)}\n icons={actionIcons}\n />\n );\n };\n\n const pageCheckbox = (\n <Checkbox\n aria-label={intl.formatMessage(messages.selectPage)}\n isSelected={pageSelection === 'all'}\n isIndeterminate={pageSelection === 'some'}\n isDisabled={pageItems.length === 0}\n onChange={() => {\n const ids = pageItems.map((item) => item.id);\n if (pageSelection === 'all') {\n list.deselect(ids);\n } else {\n list.select(ids);\n }\n }}\n />\n );\n\n const sortable = columns.some((column) => column.sortable === true);\n const showToolbar =\n search ||\n filters.length > 0 ||\n sortable ||\n layouts.length > 1 ||\n resourceActions.length > 0 ||\n toolbar !== undefined;\n\n const body = (() => {\n if (error !== null) {\n return (\n <Alert\n tone=\"danger\"\n title={intl.formatMessage(messages.loadFailed)}\n actions={\n onRetry !== undefined ? (\n <Button size=\"sm\" onPress={onRetry}>\n {intl.formatMessage(messages.retry)}\n </Button>\n ) : undefined\n }\n >\n {error.message}\n </Alert>\n );\n }\n if (items === undefined) {\n return (\n <div className={styles.state}>\n <Spinner showLabel />\n </div>\n );\n }\n if (pageItems.length === 0) {\n return (\n <div className={styles.state}>\n <p>\n {hasQuery\n ? intl.formatMessage(messages.noResults)\n : (emptyMessage ?? intl.formatMessage(messages.empty))}\n </p>\n {hasQuery ? (\n <Button size=\"sm\" onPress={() => clearFilters()}>\n {intl.formatMessage(messages.clearFilters)}\n </Button>\n ) : null}\n </div>\n );\n }\n return (\n <Layout\n items={pageItems}\n columns={columns}\n renderValue={renderValue}\n getItemLabel={getItemLabel}\n getItemHref={getItemHref}\n selectable={selectable}\n isSelected={(item) => isItemSelected(selection, item.id)}\n onToggle={(item) => list.toggle(item.id)}\n renderActions={renderActions}\n sort={sort}\n onSort={(column) => list.toggleSort(column.id)}\n label={label}\n pageSelection={pageCheckbox}\n />\n );\n })();\n\n function clearFilters() {\n Object.keys(filterValues).forEach((id) => list.setFilter(id, undefined));\n if ((query.q ?? '') !== '') {\n list.setSearch('');\n }\n }\n\n return (\n <div className={cx(styles.list, className)}>\n {showToolbar ? (\n <div className={styles.toolbar}>\n <div className={styles.toolbarStart}>\n {search ? (\n <SearchField\n value={query.q ?? ''}\n onChange={(q) => list.setSearch(q)}\n />\n ) : null}\n {filters.length > 0 ? (\n <FilterControls\n filters={filters}\n values={filterValues}\n onChange={(id, value) => list.setFilter(id, value)}\n onLabel={filterLabels.remember}\n controls={filterControls}\n columns={columns}\n fields={fields}\n labels={filterLabels.labels}\n />\n ) : null}\n </div>\n <div className={styles.toolbarEnd}>\n {sortable ? (\n <div className={layout === 'table' ? styles.tableSort : undefined}>\n <SortSelect\n columns={columns}\n sort={sort}\n onChange={(next) => list.setSort(next)}\n />\n </div>\n ) : null}\n {layouts.length > 1 ? (\n <LayoutSwitch\n layouts={layouts}\n value={layout}\n onChange={(next) => {\n setLayout(next);\n onLayoutChange?.(next);\n }}\n />\n ) : null}\n {resourceActions\n .filter(\n (action) => selectedCount === 0 || !hasScope(action, 'selection'),\n )\n .map((action) => (\n <ActionButton\n key={action.id}\n action={action}\n icons={actionIcons}\n size=\"md\"\n href={getActionHref(action)}\n isPending={pending === keyOf(action)}\n onPress={() => runOnResource(action)}\n />\n ))}\n {toolbar}\n </div>\n </div>\n ) : null}\n <FilterChips\n filters={filters}\n values={filterValues}\n q={query.q}\n onRemove={(id) => list.setFilter(id, undefined)}\n onClearSearch={() => list.setSearch('')}\n onClear={clearFilters}\n columns={columns}\n fields={fields}\n labels={filterLabels.labels}\n />\n {selectedCount > 0 ? (\n <div className={styles.selection}>\n <span className={styles.selectionCount} role=\"status\">\n {selection.mode === 'all'\n ? intl.formatMessage(messages.allMatchingSelected, {\n total: selectedCount,\n })\n : intl.formatMessage(messages.selectedCount, { count: selectedCount })}\n </span>\n {selection.mode === 'ids' &&\n pageSelection === 'all' &&\n count > pageItems.length ? (\n <Button size=\"sm\" variant=\"ghost\" onPress={() => list.selectAllMatching()}>\n {intl.formatMessage(messages.selectAllMatching, { total: count })}\n </Button>\n ) : null}\n <Button size=\"sm\" variant=\"ghost\" onPress={() => list.clearSelection()}>\n {intl.formatMessage(messages.clearSelection)}\n </Button>\n {selectionActions.length > 0 ? (\n <div className={styles.selectionActions}>\n {selectionActions.map((action) => {\n const skipped = selectedItems.filter(\n (item) => !isAllowedOn(action, item),\n ).length;\n return (\n <ActionButton\n key={action.id}\n action={action}\n icons={actionIcons}\n size=\"md\"\n href={null}\n isPending={pending === keyOf(action)}\n isDisabled={\n (pending !== null && pending !== keyOf(action)) ||\n skipped >= selectedCount\n }\n onPress={() => runOnSelection(action)}\n />\n );\n })}\n </div>\n ) : null}\n </div>\n ) : null}\n <div className={styles.body} aria-busy={isLoading && items !== undefined}>\n {body}\n </div>\n {total !== undefined && error === null && total > 0 ? (\n <Pagination\n page={query.page ?? 1}\n perPage={query.perPage ?? pageItems.length}\n total={total}\n onPageChange={(page) => list.setPage(page)}\n perPageOptions={perPageOptions}\n onPerPageChange={(perPage) => list.setPerPage(perPage)}\n />\n ) : null}\n <ConfirmDialog\n event={confirming}\n onConfirm={(event) => void execute(event)}\n onCancel={() => setConfirming(null)}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
package/dist/ListView.module.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import "./ListView.css";
|
|
2
2
|
//#region src/ListView.module.css
|
|
3
|
-
var list = "panneau-list-
|
|
4
|
-
var toolbar = "panneau-toolbar
|
|
5
|
-
var toolbarStart = "panneau-
|
|
6
|
-
var toolbarEnd = "panneau-
|
|
7
|
-
var tableSort = "panneau-
|
|
8
|
-
var selection = "panneau-selection
|
|
9
|
-
var selectionCount = "panneau-
|
|
10
|
-
var selectionActions = "panneau-
|
|
11
|
-
var body = "panneau-body
|
|
12
|
-
var state = "panneau-state
|
|
3
|
+
var list = "panneau-list-view-list";
|
|
4
|
+
var toolbar = "panneau-list-view-toolbar";
|
|
5
|
+
var toolbarStart = "panneau-list-view-toolbar-start";
|
|
6
|
+
var toolbarEnd = "panneau-list-view-toolbar-end";
|
|
7
|
+
var tableSort = "panneau-list-view-table-sort";
|
|
8
|
+
var selection = "panneau-list-view-selection";
|
|
9
|
+
var selectionCount = "panneau-list-view-selection-count";
|
|
10
|
+
var selectionActions = "panneau-list-view-selection-actions";
|
|
11
|
+
var body = "panneau-list-view-body";
|
|
12
|
+
var state = "panneau-list-view-state";
|
|
13
13
|
var ListView_module_default = {
|
|
14
14
|
list,
|
|
15
15
|
toolbar,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { Action } from '@panneau/protocol';
|
|
2
|
+
import { type ActionIcons } from './icons';
|
|
2
3
|
export interface ActionButtonProps {
|
|
3
4
|
action: Action;
|
|
5
|
+
/** Icons of actions by id, over the icon the action names. */
|
|
6
|
+
icons?: ActionIcons | undefined;
|
|
4
7
|
/** The link of a `link` action. Links go through React Aria, so an app's router handles them. */
|
|
5
8
|
href: string | null;
|
|
6
9
|
onPress: () => void;
|
|
@@ -11,5 +14,5 @@ export interface ActionButtonProps {
|
|
|
11
14
|
'aria-label'?: string;
|
|
12
15
|
}
|
|
13
16
|
/** An action as a button, or as a link for a `link` action with a URL. */
|
|
14
|
-
export declare function ActionButton({ action, href, onPress, isPending, isDisabled, size, 'aria-label': ariaLabel, }: ActionButtonProps): import("react").JSX.Element;
|
|
17
|
+
export declare function ActionButton({ action, icons, href, onPress, isPending, isDisabled, size, 'aria-label': ariaLabel, }: ActionButtonProps): import("react").JSX.Element;
|
|
15
18
|
//# sourceMappingURL=ActionButton.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActionButton.d.ts","sourceRoot":"","sources":["../../src/actions/ActionButton.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"ActionButton.d.ts","sourceRoot":"","sources":["../../src/actions/ActionButton.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAKhD,OAAO,EAAE,KAAK,WAAW,EAAiB,MAAM,SAAS,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,KAAK,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAChC,iGAAiG;IACjG,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,iFAAiF;IACjF,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,EACzB,MAAM,EACN,KAAK,EACL,IAAI,EACJ,OAAO,EACP,SAAiB,EACjB,UAAkB,EAClB,IAAW,EACX,YAAY,EAAE,SAAS,GAC1B,EAAE,iBAAiB,+BA2BnB"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import Actions_module_default from "./Actions.module.js";
|
|
2
|
-
import { cx } from "../cx.js";
|
|
3
2
|
import { getActionIcon } from "./icons.js";
|
|
3
|
+
import { cx } from "../cx.js";
|
|
4
4
|
import { Button, Icon } from "@panneau/ui";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { Link } from "react-aria-components";
|
|
7
7
|
//#region src/actions/ActionButton.tsx
|
|
8
8
|
/** An action as a button, or as a link for a `link` action with a URL. */
|
|
9
|
-
function ActionButton({ action, href, onPress, isPending = false, isDisabled = false, size = "sm", "aria-label": ariaLabel }) {
|
|
10
|
-
const icon = getActionIcon(action);
|
|
9
|
+
function ActionButton({ action, icons, href, onPress, isPending = false, isDisabled = false, size = "sm", "aria-label": ariaLabel }) {
|
|
10
|
+
const icon = getActionIcon(action, icons);
|
|
11
11
|
if (href !== null) return /* @__PURE__ */ jsxs(Link, {
|
|
12
12
|
href,
|
|
13
13
|
"aria-label": ariaLabel,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActionButton.js","names":["styles"],"sources":["../../src/actions/ActionButton.tsx"],"sourcesContent":["import { Link } from 'react-aria-components';\n\nimport type { Action } from '@panneau/protocol';\nimport { Button, Icon } from '@panneau/ui';\n\nimport { cx } from '../cx';\nimport styles from './Actions.module.css';\nimport { getActionIcon } from './icons';\n\nexport interface ActionButtonProps {\n action: Action;\n /** The link of a `link` action. Links go through React Aria, so an app's router handles them. */\n href: string | null;\n onPress: () => void;\n isPending?: boolean;\n isDisabled?: boolean;\n size?: 'sm' | 'md';\n /** Accessible label, when the label alone isn't enough (`Edit Ada Lovelace`). */\n 'aria-label'?: string;\n}\n\n/** An action as a button, or as a link for a `link` action with a URL. */\nexport function ActionButton({\n action,\n href,\n onPress,\n isPending = false,\n isDisabled = false,\n size = 'sm',\n 'aria-label': ariaLabel,\n}: ActionButtonProps) {\n const icon = getActionIcon(action);\n if (href !== null) {\n return (\n <Link\n href={href}\n aria-label={ariaLabel}\n className={cx(styles.link, size === 'md' && styles.linkMd)}\n >\n {icon !== undefined ? <Icon icon={icon} size=\"sm\" /> : null}\n {action.label}\n </Link>\n );\n }\n return (\n <Button\n size={size}\n variant={action.destructive === true ? 'danger' : size === 'sm' ? 'ghost' : 'secondary'}\n {...(icon !== undefined ? { icon } : {})}\n isPending={isPending}\n isDisabled={isDisabled}\n aria-label={ariaLabel}\n onPress={onPress}\n >\n {action.label}\n </Button>\n );\n}\n"],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"ActionButton.js","names":["styles"],"sources":["../../src/actions/ActionButton.tsx"],"sourcesContent":["import { Link } from 'react-aria-components';\n\nimport type { Action } from '@panneau/protocol';\nimport { Button, Icon } from '@panneau/ui';\n\nimport { cx } from '../cx';\nimport styles from './Actions.module.css';\nimport { type ActionIcons, getActionIcon } from './icons';\n\nexport interface ActionButtonProps {\n action: Action;\n /** Icons of actions by id, over the icon the action names. */\n icons?: ActionIcons | undefined;\n /** The link of a `link` action. Links go through React Aria, so an app's router handles them. */\n href: string | null;\n onPress: () => void;\n isPending?: boolean;\n isDisabled?: boolean;\n size?: 'sm' | 'md';\n /** Accessible label, when the label alone isn't enough (`Edit Ada Lovelace`). */\n 'aria-label'?: string;\n}\n\n/** An action as a button, or as a link for a `link` action with a URL. */\nexport function ActionButton({\n action,\n icons,\n href,\n onPress,\n isPending = false,\n isDisabled = false,\n size = 'sm',\n 'aria-label': ariaLabel,\n}: ActionButtonProps) {\n const icon = getActionIcon(action, icons);\n if (href !== null) {\n return (\n <Link\n href={href}\n aria-label={ariaLabel}\n className={cx(styles.link, size === 'md' && styles.linkMd)}\n >\n {icon !== undefined ? <Icon icon={icon} size=\"sm\" /> : null}\n {action.label}\n </Link>\n );\n }\n return (\n <Button\n size={size}\n variant={action.destructive === true ? 'danger' : size === 'sm' ? 'ghost' : 'secondary'}\n {...(icon !== undefined ? { icon } : {})}\n isPending={isPending}\n isDisabled={isDisabled}\n aria-label={ariaLabel}\n onPress={onPress}\n >\n {action.label}\n </Button>\n );\n}\n"],"mappings":";;;;;;;;AAwBA,SAAgB,aAAa,EACzB,QACA,OACA,MACA,SACA,YAAY,OACZ,aAAa,OACb,OAAO,MACP,cAAc,aACI;CAClB,MAAM,OAAO,cAAc,QAAQ,KAAK;CACxC,IAAI,SAAS,MACT,OACI,qBAAC,MAAD;EACU;EACN,cAAY;EACZ,WAAW,GAAGA,uBAAO,MAAM,SAAS,QAAQA,uBAAO,MAAM;EAH7D,UAAA,CAKK,SAAS,KAAA,IAAY,oBAAC,MAAD;GAAY;GAAM,MAAK;EAAI,CAAA,IAAM,MACtD,OAAO,KACN;;CAGd,OACI,oBAAC,QAAD;EACU;EACN,SAAS,OAAO,gBAAgB,OAAO,WAAW,SAAS,OAAO,UAAU;EAC5E,GAAK,SAAS,KAAA,IAAY,EAAE,KAAK,IAAI,CAAC;EAC3B;EACC;EACZ,cAAY;EACH;EAER,UAAA,OAAO;CACJ,CAAA;AAEhB"}
|
package/dist/actions/Actions.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.panneau-row
|
|
1
|
+
.panneau-actions-row {
|
|
2
2
|
display: inline-flex;
|
|
3
3
|
flex-wrap: nowrap;
|
|
4
4
|
align-items: center;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
gap: var(--panneau-space-1);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
.panneau-toolbar
|
|
9
|
+
.panneau-actions-toolbar {
|
|
10
10
|
display: flex;
|
|
11
11
|
flex-wrap: wrap;
|
|
12
12
|
align-items: center;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/* A link that looks like a small ghost button (link actions: show, edit). */
|
|
17
|
-
.panneau-link
|
|
17
|
+
.panneau-actions-link {
|
|
18
18
|
display: inline-flex;
|
|
19
19
|
align-items: center;
|
|
20
20
|
gap: var(--panneau-space-1);
|
|
@@ -29,30 +29,30 @@
|
|
|
29
29
|
cursor: pointer;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
.panneau-link
|
|
32
|
+
.panneau-actions-link[data-hovered] {
|
|
33
33
|
background-color: var(--panneau-color-hover);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
.panneau-link
|
|
36
|
+
.panneau-actions-link[data-pressed] {
|
|
37
37
|
background-color: var(--panneau-color-pressed);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
.panneau-link
|
|
40
|
+
.panneau-actions-link[data-focus-visible] {
|
|
41
41
|
outline: var(--panneau-focus-width) solid var(--panneau-color-focus);
|
|
42
42
|
outline-offset: 1px;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
.panneau-
|
|
45
|
+
.panneau-actions-link-md {
|
|
46
46
|
height: var(--panneau-control-height);
|
|
47
47
|
padding: 0 var(--panneau-space-3);
|
|
48
48
|
font-size: var(--panneau-font-size-control);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
.panneau-message
|
|
51
|
+
.panneau-actions-message {
|
|
52
52
|
margin: 0;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
.panneau-skipped
|
|
55
|
+
.panneau-actions-skipped {
|
|
56
56
|
margin: var(--panneau-space-2) 0 0;
|
|
57
57
|
color: var(--panneau-color-warning);
|
|
58
58
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import "./Actions.css";
|
|
2
2
|
//#region src/actions/Actions.module.css
|
|
3
|
-
var row = "panneau-row
|
|
4
|
-
var toolbar = "panneau-toolbar
|
|
5
|
-
var link = "panneau-link
|
|
6
|
-
var linkMd = "panneau-
|
|
7
|
-
var message = "panneau-message
|
|
8
|
-
var skipped = "panneau-skipped
|
|
3
|
+
var row = "panneau-actions-row";
|
|
4
|
+
var toolbar = "panneau-actions-toolbar";
|
|
5
|
+
var link = "panneau-actions-link";
|
|
6
|
+
var linkMd = "panneau-actions-link-md";
|
|
7
|
+
var message = "panneau-actions-message";
|
|
8
|
+
var skipped = "panneau-actions-skipped";
|
|
9
9
|
var Actions_module_default = {
|
|
10
10
|
row,
|
|
11
11
|
toolbar,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Action } from '@panneau/protocol';
|
|
2
2
|
import type { ListItem } from '../types';
|
|
3
|
+
import { type ActionIcons } from './icons';
|
|
3
4
|
export interface RowActionsProps {
|
|
4
5
|
item: ListItem;
|
|
5
6
|
/** The label of the item, for the accessible names of its actions. */
|
|
@@ -11,7 +12,9 @@ export interface RowActionsProps {
|
|
|
11
12
|
/** Id of the action that is running on this item, if any. */
|
|
12
13
|
pending: string | null;
|
|
13
14
|
onRun: (action: Action) => void;
|
|
15
|
+
/** Icons of actions by id, over the icon the action names. */
|
|
16
|
+
icons?: ActionIcons | undefined;
|
|
14
17
|
}
|
|
15
18
|
/** The actions of a row: buttons, then an overflow menu. */
|
|
16
|
-
export declare function RowActions({ item, itemLabel, actions, menu, pending, onRun }: RowActionsProps): import("react").JSX.Element;
|
|
19
|
+
export declare function RowActions({ item, itemLabel, actions, menu, pending, onRun, icons, }: RowActionsProps): import("react").JSX.Element;
|
|
17
20
|
//# sourceMappingURL=RowActions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RowActions.d.ts","sourceRoot":"","sources":["../../src/actions/RowActions.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAIhD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"RowActions.d.ts","sourceRoot":"","sources":["../../src/actions/RowActions.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAIhD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAIzC,OAAO,EAAE,KAAK,WAAW,EAAiB,MAAM,SAAS,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,QAAQ,CAAC;IACf,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,gCAAgC;IAChC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,oCAAoC;IACpC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,8DAA8D;IAC9D,KAAK,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CACnC;AAED,4DAA4D;AAC5D,wBAAgB,UAAU,CAAC,EACvB,IAAI,EACJ,SAAS,EACT,OAAO,EACP,IAAI,EACJ,OAAO,EACP,KAAK,EACL,KAAK,GACR,EAAE,eAAe,+BAwDjB"}
|