@k-int/stripes-kint-components 5.21.0 → 5.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [5.23.0](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.22.0...v5.23.0) (2025-06-25)
2
+
3
+
4
+ ### Features
5
+
6
+ * SearchKeyControl subIndexes ([40036a4](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/40036a4353f24e47480e8060d901ef44c5c3b6ed))
7
+
8
+ # [5.22.0](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.21.0...v5.22.0) (2025-06-13)
9
+
10
+
11
+ ### Features
12
+
13
+ * Customisable NoResultsMessage ([f4b18d8](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/f4b18d809ba83e7d7f6141b2ace09f3e32e18b02))
14
+
1
15
  # [5.21.0](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.20.1...v5.21.0) (2025-06-06)
2
16
 
3
17
 
@@ -66,7 +66,7 @@ const NoResultsMessage = _ref => {
66
66
  iconRootClass: _NoResultsMessage.default.noResultsMessageIcon
67
67
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
68
68
  className: _NoResultsMessage.default.noResultsMessageLabel,
69
- children: userLabel ?? label
69
+ children: userLabel ?? labelOverrides.noResultsLabel ?? label
70
70
  })]
71
71
  }), !filterPaneIsVisible && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Button, {
72
72
  buttonClass: _NoResultsMessage.default.noResultsMessageButton,
@@ -46,6 +46,7 @@ const SASQLookupComponent = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =>
46
46
  },
47
47
  mainPaneProps = {},
48
48
  mclProps = {},
49
+ noResultsProps = {},
49
50
  noSearchField,
50
51
  persistedPanesetProps = {},
51
52
  queryParameterGenerator = _utils.generateKiwtQuery,
@@ -260,6 +261,7 @@ const SASQLookupComponent = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =>
260
261
  intlKey: passedIntlKey,
261
262
  intlNS: passedIntlNS,
262
263
  labelOverrides: labelOverrides,
264
+ noResultsProps: noResultsProps,
263
265
  query: query,
264
266
  rowNavigation: rowNavigation,
265
267
  toggleFilterPane: toggleFilterPane,
@@ -27,6 +27,7 @@ const TableBody = _ref => {
27
27
  formatter = {},
28
28
  ...mclProps
29
29
  } = {},
30
+ noResultsProps = {},
30
31
  onSort,
31
32
  path,
32
33
  resultColumns,
@@ -82,6 +83,7 @@ const TableBody = _ref => {
82
83
  } = _ref2;
83
84
  return item.id === match?.params?.id;
84
85
  }, [match?.params?.id]);
86
+ const NoResultsComponent = (0, _react.useMemo)(() => noResultsProps?.component ?? _NoResultsMessage.default, [noResultsProps?.component]);
85
87
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.MultiColumnList, {
86
88
  autosize: true,
87
89
  columnMapping: columnMapping,
@@ -90,7 +92,7 @@ const TableBody = _ref => {
90
92
  ,
91
93
  hasMargin: true,
92
94
  interactive: rowNavigation,
93
- isEmptyMessage: /*#__PURE__*/(0, _jsxRuntime.jsx)(_NoResultsMessage.default, {
95
+ isEmptyMessage: /*#__PURE__*/(0, _jsxRuntime.jsx)(NoResultsComponent, {
94
96
  error,
95
97
  filterPaneIsVisible: filterPaneVisible,
96
98
  intlKey: passedIntlKey,
@@ -99,7 +101,8 @@ const TableBody = _ref => {
99
101
  isLoading,
100
102
  labelOverrides,
101
103
  searchTerm: query.query,
102
- toggleFilterPane
104
+ toggleFilterPane,
105
+ ...noResultsProps // Anything passed in directly takes precedence
103
106
  }),
104
107
  isSelected: isSelected,
105
108
  onHeaderClick: onSort,
@@ -25,9 +25,12 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
25
25
 
26
26
  // Memoise this process so keyState changes if and only if options/qIndex change
27
27
  const createKeyState = (0, _react.useCallback)(() => options.reduce((acc, curr) => {
28
+ // Is in use if URL contains ALL of the indexes
29
+ const subindexes = (curr?.indexes?.length ?? 0) > 0 ? curr.indexes : [curr.key];
28
30
  acc[curr.key] = {
29
- inUse: qIndexArray?.includes(curr.key),
30
- label: curr.label ?? curr.key
31
+ inUse: subindexes.every(si => qIndexArray.includes(si)),
32
+ label: curr.label ?? curr.key,
33
+ subIndexes: curr.indexes
31
34
  };
32
35
  return acc;
33
36
  }, {}), [options, qIndexArray]);
@@ -48,11 +51,44 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
48
51
  } = (0, _hooks.usePrevNextPagination)({
49
52
  defaultToPageOne: false
50
53
  });
54
+ const handleSearchKeyChange = (0, _react.useCallback)(_ref2 => {
55
+ let {
56
+ e: {
57
+ target: {
58
+ checked: targetIsChecked
59
+ } = {}
60
+ } = {},
61
+ key,
62
+ value
63
+ } = _ref2;
64
+ // Set up which indexes need to change
65
+ const indicesToChange = (value?.subIndexes?.length ?? 0) > 0 ? value.subIndexes : [key];
66
+
67
+ // If false, we must remove from the qIndex
68
+ if (!targetIsChecked) {
69
+ indicesToChange.forEach(ind => {
70
+ const indexOfKey = qIndexArray.indexOf(ind);
71
+ if (indexOfKey > -1) {
72
+ // only splice array when item is found
73
+ qIndexArray.splice(indexOfKey, 1); // 2nd parameter means remove one item only
74
+ }
75
+ });
76
+ } else {
77
+ // If true, we need to add to qIndex
78
+ indicesToChange.forEach(ind => {
79
+ qIndexArray.push(ind);
80
+ });
81
+ }
82
+ setQIndex(qIndexArray?.join(','));
83
+ if (currentPage) {
84
+ resetPage();
85
+ }
86
+ }, [currentPage, qIndexArray, resetPage, setQIndex]);
51
87
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
52
88
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
53
89
  className: _SearchKeyControl.default.searchKeyControlContainer,
54
- children: Object.entries(keyState).map(_ref2 => {
55
- let [key, value] = _ref2;
90
+ children: Object.entries(keyState).map(_ref3 => {
91
+ let [key, value] = _ref3;
56
92
  /* At this point we have "key" corresponding to a searchKey option,
57
93
  * and "value" an object of the shape
58
94
  {
@@ -64,23 +100,11 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
64
100
  checked: value?.inUse,
65
101
  className: _SearchKeyControl.default.searchKeyControlElement,
66
102
  label: value?.label,
67
- onChange: e => {
68
- // If false, we must remove from the qIndex
69
- if (!e.target.checked) {
70
- const indexOfKey = qIndexArray.indexOf(key);
71
- if (indexOfKey > -1) {
72
- // only splice array when item is found
73
- qIndexArray.splice(indexOfKey, 1); // 2nd parameter means remove one item only
74
- }
75
- } else {
76
- // If true, we need to add to qIndex
77
- qIndexArray.push(key);
78
- }
79
- setQIndex(qIndexArray?.join(','));
80
- if (currentPage) {
81
- resetPage();
82
- }
83
- }
103
+ onChange: e => handleSearchKeyChange({
104
+ e,
105
+ key,
106
+ value
107
+ })
84
108
  }, `search-key-control-${key}`);
85
109
  })
86
110
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k-int/stripes-kint-components",
3
- "version": "5.21.0",
3
+ "version": "5.23.0",
4
4
  "description": "Stripes Component library for K-Int specific applications",
5
5
  "sideEffects": [
6
6
  "*.css"
@@ -0,0 +1,80 @@
1
+ # FormattedKintMessage
2
+
3
+ A wrapper component around `react-intl`'s `FormattedMessage` that enhances internationalization capabilities by providing automatic key resolution, direct value overrides, and flexible fallback messaging. **FormattedKintMessage** is designed to work within the `k-int` internationalization ecosystem, leveraging `useIntlKey` for intelligent message key lookup via `useIntlKey` and other intl hooks. [See intl hooks for details](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/-/tree/main/src/lib/hooks/intlHooks)
4
+
5
+ ## Basic Usage
6
+
7
+ Below is an example demonstrating the most basic use of **FormattedKintMessage** to display translated text using automatic key resolution.
8
+
9
+ ```jsx
10
+ import { FormattedKintMessage } from '@k-int/stripes-kint-components';
11
+
12
+ const MyInternationalizedComponent = () => (
13
+ <div>
14
+ {/* Resolves key using intlKey/intlNS from context or props */}
15
+ <FormattedKintMessage id="title.greeting" intlKey="myModule" intlNS="myApp" />
16
+ </div>
17
+ );
18
+
19
+ export default MyInternationalizedComponent;
20
+ ```
21
+
22
+ ### Explanation
23
+
24
+ * **`id`**: The relative ID for your message, which will be prefixed by the resolved `intlKey`.
25
+
26
+ * **`intlKey` / `intlNS`**: These props are used by `useIntlKey` to determine the base prefix for your message ID, allowing for flexible internationalization key management.
27
+
28
+ ---
29
+
30
+ ## Override and Fallback Message Usage
31
+
32
+ **`overrideValue`** and **`fallbackMessage`** both provide ways to control the message displayed, but they serve distinct purposes:
33
+
34
+ * **`overrideValue`**: Use this when you want to **explicitly define** the content to be displayed, entirely bypassing the translation lookup process for the `id` prop.
35
+ * The `overrideValue` can be a direct string (e.g., "Custom Text") or another existing `react-intl` message key (e.g., "common.cancelButton").
36
+ * If it's a key, `FormattedKintMessage` will attempt to format that key; otherwise, it displays the string literally.
37
+ * This is useful for dynamic content that might not have a static translation key or for providing specific, hardcoded text for a particular instance.
38
+ * As of `stripes-kint-components ^3.0.0`, `overrideValue` must be a string.
39
+
40
+ * **`fallbackMessage`**: Use this when you expect a translation to exist for your primary `id`, but you want a **graceful, silent fallback** if that translation is unexpectedly missing.
41
+ * This prevents `react-intl`'s default console warnings for missing messages.
42
+ * The `fallbackMessage` can also be a direct string or another existing `react-intl` message key.
43
+ * It acts as a safety net, ensuring *something* is displayed even if the primary translation isn't available.
44
+
45
+ **In summary:**
46
+
47
+ * Choose `overrideValue` for **deliberate replacement** of the translation.
48
+
49
+ * Choose `fallbackMessage` for **graceful handling of missing translations**.
50
+
51
+ ```jsx
52
+ import { FormattedKintMessage } from '@k-int/stripes-kint-components';
53
+
54
+ const OverrideAndFallbackExample = () => (
55
+ <div>
56
+ {/* Using an overrideValue (direct string) */}
57
+ <FormattedKintMessage id="unimportant.key" overrideValue="This is a direct message." />
58
+
59
+ {/* Using an overrideValue (another existing intl key) */}
60
+ <FormattedKintMessage id="unused.key" overrideValue="someOtherIntlKey" />
61
+
62
+ {/* Using a fallbackMessage when the primary key is not found (direct string) */}
63
+ <FormattedKintMessage id="missing.message" fallbackMessage="Default message if not found." />
64
+
65
+ {/* Using a fallbackMessage that is itself an existing intl key */}
66
+ <FormattedKintMessage id="another.missing.message" fallbackMessage="common.error.generic" />
67
+ </div>
68
+ );
69
+ ```
70
+
71
+ ## Props
72
+
73
+ | Prop | Type | Required | Description |
74
+ | :----------------------- | :--------------------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
75
+ | `fallbackMessage` | `string` | ✕ | A message or `react-intl` key to display if the primary `id` (after `intlKey` resolution) does not have a corresponding translation. This acts as a silent `defaultMessage` without console warnings. If it is a key, it will be formatted; otherwise, it will be displayed literally. |
76
+ | `id` | `string` | ✓ | The relative message ID to be translated. This ID is combined with the `intlKey` (resolved via `useIntlKey`) to form the full translation key. |
77
+ | `intlKey` | `string` | ✕ | A base internationalization key passed to the internal `useIntlKey` hook to resolve the full message path. |
78
+ | `intlNS` | `string` | ✕ | An internationalization namespace passed to the internal `useIntlKey` hook for resolving the full message path. |
79
+ | `overrideValue` | `string` | ✕ | A string value that, if present, takes precedence over all other translation logic. It can be a direct string to display or another `react-intl` message `id`. If it's a key, it will be formatted; otherwise, it will be displayed literally. **As of `stripes-kint-components ^3.0.0`, this must be a string.** |
80
+ | `...formattedMessageProps` | `object` | ✕ | Any additional props supported by `react-intl`'s original `FormattedMessage` component, such as `values` for injecting dynamic content into the message, `tagName`, etc. |
@@ -50,7 +50,7 @@ const NoResultsMessage = ({
50
50
  iconRootClass={css.noResultsMessageIcon}
51
51
  />
52
52
  }
53
- <span className={css.noResultsMessageLabel}>{userLabel ?? label}</span>
53
+ <span className={css.noResultsMessageLabel}>{userLabel ?? labelOverrides.noResultsLabel ?? label}</span>
54
54
  </div>
55
55
  {/* If the filter pane is closed we show a button that toggles filter pane */}
56
56
  {!filterPaneIsVisible && (
@@ -0,0 +1,81 @@
1
+ # NoResultsMessage
2
+
3
+ A component that displays messages based on the state of data fetching and user interaction within lookup views.
4
+ **NoResultsMessage** dynamically adjusts its icon and text to indicate loading states, no results found after a search, no search terms entered, or an error during data retrieval.
5
+ It also provides a button to toggle the visibility of a filter pane. Generally called within context of `SASQLookupComponent -> TableBody`
6
+
7
+ ## Basic Usage
8
+
9
+ Below is an example demonstrating how **NoResultsMessage** might be used within a component that manages data fetching, such as `SASQTableBody` or directly within a `MultiColumnList`'s `isEmptyMessage` prop.
10
+
11
+ ```jsx
12
+ import { useState } from 'react';
13
+ import NoResultsMessage from '@k-int/stripes-kint-components/lib/NoResultsMessage';
14
+
15
+ const MyDataView = () => {
16
+ const [isLoading, setIsLoading] = useState(true);
17
+ const [searchTerm, setSearchTerm] = useState('');
18
+ const [filterPaneIsVisible, setFilterPaneIsVisible] = useState(true);
19
+ const [isError, setIsError] = useState(false);
20
+ const [error, setError] = useState(null);
21
+
22
+ const toggleFilterPane = () => setFilterPaneIsVisible(prev => !prev);
23
+
24
+ // Simulate data fetching
25
+ useState(() => {
26
+ setTimeout(() => {
27
+ setIsLoading(false);
28
+ // setSearchTerm('example'); // Uncomment to simulate a search term
29
+ // setIsError(true); // Uncomment to simulate an error
30
+ // setError({ message: 'Failed to fetch data' });
31
+ }, 2000);
32
+ }, []);
33
+
34
+ return (
35
+ <div style={{ padding: '20px', border: '1px solid #ccc', minHeight: '200px' }}>
36
+ <h3>Data Display Area</h3>
37
+ <NoResultsMessage
38
+ isLoading={isLoading}
39
+ searchTerm={searchTerm}
40
+ filterPaneIsVisible={filterPaneIsVisible}
41
+ toggleFilterPane={toggleFilterPane}
42
+ isError={isError}
43
+ error={error}
44
+ // label="Custom No Results Text" // Optional: provide custom label
45
+ // icon="info" // Optional: provide custom icon
46
+ // labelOverrides={{ showFilters: 'Open Filters Panel' }} // Optional: override specific labels
47
+ />
48
+ </div>
49
+ );
50
+ };
51
+
52
+ export default MyDataView;
53
+ ```
54
+
55
+ ### Explanation
56
+
57
+ * **isLoading:** Set to `true` when data is being fetched, displaying a loading message and spinner icon.
58
+
59
+ * **searchTerm:** Indicates whether a search term has been entered by the user. Influences the displayed message for "no results" vs. "no terms entered."
60
+
61
+ * **filterPaneIsVisible:** Controls the visibility of the "Show Filters" button.
62
+
63
+ * **toggleFilterPane:** A callback function triggered by the "Show Filters" button, typically to open the filter pane.
64
+
65
+ * **isError** and **error:** Used to display an error icon and the error message if the data fetching operation fails.
66
+
67
+ ## Props
68
+
69
+ | **Prop** | **Type** | **Required** | **Description** |
70
+ |-----------------------|--------------------------|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
71
+ | `filterPaneIsVisible` | `boolean` | ✓ | Indicates if the associated filter pane is currently visible. Controls the display of the "Show Filters" button. |
72
+ | `toggleFilterPane` | `() => void` | ✓ | Callback function invoked when the "Show Filters" button is clicked. Typically used to toggle the filter pane's visibility. |
73
+ | `error` | `object` | ✕ | An error object (e.g., from `react-query`) containing details of a failed request. Its `message` property will be displayed if `isError` is `true`. |
74
+ | `icon` | `string` | ✕ | An optional custom icon string (from `@folio/stripes/components/Icon`) to override the default icon based on the message state. |
75
+ | `intlKey` | `string` | ✕ | A base internationalization key used by the internal `useKintIntl` hook to resolve localized messages. |
76
+ | `intlNS` | `string` | ✕ | An internationalization namespace used by the internal `useKintIntl` hook. |
77
+ | `isLoading` | `boolean` | ✕ | When `true`, indicates that data is currently being loaded, displaying a loading message. |
78
+ | `isError` | `boolean` | ✕ | When `true`, indicates an error occurred during data fetching, displaying an error message. |
79
+ | `label` | `string`, `node`, `func` | ✕ | An optional custom label to override all default message content. |
80
+ | `labelOverrides` | `object` | ✕ | An object for overriding specific parts of the default labels, such as `{ noResultsLabel: 'No records found' }` or `{ showFilters: 'View Filters' }`. |
81
+ | `searchTerm` | `string` | ✕ | The current search query string. Used to differentiate between "no results for search" and "no search term entered" messages. |
@@ -57,29 +57,30 @@ In this example, **SASQLookupComponent**:
57
57
 
58
58
  ## Props
59
59
 
60
- | Name | Type | Description | Default | Required |
61
- |---------------------------------|-------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------|----------|
62
- | `id` | `string` | A unique identifier for the lookup instance. Used to generate unique keys for query state, persistent paneset, and filter pane visibility in local storage. | | ✓ |
63
- | `fetchParameters` | `object` | An object that defines the endpoints and SASQ configuration used for fetching data. It must include:<br><br>• **endpoint:** The URL used to fetch the main data set.<br>• **SASQ_MAP:** An object containing settings for search, filter, pagination (e.g., `searchKey`, `perPage`, etc.). Additional keys (such as `itemEndpoint`) can also be provided if needed. | `{}` | ✓ |
64
- | `FilterComponent` | `func` or `node` | A custom component used to render additional filtering options in the filter pane. This component receives props for managing active filters, search state, and resetting filters. | `() => null` | ✕ |
65
- | `FilterPaneHeaderComponent` | `func` or `node` | A component rendered at the top of the filter pane. Use this to customize the header area of the filter pane. | `() => null` | ✕ |
66
- | `filterPaneProps` | `object` | Additional props to be passed to the filter pane. These props can customize the appearance and behavior of the filter pane (such as first or last menu elements). | `{}` | ✕ |
67
- | `intlKey` | `string` | A base internationalization key used by the internal `useKintIntl` hook to resolve localized messages (e.g., for labels or pane titles). | | ✕ |
68
- | `intlNS` | `string` | An internationalization namespace used by the internal `useKintIntl` hook. | | ✕ |
69
- | `labelOverrides` | `object` | An object for overriding default labels, such as the text for displaying the count of found values. | `{}` | ✕ |
70
- | `lookupQueryNamespaceGenerator` | `func` | A function that generates a query namespace (an array) for `react-query` based on provided parameters such as `currentPage`, `namespace`, `id`, `query`, etc. This ensures that queries are uniquely identified. | See default implementation in source code | ✕ |
71
- | `mainPaneProps` | `object` | Additional props to be passed to the main pane containing the lookup results. This can include custom menu elements or styling. | `{}` | ✕ |
72
- | `mclProps` | `object` | Custom properties to be passed to the underlying Multi-Column List (MCL) component. These properties are merged with pagination props derived from `usePrevNextPagination`. | `{}` | ✕ |
73
- | `noSearchField` | `bool` | When set to `true`, the search field is not rendered in the filter pane. Use this if you want to disable search input. | `false` | ✕ |
74
- | `persistedPanesetProps` | `object` | Additional props to be passed to the **PersistedPaneset** component, which manages the persistent layout of the filter and main panes. | `{}` | ✕ |
75
- | `queryParameterGenerator` | `func` | A function that takes the SASQ_MAP configuration and current query values to generate an array of query parameters for the fetch call. Defaults to `generateKiwtQuery`. | `generateKiwtQuery` | ✕ |
76
- | `RenderBody` | `func` or `node` | A component used to render the body of the main pane (typically a table). If not provided, the internal **TableBody** component is used. | `TableBody` | ✕ |
77
- | `rowNavigation` | `bool` | Flag to enable or disable row navigation. When enabled, clicking on a row triggers navigation to a detailed view. | `true` | ✕ |
78
- | `sasqProps` | `object` | Additional props to override or extend the default SASQ query behavior. These props can include custom query values, setters, or getters. | | ✕ |
79
- | `searchableIndexes` | `arrayOf(object)` | An array of objects defining the options for the search index selection component (`SearchKeyControl`), rendered below the search field. Each object typically has `value` and `label` keys. If empty or omitted, the qindex checkboxes are not shown. | `[]` | ✕ |
80
- | `searchFieldAriaLabel` | `string` | Accessibility label for the search field. | | ✕ |
81
- | `searchFieldProps` | `object` | Additional props to be passed to the **SearchField** component for customizing its behavior or styling. | | ✕ |
82
- | `children` | `node`, `func` | Optional children to be rendered below the main pane. These are rendered within the **PersistedPaneset** container. | | ✕ |
60
+ | Name | Type | Description | Default | Required |
61
+ |---------------------------------|------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------|----------|
62
+ | `id` | `string` | A unique identifier for the lookup instance. Used to generate unique keys for query state, persistent paneset, and filter pane visibility in local storage. | | ✓ |
63
+ | `fetchParameters` | `object` | An object that defines the endpoints and SASQ configuration used for fetching data. It must include:<br><br>• **endpoint:** The URL used to fetch the main data set.<br>• **SASQ_MAP:** An object containing settings for search, filter, pagination (e.g., `searchKey`, `perPage`, etc.). Additional keys (such as `itemEndpoint`) can also be provided if needed. | `{}` | ✓ |
64
+ | `FilterComponent` | `func` or `node` | A custom component used to render additional filtering options in the filter pane. This component receives props for managing active filters, search state, and resetting filters. | `() => null` | ✕ |
65
+ | `FilterPaneHeaderComponent` | `func` or `node` | A component rendered at the top of the filter pane. Use this to customize the header area of the filter pane. | `() => null` | ✕ |
66
+ | `filterPaneProps` | `object` | Additional props to be passed to the filter pane. These props can customize the appearance and behavior of the filter pane (such as first or last menu elements). | `{}` | ✕ |
67
+ | `intlKey` | `string` | A base internationalization key used by the internal `useKintIntl` hook to resolve localized messages (e.g., for labels or pane titles). | | ✕ |
68
+ | `intlNS` | `string` | An internationalization namespace used by the internal `useKintIntl` hook. | | ✕ |
69
+ | `labelOverrides` | `object` | An object for overriding default labels, such as the text for displaying the count of found values. | `{}` | ✕ |
70
+ | `lookupQueryNamespaceGenerator` | `func` | A function that generates a query namespace (an array) for `react-query` based on provided parameters such as `currentPage`, `namespace`, `id`, `query`, etc. This ensures that queries are uniquely identified. | See default implementation in source code | ✕ |
71
+ | `mainPaneProps` | `object` | Additional props to be passed to the main pane containing the lookup results. This can include custom menu elements or styling. | `{}` | ✕ |
72
+ | `mclProps` | `object` | Custom properties to be passed to the underlying Multi-Column List (MCL) component. These properties are merged with pagination props derived from `usePrevNextPagination`. | `{}` | ✕ |
73
+ | `noResultsProps` | `object` | Props to be passed down to the [NoResultsMessage](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/-/tree/main/src/lib/NoResultsMessage) splash screen component rendered when no results are present from the query. If `component` is passed, this will be rendered with the props available to `NoResultsMessage` | {} | ✕ |
74
+ | `noSearchField` | `bool` | When set to `true`, the search field is not rendered in the filter pane. Use this if you want to disable search input. | `false` | ✕ |
75
+ | `persistedPanesetProps` | `object` | Additional props to be passed to the **PersistedPaneset** component, which manages the persistent layout of the filter and main panes. | `{}` | ✕ |
76
+ | `queryParameterGenerator` | `func` | A function that takes the SASQ_MAP configuration and current query values to generate an array of query parameters for the fetch call. Defaults to `generateKiwtQuery`. | `generateKiwtQuery` | ✕ |
77
+ | `RenderBody` | `func` or `node` | A component used to render the body of the main pane (typically a table). If not provided, the internal **TableBody** component is used. | `TableBody` | ✕ |
78
+ | `rowNavigation` | `bool` | Flag to enable or disable row navigation. When enabled, clicking on a row triggers navigation to a detailed view. | `true` | ✕ |
79
+ | `sasqProps` | `object` | Additional props to override or extend the default SASQ query behavior. These props can include custom query values, setters, or getters. | | ✕ |
80
+ | `searchableIndexes` | `arrayOf(object)` | An array of objects defining the options for the search index selection component (`SearchKeyControl`), rendered below the search field. Each object typically has `value` and `label` keys. If empty or omitted, the qindex checkboxes are not shown. | `[]` | ✕ |
81
+ | `searchFieldAriaLabel` | `string` | Accessibility label for the search field. | | ✕ |
82
+ | `searchFieldProps` | `object` | Additional props to be passed to the **SearchField** component for customizing its behavior or styling. | | ✕ |
83
+ | `children` | `node`, `func` | Optional children to be rendered below the main pane. These are rendered within the **PersistedPaneset** container. | | ✕ |
83
84
  Below is an additional section for **SASQLookupComponent** documentation that describes the available functionality on the component's ref along with a worked example.
84
85
 
85
86
 
@@ -54,6 +54,7 @@ const SASQLookupComponent = forwardRef((props, ref) => {
54
54
  },
55
55
  mainPaneProps = {},
56
56
  mclProps = {},
57
+ noResultsProps = {},
57
58
  noSearchField,
58
59
  persistedPanesetProps = {},
59
60
  queryParameterGenerator = generateKiwtQuery, // Expects a function which accepts SASQ_MAP and nsValues
@@ -304,6 +305,7 @@ const SASQLookupComponent = forwardRef((props, ref) => {
304
305
  intlKey={passedIntlKey}
305
306
  intlNS={passedIntlNS}
306
307
  labelOverrides={labelOverrides}
308
+ noResultsProps={noResultsProps}
307
309
  query={query}
308
310
  rowNavigation={rowNavigation}
309
311
  toggleFilterPane={toggleFilterPane}
@@ -61,24 +61,25 @@ export default MyTableView;
61
61
 
62
62
  ## Props
63
63
 
64
- | Prop | Type | Required | Description |
65
- |---------------------|--------------------------------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
66
- | `data` | `{ totalRecords: number, results: object[] }` | ✓ | Data to display. `results` contains the items, `totalRecords` sets pagination bounds. |
67
- | `query` | `object` | | Current query state (e.g., `{ sort: 'name', query: 'search term' }`). Used for sorting and empty state messages. |
68
- | `path` | `string` | ✓ | Base path for row URLs. Each row links to `{path}/{rowData.id}`. |
69
- | `resultColumns` | `Array<{ propertyPath: string, label: string }>` | | Columns to render. `propertyPath` accesses the data field, `label` sets the header. |
70
- | `fetchNextPage` | `({ pageParam: number }) => void` | | Callback to load more data. `pageParam` indicates the next page index. |
71
- | `mclProps` | `object` | ✕ | Props passed to the underlying [MultiColumnList](https://github.com/folio-org/stripes-components/tree/master/lib/MultiColumnList). Example: `formatter`, `id`. |
72
- | `rowNavigation` | `boolean` | ✕ | Enables navigation to detail views on row click. Default: `true`. |
73
- | `filterPaneVisible` | `boolean` | ✕ | Toggles filter pane visibility in `NoResultsMessage`. |
74
- | `toggleFilterPane` | `() => void` | ✕ | Callback to toggle filter pane. Used in `NoResultsMessage`. |
75
- | `intlKey` | `string` | ✕ | Base key for internationalizing labels. See `useKintIntl`. |
76
- | `intlNS` | `string` | ✕ | Namespace for internationalization. See `useKintIntl`. |
77
- | `labelOverrides` | `object` | ✕ | Overrides default labels (e.g., `{ noResultsFound: 'No items' }`). |
78
- | `match` | `object` | | React Router `match` object. Used to highlight the selected row via URL `id` param. |
79
- | `isLoading` | `boolean` | | Shows loading state in `NoResultsMessage`. |
80
- | `isError` | `boolean` | | Shows error state in `NoResultsMessage`. |
81
- | `error` | `object` | ✕ | Error object displayed in `NoResultsMessage`. |
64
+ | Prop | Type | Required | Description |
65
+ |---------------------|--------------------------------------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
66
+ | `data` | `{ totalRecords: number, results: object[] }` | ✓ | Data to display. `results` contains the items, `totalRecords` sets pagination bounds. |
67
+ | `error` | `object` | | Error object displayed in `NoResultsMessage`. |
68
+ | `fetchNextPage` | `({ pageParam: number }) => void` | ✓ | Callback to load more data. `pageParam` indicates the next page index. |
69
+ | `filterPaneVisible` | `boolean` | | Toggles filter pane visibility in `NoResultsMessage`. |
70
+ | `intlKey` | `string` | | Base key for internationalizing labels. See `useKintIntl`. |
71
+ | `intlNS` | `string` | ✕ | Namespace for internationalization. See `useKintIntl`. |
72
+ | `isError` | `boolean` | ✕ | Shows error state in `NoResultsMessage`. |
73
+ | `isLoading` | `boolean` | ✕ | Shows loading state in `NoResultsMessage`. |
74
+ | `labelOverrides` | `object` | ✕ | Overrides default labels (e.g., `{ noResultsFound: 'No items' }`). |
75
+ | `match` | `object` | ✕ | React Router `match` object. Used to highlight the selected row via URL `id` param. |
76
+ | `mclProps` | `object` | ✕ | Props passed to the underlying [MultiColumnList](https://github.com/folio-org/stripes-components/tree/master/lib/MultiColumnList). Example: `formatter`, `id`. |
77
+ | `noResultsProps` | `object` | ✕ | Props to be passed down to the [NoResultsMessage](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/-/tree/main/src/lib/NoResultsMessage) splash screen component rendered when no results are present from the query. If `component` is passed, this will be rendered with the props available to `NoResultsMessage` |
78
+ | `path` | `string` | | Base path for row URLs. Each row links to `{path}/{rowData.id}`. |
79
+ | `query` | `object` | | Current query state (e.g., `{ sort: 'name', query: 'search term' }`). Used for sorting and empty state messages. |
80
+ | `resultColumns` | `Array<{ propertyPath: string, label: string }>` | | Columns to render. `propertyPath` accesses the data field, `label` sets the header. |
81
+ | `rowNavigation` | `boolean` | ✕ | Enables navigation to detail views on row click. Default: `true`. |
82
+ | `toggleFilterPane` | `() => void` | ✕ | Callback to toggle filter pane. Used in `NoResultsMessage`. |
82
83
 
83
84
  ## Key Features
84
85
 
@@ -1,4 +1,4 @@
1
- import { useCallback } from 'react';
1
+ import { useCallback, useMemo } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
 
4
4
  import { useHistory, useLocation } from 'react-router-dom';
@@ -23,6 +23,7 @@ const TableBody = ({
23
23
  formatter = {},
24
24
  ...mclProps
25
25
  } = {},
26
+ noResultsProps = {},
26
27
  onSort,
27
28
  path,
28
29
  resultColumns,
@@ -76,6 +77,7 @@ const TableBody = ({
76
77
  }, [getRowUrl, history, rowNavigation]);
77
78
 
78
79
  const isSelected = useCallback(({ item }) => item.id === match?.params?.id, [match?.params?.id]);
80
+ const NoResultsComponent = useMemo(() => noResultsProps?.component ?? NoResultsMessage, [noResultsProps?.component]);
79
81
 
80
82
  return (
81
83
  <MultiColumnList
@@ -86,7 +88,7 @@ const TableBody = ({
86
88
  hasMargin
87
89
  interactive={rowNavigation}
88
90
  isEmptyMessage={
89
- <NoResultsMessage
91
+ <NoResultsComponent
90
92
  {...{
91
93
  error,
92
94
  filterPaneIsVisible: filterPaneVisible,
@@ -96,7 +98,8 @@ const TableBody = ({
96
98
  isLoading,
97
99
  labelOverrides,
98
100
  searchTerm: query.query,
99
- toggleFilterPane
101
+ toggleFilterPane,
102
+ ...noResultsProps, // Anything passed in directly takes precedence
100
103
  }}
101
104
  />
102
105
  }
@@ -1,12 +1,14 @@
1
1
  # SearchKeyControl
2
2
 
3
- A component that renders a group of checkboxes allowing users to select one or more search keys (indexes). It directly interacts with the `qIndex` URL parameter (managed via the `useQIndex` hook) to reflect the currently active search indexes.
3
+ A component that renders a group of checkboxes allowing users to select one or more search keys (indexes). It synchronizes its state with the `qIndex` URL parameter (managed via the `useQIndex` hook), updating this parameter by injecting or removing individual keys based on user interactions.
4
4
 
5
- > **Note:** This component is designed to be controlled by the `qIndex` URL parameter rather than receiving explicit `value` or `onChange` props for the entire set of keys. It modifies the `qIndex` parameter by adding or removing individual keys based on checkbox interactions.
5
+ > **Note:** This component is **controlled by the `qIndex` URL parameter** rather than external props like `value` or `onChange`. When users interact with checkboxes, the component modifies the `qIndex` by adding or removing search keys accordingly. It **does not** replace the entire `qIndex` value.
6
+
7
+ ---
6
8
 
7
9
  ## Basic Usage
8
10
 
9
- Below is an example demonstrating how to use **SearchKeyControl**. You provide an array of `options`, each defining a potential search key with a label. The component automatically reads the current `qIndex` from the URL, determines the checked state of each checkbox, and updates the `qIndex` when checkboxes are toggled.
11
+ Provide an `options` array with each search key to be rendered. Each option includes a `key`, a display `label`, and optionally, an `indexes` array to represent multiple underlying sub-indexes.
10
12
 
11
13
  ```jsx
12
14
  import { SearchKeyControl } from '@k-int/stripes-kint-components';
@@ -16,55 +18,78 @@ const MySearchForm = () => {
16
18
  { key: 'keyword', label: 'Keyword' },
17
19
  { key: 'title', label: 'Title' },
18
20
  { key: 'author', label: 'Author' },
19
- { key: 'subject', label: 'Subject' }
21
+ {
22
+ key: 'subjectPlus',
23
+ label: 'Subject (incl. subdivisions)',
24
+ indexes: ['subject', 'subdivision']
25
+ }
20
26
  ];
21
27
 
22
28
  return (
23
29
  <div>
24
- {/* ... other search form elements ... */}
25
30
  <label>Search Indexes:</label>
26
31
  <SearchKeyControl options={searchKeyOptions} />
27
- {/* ... submit button etc ... */}
28
32
  </div>
29
33
  );
30
34
  };
31
35
 
32
36
  export default MySearchForm;
33
- ````
37
+ ```
38
+
39
+ In this example:
34
40
 
35
- In this example, **SearchKeyControl**:
41
+ * **Checkboxes** are rendered for each `key`, including the composite `subjectPlus`.
42
+ * The `qIndex` URL parameter (e.g. `?qIndex=title,author`) determines the checked state.
43
+ * Clicking a checkbox adds or removes the relevant index or indexes:
36
44
 
37
- - Renders checkboxes for "Keyword", "Title", "Author", and "Subject".
38
- - Reads the `qIndex` URL parameter (e.g., `?qIndex=keyword,title`).
39
- - Sets the "Keyword" and "Title" checkboxes as checked based on the example `qIndex`.
40
- - If the user clicks the "Author" checkbox, the component updates the URL parameter to `?qIndex=keyword,title,author`.
41
- - If the user then unclicks the "Keyword" checkbox, the URL parameter becomes `?qIndex=title,author`.
42
- - It also automatically resets pagination (via `usePrevNextPagination`) when the `qIndex` changes.
45
+ * For simple entries like `"title"`, toggling directly modifies `qIndex`.
46
+ * For composite entries like `"subjectPlus"`, checking adds both `"subject"` and `"subdivision"`; unchecking removes both.
47
+ * **Pagination is reset** automatically via `usePrevNextPagination()` when the `qIndex` changes.
48
+
49
+ ---
43
50
 
44
51
  ## Props
45
52
 
46
- | Name | Type | Description | Default | Required |
47
- |:----------|:------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------|
48
- | `options` | `arrayOf(object)` | An array of objects defining the available search keys. Each object must have: <br>• `key` (string): The value representing the search index. <br/>• `label` (string \| node): The text or element displayed next to the checkbox. | `[]` | ✕ |
53
+ | Name | Type | Description | Default | Required |
54
+ | :-------- | :---------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------ | :------- |
55
+ | `options` | `arrayOf(object)` | Array of search key definitions. Each object must include:<br>• `key` (string): A unique identifier.<br>• `label` (string \| node): The displayed label.<br>• `indexes` (array of strings, optional): One or more sub-indexes that this checkbox controls (defaults to `[key]` if omitted). | `[]` | ✕ |
49
56
 
57
+ ---
50
58
 
51
59
  ## How It Works
52
60
 
53
- 1. **Reading Search Index State:**
54
- The component uses the `useQIndex` hook to read the current `qIndex` URL parameter, which is expected to be a comma-separated string of active search keys (e.g., `"keyword,title"`). It memoizes this string split into an array (`qIndexArray`).
61
+ ### 1. Reading Search Index State
62
+
63
+ The component uses `useQIndex()` to retrieve and manage the `qIndex` URL parameter. The value is split into an array of active keys (`qIndexArray`), trimmed for whitespace and memoized.
64
+
65
+ ### 2. Creating Internal State
66
+
67
+ A `keyState` object maps each option's `key` to:
68
+
69
+ * `inUse`: A boolean indicating whether all of the entry’s `indexes` are present in `qIndexArray`.
70
+ * `label`: The label to display next to the checkbox.
71
+ * `subIndexes`: The underlying `indexes` array (or `[key]` if not specified).
72
+
73
+ This mapping is memoized via `useCallback()` and kept in sync with URL state using `useEffect()`.
74
+
75
+ ### 3. Rendering Checkboxes
76
+
77
+ Each `keyState` entry is rendered as a checkbox using `@folio/stripes/components`' `Checkbox`. Its `checked` state is tied to `inUse`, and its `label` comes from the `label` field.
78
+
79
+ ### 4. Handling Changes
80
+
81
+ On user interaction:
82
+
83
+ * If a checkbox is checked:
84
+
85
+ * The component **adds** all `subIndexes` for the key to the `qIndexArray`.
86
+ * If a checkbox is unchecked:
55
87
 
56
- 2. **Determining Checkbox State:**
57
- It uses `useState`, `useEffect`, `useMemo`, and `useCallback` along with `lodash/isEqual` to create and maintain an internal `keyState` object. This object maps each `key` from the `options` prop to its display `label` and an `inUse` boolean flag. The `inUse` flag is determined by checking if the option's `key` exists within the current `qIndexArray` derived from the `qIndex` URL parameter. This ensures the checkboxes visually reflect the state in the URL.
88
+ * The component **removes** all `subIndexes` for the key from `qIndexArray`.
58
89
 
59
- 3. **Rendering Checkboxes:**
60
- The component iterates over the `keyState` object and renders a `@folio/stripes/components` `Checkbox` for each entry. The `checked` prop of the checkbox is bound to the `inUse` flag from `keyState`, and the `label` is set accordingly.
90
+ The updated array is joined back into a comma-separated string and passed to `setQIndex()` to update the URL.
61
91
 
62
- 4. **Handling Changes:**
63
- When a checkbox's state is changed by the user:
92
+ ### 5. Pagination Reset
64
93
 
65
- * The `onChange` handler determines if the key should be added or removed from the list of active keys.
66
- * It updates a temporary copy of the `qIndexArray` (either adding the key with `push` or removing it with `splice`).
67
- * It calls `setQIndex` (from `useQIndex`) with the modified array joined back into a comma-separated string, which updates the URL parameter.
94
+ If the current page is non-zero (via `usePrevNextPagination`), the component calls `resetPage()` after modifying `qIndex`. This ensures consistency in result navigation after a filter change.
68
95
 
69
- 5. **Pagination Reset:**
70
- The component also uses the `usePrevNextPagination` hook. If pagination is active (`currentPage` has a value), it calls `resetPage()` after updating the `qIndex`. This ensures that users are returned to the first page of results when the search indexes change, preventing potentially invalid page numbers for the new search scope.
@@ -23,7 +23,14 @@ const SearchKeyControl = ({
23
23
  // Memoise this process so keyState changes if and only if options/qIndex change
24
24
  const createKeyState = useCallback(() => (
25
25
  options.reduce((acc, curr) => {
26
- acc[curr.key] = { inUse: qIndexArray?.includes(curr.key), label: curr.label ?? curr.key };
26
+ // Is in use if URL contains ALL of the indexes
27
+ const subindexes = ((curr?.indexes?.length ?? 0) > 0) ? curr.indexes : [curr.key];
28
+
29
+ acc[curr.key] = {
30
+ inUse: subindexes.every(si => qIndexArray.includes(si)),
31
+ label: curr.label ?? curr.key,
32
+ subIndexes: curr.indexes
33
+ };
27
34
  return acc;
28
35
  }, {})
29
36
  ), [options, qIndexArray]);
@@ -41,6 +48,31 @@ const SearchKeyControl = ({
41
48
  // Check to see if page param exists, and if it does then changing qIndex should reset it
42
49
  const { currentPage, resetPage } = usePrevNextPagination({ defaultToPageOne: false });
43
50
 
51
+ const handleSearchKeyChange = useCallback(({ e: { target: { checked: targetIsChecked } = {} } = {}, key, value }) => {
52
+ // Set up which indexes need to change
53
+ const indicesToChange = ((value?.subIndexes?.length ?? 0) > 0) ? value.subIndexes : [key];
54
+
55
+ // If false, we must remove from the qIndex
56
+ if (!targetIsChecked) {
57
+ indicesToChange.forEach(ind => {
58
+ const indexOfKey = qIndexArray.indexOf(ind);
59
+ if (indexOfKey > -1) { // only splice array when item is found
60
+ qIndexArray.splice(indexOfKey, 1); // 2nd parameter means remove one item only
61
+ }
62
+ });
63
+ } else {
64
+ // If true, we need to add to qIndex
65
+ indicesToChange.forEach(ind => {
66
+ qIndexArray.push(ind);
67
+ });
68
+ }
69
+
70
+ setQIndex(qIndexArray?.join(','));
71
+ if (currentPage) {
72
+ resetPage();
73
+ }
74
+ }, [currentPage, qIndexArray, resetPage, setQIndex]);
75
+
44
76
  return (
45
77
  <>
46
78
  <div className={css.searchKeyControlContainer}>
@@ -59,23 +91,7 @@ const SearchKeyControl = ({
59
91
  checked={value?.inUse}
60
92
  className={css.searchKeyControlElement}
61
93
  label={value?.label}
62
- onChange={e => {
63
- // If false, we must remove from the qIndex
64
- if (!e.target.checked) {
65
- const indexOfKey = qIndexArray.indexOf(key);
66
- if (indexOfKey > -1) { // only splice array when item is found
67
- qIndexArray.splice(indexOfKey, 1); // 2nd parameter means remove one item only
68
- }
69
- } else {
70
- // If true, we need to add to qIndex
71
- qIndexArray.push(key);
72
- }
73
-
74
- setQIndex(qIndexArray?.join(','));
75
- if (currentPage) {
76
- resetPage();
77
- }
78
- }}
94
+ onChange={e => handleSearchKeyChange({ e, key, value })}
79
95
  />
80
96
  );
81
97
  })