@k-int/stripes-kint-components 5.17.0 → 5.18.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +1 -1
  3. package/es/index.js +24 -12
  4. package/es/lib/EditableSettingsList/EditableSettingsList.js +4 -3
  5. package/es/lib/EditableSettingsList/EditableSettingsListFieldArray/EditableSettingsListFieldArray.js +110 -0
  6. package/es/lib/EditableSettingsList/{EditableSettingsListFieldArray.test.js → EditableSettingsListFieldArray/EditableSettingsListFieldArray.test.js} +3 -4
  7. package/es/lib/EditableSettingsList/EditableSettingsListFieldArray/index.js +13 -0
  8. package/es/lib/EditableSettingsList/SettingField/{EditSettingValue.js → EditSettingValue/EditSettingValue.js} +2 -2
  9. package/es/lib/EditableSettingsList/SettingField/{EditSettingValue.test.js → EditSettingValue/EditSettingValue.test.js} +2 -3
  10. package/es/lib/EditableSettingsList/SettingField/EditSettingValue/index.js +13 -0
  11. package/es/lib/EditableSettingsList/SettingField/{RenderSettingValue.js → RenderSettingValue/RenderSettingValue.js} +1 -1
  12. package/es/lib/EditableSettingsList/SettingField/{RenderSettingValue.test.js → RenderSettingValue/RenderSettingValue.test.js} +2 -2
  13. package/es/lib/EditableSettingsList/SettingField/RenderSettingValue/index.js +13 -0
  14. package/es/lib/EditableSettingsList/SettingField/SettingField.js +5 -5
  15. package/es/lib/EditableSettingsList/SettingField/SettingField.test.js +54 -44
  16. package/es/lib/EditableSettingsList/index.js +12 -0
  17. package/es/lib/SettingPage/SettingPage.js +3 -1
  18. package/es/lib/SettingPage/{SettingPagePane.js → SettingPagePane/SettingPagePane.js} +2 -2
  19. package/es/lib/SettingPage/SettingPagePane/index.js +13 -0
  20. package/es/lib/settingsHooks/useAppSettings/index.js +13 -0
  21. package/es/lib/settingsHooks/{useAppSettings.js → useAppSettings/useAppSettings.js} +1 -1
  22. package/es/lib/settingsHooks/useSettingSection/index.js +13 -0
  23. package/es/lib/settingsHooks/{useSettingSection.js → useSettingSection/useSettingSection.js} +1 -1
  24. package/es/lib/settingsHooks/useSettings/index.js +13 -0
  25. package/es/lib/settingsHooks/{useSettings.js → useSettings/useSettings.js} +10 -9
  26. package/package.json +1 -1
  27. package/src/index.js +4 -3
  28. package/src/lib/ActionList/README.md +18 -18
  29. package/src/lib/EditableSettingsList/EditableSettingsList.js +3 -2
  30. package/src/lib/EditableSettingsList/EditableSettingsListFieldArray/EditableSettingsListFieldArray.js +118 -0
  31. package/src/lib/EditableSettingsList/{EditableSettingsListFieldArray.test.js → EditableSettingsListFieldArray/EditableSettingsListFieldArray.test.js} +3 -5
  32. package/src/lib/EditableSettingsList/EditableSettingsListFieldArray/README.md +68 -0
  33. package/src/lib/EditableSettingsList/EditableSettingsListFieldArray/index.js +1 -0
  34. package/src/lib/EditableSettingsList/README.md +74 -0
  35. package/src/lib/EditableSettingsList/SettingField/{EditSettingValue.js → EditSettingValue/EditSettingValue.js} +2 -2
  36. package/src/lib/EditableSettingsList/SettingField/{EditSettingValue.test.js → EditSettingValue/EditSettingValue.test.js} +2 -5
  37. package/src/lib/EditableSettingsList/SettingField/EditSettingValue/README.md +63 -0
  38. package/src/lib/EditableSettingsList/SettingField/EditSettingValue/index.js +1 -0
  39. package/src/lib/EditableSettingsList/SettingField/README.md +61 -0
  40. package/src/lib/EditableSettingsList/SettingField/{RenderSettingValue.js → RenderSettingValue/RenderSettingValue.js} +1 -1
  41. package/src/lib/EditableSettingsList/SettingField/{RenderSettingValue.test.js → RenderSettingValue/RenderSettingValue.test.js} +2 -2
  42. package/src/lib/EditableSettingsList/SettingField/RenderSettingValue/index.js +1 -0
  43. package/src/lib/EditableSettingsList/SettingField/SettingField.js +5 -3
  44. package/src/lib/EditableSettingsList/SettingField/SettingField.test.js +65 -44
  45. package/src/lib/EditableSettingsList/index.js +1 -1
  46. package/src/lib/SettingPage/README.md +66 -0
  47. package/src/lib/SettingPage/SettingPage.js +3 -1
  48. package/src/lib/SettingPage/SettingPagePane/README.md +31 -0
  49. package/src/lib/SettingPage/{SettingPagePane.js → SettingPagePane/SettingPagePane.js} +2 -2
  50. package/src/lib/SettingPage/SettingPagePane/index.js +1 -0
  51. package/src/lib/settingsHooks/useAppSettings/index.js +1 -0
  52. package/src/lib/settingsHooks/{useAppSettings.js → useAppSettings/useAppSettings.js} +1 -1
  53. package/src/lib/settingsHooks/useSettingSection/README.md +54 -0
  54. package/src/lib/settingsHooks/useSettingSection/index.js +1 -0
  55. package/src/lib/settingsHooks/{useSettingSection.js → useSettingSection/useSettingSection.js} +1 -1
  56. package/src/lib/settingsHooks/useSettings/README.md +84 -0
  57. package/src/lib/settingsHooks/useSettings/index.js +1 -0
  58. package/src/lib/settingsHooks/{useSettings.js → useSettings/useSettings.js} +10 -7
  59. package/es/lib/EditableSettingsList/EditableSettingsListFieldArray.js +0 -57
  60. package/src/lib/EditableSettingsList/EditableSettingsListFieldArray.js +0 -58
@@ -0,0 +1,61 @@
1
+ # SettingField
2
+
3
+ A component designed to render a single setting within a settings list. It handles fetching contextual data (like reference data via `useRefdata` or templates via `useTemplates` based on the setting's configuration), displays the setting's name, help text (`InfoPopover`), and value, and manages the switch between display mode (using `RenderSettingValue`) and edit mode (using `EditSettingValue`).
4
+
5
+ It is typically used as the `component` prop for a `react-final-form` `<Field>` within the structure provided by `EditableSettingsListFieldArray` and `EditableSettingsList`. It utilizes `@folio/stripes/components` (`Card`, `Button`, `InfoPopover`) for its UI structure and relies on `SettingsContext` to determine API endpoints for fetching related data.
6
+
7
+ ## Basic Usage
8
+
9
+ `SettingField` is usually rendered internally by the `DefaultField` implementation within `EditableSettingsListField`. It receives props from `react-final-form`'s `<Field>` (like `input`, `meta`) and custom props from `EditableSettingsListField` (like `editing`, `setEditing`, `settingData`, `onSave`).
10
+
11
+ ```jsx
12
+ // SettingField is typically used internally by EditableSettingsListField.
13
+ // The default implementation within EditableSettingsListField looks
14
+ // somewhat like this:
15
+
16
+ import { Field } from 'react-final-form';
17
+
18
+ // Inside EditableSettingsListField's render logic (simplified):
19
+ <Field
20
+ // Props passed down:
21
+ allowEdit={props.allowEdit}
22
+ component={SettingField} // <-- Usage of SettingField
23
+ editing={props.editing}
24
+ intlKey={props.intlKey}
25
+ intlNS={props.intlNS}
26
+ labelOverrides={props.labelOverrides}
27
+ onSave={props.onSave} // Specific save handler for this row
28
+ setEditing={props.setEditing} // Function to toggle editing state for this row
29
+ settingData={props.settingData} // Object containing currentSetting etc.
30
+
31
+ // Props provided by Field:
32
+ name={settingIdentifier} // The name/path for this field within the form
33
+ // 'input' and 'meta' props are also automatically passed by Field
34
+ />
35
+
36
+ // Direct usage is less common but possible if customizing the `render` prop
37
+ // of EditableSettingsListFieldArray or EditableSettingsList.
38
+
39
+ // Note: The actual rendering of the setting's value in view or edit mode
40
+ // is delegated to the RenderSettingValue and EditSettingValue components,
41
+ // respectively, based on the 'editing' prop and the setting's configuration
42
+ // (currentSetting.settingType).
43
+ ```
44
+
45
+ **Note:** This component requires `SettingsContext` to be available higher up in the component tree to provide `refdataEndpoint` and `templateEndpoint`.
46
+
47
+ ## Props
48
+
49
+ | Name | Type | Description | default | required |
50
+ |------------------------------|-----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------|------------|
51
+ | `allowEdit` | `boolean` | Controls whether the "Edit"/"Save" button is displayed and if editing is permitted. Passed down from parent components. | `true` | ✕ |
52
+ | `editing` | `boolean` | Determines if the field should render in display mode (`RenderSettingValue`) or edit mode (`EditSettingValue`). This state is typically managed by the parent `EditableSettingsListField`. | `undefined` | ✓ |
53
+ | `input` | `object` | Provided by `react-final-form`'s `<Field>`. Contains field state like `value`, `name`, and callbacks like `onChange`, `onBlur`. Crucial for connecting to the form state, passed down to `EditSettingValue`. | `undefined` | ✓ (by RFF) |
54
+ | `intlKey` | `string` | Base key for internationalization, used by the internal `useKintIntl` hook for fetching labels and messages. | `undefined` | ✕ |
55
+ | `intlNS` | `string` | Namespace for internationalization, used by the internal `useKintIntl` hook. | `undefined` | ✕ |
56
+ | `labelOverrides` | `object` | An object potentially used to override default labels, passed down to `RenderSettingValue`/`EditSettingValue`. | `{}` | ✕ |
57
+ | `meta` | `object` | Provided by `react-final-form`'s `<Field>`. Contains metadata about the field state, such as `touched`, `error`, `dirty`, `valid`, etc. | `undefined` | ✓ (by RFF) |
58
+ | `onSave` | `func` | Callback function triggered when the "Save" button is clicked (while `editing` is true). It should handle the persistence logic for this *single setting* and is expected to return a `Promise`. The success of the promise determines whether the component switches back to display mode (`setEditing(false)`). | `undefined` | ✓ |
59
+ | `setEditing` | `func` | A function, provided by the parent component (e.g., `EditableSettingsListField`), used to signal a change in the editing state (e.g., `setEditing(true)` when "Edit" is clicked, or `setEditing(false)` after a successful save). | `undefined` | ✓ |
60
+ | `settingData` | `object` | An object containing data pertinent to the specific setting being rendered. | `undefined` | ✓ |
61
+ | `settingData.currentSetting` | `object` | The core configuration object for the setting currently being rendered. Contains properties like `key`, `section`, `settingType`, `vocab`, etc., which drive the component's behavior (fetching data, rendering labels, choosing input types via `EditSettingValue`). | `undefined` | ✓ |
@@ -1,6 +1,6 @@
1
1
  import PropTypes from 'prop-types';
2
2
 
3
- import { useKintIntl } from '../../hooks';
3
+ import { useKintIntl } from '../../../hooks';
4
4
 
5
5
  const RenderSettingValue = (props) => {
6
6
  const {
@@ -2,9 +2,9 @@ import React from 'react';
2
2
 
3
3
  import RenderSettingValue from './RenderSettingValue';
4
4
 
5
- import { renderWithKintHarness } from '../../../../test/jest/helpers';
5
+ import { renderWithKintHarness } from '../../../../../test/jest/helpers';
6
6
 
7
- jest.mock('../../hooks');
7
+ jest.mock('../../../hooks');
8
8
 
9
9
  const stringSetting = {
10
10
  id: '12345',
@@ -0,0 +1 @@
1
+ export { default } from './RenderSettingValue';
@@ -1,4 +1,4 @@
1
- import React, { useContext, useState } from 'react';
1
+ import { useContext } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
 
4
4
  import {
@@ -18,16 +18,17 @@ import renderHelpTextCSS from '../../../../styles/renderHelpText.css';
18
18
  const SettingField = (settingFieldProps) => {
19
19
  const {
20
20
  allowEdit = true,
21
+ editing,
21
22
  intlKey: passedIntlKey,
22
23
  intlNS: passedIntlNS,
23
24
  labelOverrides = {},
24
25
  onSave,
26
+ setEditing,
25
27
  settingData: {
26
28
  currentSetting
27
29
  } = {}
28
30
  } = settingFieldProps;
29
31
 
30
- const [editing, setEditing] = useState(false);
31
32
  const kintIntl = useKintIntl(passedIntlKey, passedIntlNS);
32
33
  const { refdataEndpoint, templateEndpoint } = useContext(SettingsContext);
33
34
 
@@ -39,7 +40,8 @@ const SettingField = (settingFieldProps) => {
39
40
  endpoint: refdataEndpoint,
40
41
  desc: currentSetting.vocab,
41
42
  queryParams: {
42
- enabled: !!currentSetting?.vocab && currentSetting.settingType === 'Refdata'
43
+ enabled: !!currentSetting?.vocab && currentSetting.settingType === 'Refdata',
44
+ staleTime: 1000 * 60 * 2 // 2 minutes staletime for refdata in settings?
43
45
  }
44
46
  });
45
47
 
@@ -25,50 +25,71 @@ const setting = {
25
25
  value: 'diku-shared'
26
26
  };
27
27
 
28
- describe('SettingField', () => {
29
- let renderComponent;
30
- beforeEach(async () => {
31
- renderComponent = renderWithKintHarness(
32
- <TestForm
33
- initialValues={{}}
34
- onSubmit={onSubmit}
35
- >
36
- <Field
37
- component={SettingField}
38
- name="test"
39
- onSave={onSave}
40
- settingData={{
41
- currentSetting: setting
42
- }}
43
- />
44
- </TestForm>
45
- );
46
- });
47
-
48
- it('renders RenderSettingValue', async () => {
49
- const { getByText } = renderComponent;
50
- await waitFor(async () => expect(await getByText('RenderSettingValue')).toBeInTheDocument());
51
- });
52
-
53
- test('renders the edit button', () => {
54
- Button('Edit').exists();
55
- });
28
+ const setEditing = jest.fn();
56
29
 
57
- it('clicking edit/save works as expected', async () => {
58
- const { findByText } = renderComponent;
59
- // before clicking on edit button, we should be rendering setting value
60
- await waitFor(async () => expect(await findByText('RenderSettingValue')).toBeInTheDocument());
61
-
62
- // Clicking edit button
63
- await waitFor(async () => { await Button('Edit').click(); });
64
-
65
- // Should be rendering the "edit" for a setting value
66
- await waitFor(async () => expect(await findByText('EditSettingValue')).toBeInTheDocument());
67
-
68
- // Clicking save button
69
- await waitFor(async () => { await Button('Save').click(); });
70
-
71
- // Should be rendering the setting value again
72
- await waitFor(async () => expect(await findByText('RenderSettingValue')).toBeInTheDocument());
30
+ let renderComponent;
31
+ describe('SettingField', () => {
32
+ describe.each([
33
+ {
34
+ editing: false,
35
+ expectedRender: 'RenderSettingValue',
36
+ expectedButton: 'Edit',
37
+ expectedCallback: setEditing,
38
+ },
39
+ {
40
+ editing: true,
41
+ expectedRender: 'EditSettingValue',
42
+ expectedButton: 'Save',
43
+ expectedCallback: onSave,
44
+
45
+ }
46
+ ])('render SettingField with editing: editing', ({
47
+ editing,
48
+ expectedButton,
49
+ expectedCallback,
50
+ expectedRender
51
+ }) => {
52
+ beforeEach(async () => {
53
+ setEditing.mockClear();
54
+ onSave.mockClear();
55
+ renderComponent = renderWithKintHarness(
56
+ <TestForm
57
+ initialValues={{}}
58
+ onSubmit={onSubmit}
59
+ >
60
+ <Field
61
+ component={SettingField}
62
+ editing={editing}
63
+ name="test"
64
+ onSave={onSave}
65
+ setEditing={setEditing}
66
+ settingData={{
67
+ currentSetting: setting
68
+ }}
69
+ />
70
+ </TestForm>
71
+ );
72
+ });
73
+
74
+ it(`renders ${expectedRender}`, () => {
75
+ const { getByText } = renderComponent;
76
+ expect(getByText(expectedRender)).toBeInTheDocument();
77
+ });
78
+
79
+ test(`renders the ${expectedButton} button`, () => {
80
+ Button('Edit').exists();
81
+ });
82
+
83
+ describe(`clicking the ${expectedButton} button`, () => {
84
+ beforeEach(async () => {
85
+ await waitFor(async () => {
86
+ await Button(expectedButton).click();
87
+ });
88
+ });
89
+
90
+ it('fired expected callback', () => {
91
+ expect(expectedCallback).toHaveBeenCalled();
92
+ });
93
+ });
73
94
  });
74
95
  });
@@ -1,3 +1,3 @@
1
1
  export { default as EditableSettingsList } from './EditableSettingsList';
2
2
  export { default as EditableSettingsListFieldArray } from './EditableSettingsListFieldArray';
3
- export { SettingField } from './SettingField';
3
+ export { SettingField, RenderSettingValue, EditSettingValue } from './SettingField';
@@ -0,0 +1,66 @@
1
+ # SettingPage
2
+
3
+ A container component that simplifies the process of displaying and editing a section of application settings. It leverages the `EditableSettingsList` component to handle the rendering and editing of individual settings within that section. `SettingPage` fetches the relevant settings data for a given `sectionName` using a custom hook and manages the submission of changes.
4
+
5
+ This component acts as a higher-level abstraction, taking care of data fetching and preparing the necessary props for `EditableSettingsList`. It utilizes the `SettingsContext` to access the settings API endpoint and the `useSettingSection` hook to retrieve and handle the settings for a specific section.
6
+
7
+ ## Basic Usage
8
+
9
+ Import the component and provide the `sectionName` to identify the settings section you want to display and manage.
10
+
11
+ ```jsx
12
+ const GeneralSettingsPage = () => {
13
+ return (
14
+ <div>
15
+ <h2>General Application Settings</h2>
16
+ <SettingPage sectionName="general" />
17
+ </div>
18
+ );
19
+ };
20
+ ```
21
+
22
+ In this example, `SettingPage` will fetch the settings associated with the "general" section and render them in an editable list using `EditableSettingsList`.
23
+
24
+ You can also customize the editing capabilities, internationalization keys, and labels.
25
+
26
+ ```jsx
27
+ const AdvancedSettingsPage = () => {
28
+ return (
29
+ <div>
30
+ <h2>Advanced Configuration</h2>
31
+ <SettingPage
32
+ sectionName="advanced"
33
+ allowEdit={false} // Disable editing for this section
34
+ intlKey="advancedSettings"
35
+ intlNS="myApp"
36
+ labelOverrides={{
37
+ 'timeoutDuration': 'Session Timeout (in seconds)'
38
+ }}
39
+ render={({ fields: { name }, index }) => (
40
+ <div key={index}>
41
+ {/* Custom rendering for each setting field */}
42
+ <label htmlFor={`${name}[${index}].value`}>{name}</label>
43
+ <input type="text" id={`${name}[${index}].value`} {...fields.field(`${name}[${index}]`, 'value')} />
44
+ </div>
45
+ )}
46
+ />
47
+ </div>
48
+ );
49
+ };
50
+ ```
51
+
52
+ **Note:**
53
+ * This component relies on the `SettingsContext` being available in the component tree to access the `settingEndpoint`.
54
+ * It also depends on the `useSettingSection` custom hook to handle data fetching and submission logic for the specified `sectionName`.
55
+ * The `render` prop allows for complete customization of how the individual settings are rendered within the `EditableSettingsList`.
56
+
57
+ ## Props
58
+
59
+ | Name | Type | Description | default | required |
60
+ |------------------|-----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------|----------|
61
+ | `allowEdit` | `boolean` | A flag to globally enable or disable editing for all settings within this section. This prop is passed directly to the underlying `EditableSettingsList` component. | `true` | ✕ |
62
+ | `intlKey` | `string` | A base internationalization key to be used for generating labels within the `EditableSettingsList` and its child components. If provided, it's passed down to `EditableSettingsList`. | `undefined` | ✕ |
63
+ | `intlNS` | `string` | An internationalization namespace used for resolving labels within the `EditableSettingsList` and its child components. If provided, it's passed down to `EditableSettingsList`. | `undefined` | ✕ |
64
+ | `labelOverrides` | `object` | An object containing key-value pairs where the keys are setting identifiers (e.g., `setting.key`) and the values are custom labels to override the default labels generated by `EditableSettingsList`. This prop is passed directly to `EditableSettingsList`. | `{}` | ✕ |
65
+ | `sectionName` | `string` | The unique name of the settings section to be displayed and managed. This prop is crucial as it's used by the `useSettingSection` hook to fetch the relevant settings data from the API. | `undefined` | ✓ |
66
+ | `render` | `func` | A render prop that receives the `fields` object from `react-final-form-arrays` (within `EditableSettingsList`) and allows for complete custom rendering of each setting row. This prop is passed directly to `EditableSettingsList`. See the `EditableSettingsList` documentation for more details on the props passed to this render function and how to use it. | `undefined` | ✕ |
@@ -11,7 +11,8 @@ const SettingPage = ({
11
11
  intlKey: passedIntlKey,
12
12
  intlNS: passedIntlNS,
13
13
  labelOverrides = {},
14
- sectionName
14
+ sectionName,
15
+ render
15
16
  }) => {
16
17
  const { settingEndpoint } = useContext(SettingsContext);
17
18
 
@@ -29,6 +30,7 @@ const SettingPage = ({
29
30
  labelOverrides={labelOverrides}
30
31
  onSave={handleSubmit}
31
32
  onSubmit={handleSubmit}
33
+ render={render}
32
34
  settingSection={sectionName}
33
35
  />
34
36
  );
@@ -0,0 +1,31 @@
1
+ # SettingPagePane
2
+
3
+ A presentational component that wraps its children within a styled pane dedicated to a settings section. **SettingPagePane** is most commonly used to wrap a **SettingsPage** component, which handles the logic for fetching and managing settings data. However, it is not strictly required to wrap a **SettingsPage**—any valid React content can be used as children.
4
+
5
+ This component leverages the `Pane` component from `@folio/stripes/components` to provide a consistent layout and styling for different settings pages. Additionally, it integrates internationalization by using the `useKintIntl` hook along with the `toCamelCase` utility to generate a pane title based on the provided `sectionName`.
6
+
7
+ ## Basic Usage
8
+
9
+ Wrap your settings-related content inside **SettingPagePane**. It is common to use this component as a wrapper for **SettingsPage**; however, you can also use it to contain any other content.
10
+
11
+ ```jsx
12
+ import SettingsPage from './SettingsPage';
13
+
14
+ const GeneralSettings = () => (
15
+ <SettingPagePane sectionName="general">
16
+ {/* In typical usage, SettingsPage is rendered inside SettingPagePane */}
17
+ <SettingsPage sectionName="general" />
18
+ </SettingPagePane>
19
+ );
20
+ ```
21
+
22
+ In this example, **SettingPagePane** creates a pane with an ID of `settings-general` and a title generated by converting `"general"` to camel case and resolving the corresponding internationalized message. If no message is found, it defaults to using `"general"`.
23
+
24
+ ## Props
25
+
26
+ | Name | Type | Description | Default | Required |
27
+ |--------------|--------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|----------|
28
+ | `sectionName`| `string` | The unique name of the settings section. This prop is used to generate the pane’s identifier and to lookup the internationalized title for the pane header. | `undefined` | ✓ |
29
+ | `intlKey` | `string` | A base internationalization key used by the `useKintIntl` hook to find the correct localized message for the pane title. | `undefined` | ✕ |
30
+ | `intlNS` | `string` | An internationalization namespace for resolving labels within the pane title. | `undefined` | ✕ |
31
+ | `children` | `node` or `func` | The content to be rendered within the pane. Typically, this is a **SettingsPage** component, but it can be any valid React node. | `undefined` | ✕ |
@@ -1,8 +1,8 @@
1
1
  import PropTypes from 'prop-types';
2
2
 
3
3
  import { Pane } from '@folio/stripes/components';
4
- import { toCamelCase } from '../utils';
5
- import { useKintIntl } from '../hooks';
4
+ import { toCamelCase } from '../../utils';
5
+ import { useKintIntl } from '../../hooks';
6
6
 
7
7
  const SettingPagePane = ({
8
8
  children,
@@ -0,0 +1 @@
1
+ export { default } from './SettingPagePane';
@@ -0,0 +1 @@
1
+ export { default } from './useAppSettings';
@@ -1,6 +1,6 @@
1
1
  import { useQuery } from 'react-query';
2
2
  import { useOkapiKy } from '@folio/stripes/core';
3
- import { generateKiwtQueryParams } from '../utils';
3
+ import { generateKiwtQueryParams } from '../../utils';
4
4
 
5
5
  const useAppSettings = ({
6
6
  endpoint,
@@ -0,0 +1,54 @@
1
+ # useSettingSection
2
+
3
+ A custom hook that retrieves settings data for a given section and provides a handler to update individual settings. It leverages `react-query` to perform asynchronous data fetching and mutations against a specified settings API endpoint.
4
+
5
+ ## Overview
6
+
7
+ `useSettingSection` is designed to:
8
+ - **Fetch Settings:**
9
+ Use `react-query` to request settings that belong to a specific section, based on a filter that matches the provided `sectionName`. The results are sorted by the setting key.
10
+ - **Update Settings:**
11
+ Provide a mutation handler (`handleSubmit`) to update a setting. The mutation performs an HTTP PUT request to the API endpoint with the setting data.
12
+
13
+ ## Basic Usage
14
+
15
+ ```jsx
16
+ const SettingsEditor = ({ sectionName, settingEndpoint }) => {
17
+ const { settings, handleSubmit } = useSettingSection({
18
+ sectionName,
19
+ settingEndpoint
20
+ });
21
+
22
+ // Render the settings and handle updates with handleSubmit
23
+ return (
24
+ <div>
25
+ {settings.map(setting => (
26
+ <div key={setting.id}>
27
+ <span>{setting.key}</span>
28
+ {/* Some UI to edit the setting */}
29
+ <button onClick={() => handleSubmit({ ...setting, value: 'new value' })}>
30
+ Save
31
+ </button>
32
+ </div>
33
+ ))}
34
+ </div>
35
+ );
36
+ };
37
+ ```
38
+
39
+ ## Parameters
40
+
41
+ | Name | Type | Description | Required |
42
+ |------------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|
43
+ | `sectionName` | `string` | The name of the settings section to fetch. This is used to filter the settings by matching the section property in the API query. | ✓ |
44
+ | `settingEndpoint`| `string` | The base URL endpoint for the settings API. The hook appends query parameters and, in the case of an update, the setting's ID. | ✓ |
45
+
46
+ ## Returns
47
+
48
+ An object containing:
49
+
50
+ - **`settings`** (`array`):
51
+ The array of settings retrieved from the API. If the query has not completed or returns no data, it defaults to an empty array.
52
+
53
+ - **`handleSubmit`** (`function`):
54
+ A mutation function that accepts a settings data object and performs an HTTP PUT request to update the setting.
@@ -0,0 +1 @@
1
+ export { default } from './useSettingSection';
@@ -1,6 +1,6 @@
1
1
  import { useMutation, useQuery } from 'react-query';
2
2
  import { useOkapiKy } from '@folio/stripes/core';
3
- import { generateKiwtQueryParams } from '../utils';
3
+ import { generateKiwtQueryParams } from '../../utils';
4
4
 
5
5
  const useSettingSection = ({
6
6
  sectionName,
@@ -0,0 +1,84 @@
1
+ # useSettings
2
+
3
+ A comprehensive hook that manages the retrieval and configuration of settings data for one or multiple sections. It orchestrates multiple queries with `react-query`, constructs dynamic pages for each settings section, and returns a pre-configured `SettingsComponent` for rendering the settings UI.
4
+
5
+ ## Overview
6
+
7
+ `useSettings` serves several purposes:
8
+
9
+ - **Multi-Section Querying:**
10
+ It supports both single and multiple settings sections. Depending on the provided props, it dynamically generates query parameters and uses `useQueries` to fetch data for each section.
11
+
12
+ - **Dynamic Page Generation:**
13
+ When queries return data, it extracts unique section names to create dynamic pages. Each page includes a route, localized label, and a component that wraps a `SettingPage` in a `SettingPagePane`.
14
+
15
+ - **Context and Configuration:**
16
+ The hook creates a `SettingsContextProvider` to pass down configuration details (like API endpoints and internationalization keys) to its children. This ensures that each section or page has the necessary context for rendering.
17
+
18
+ - **Final Settings Component:**
19
+ It returns a `SettingsComponent` that wraps the list of pages or sections using the `Settings` component from `@folio/stripes/smart-components`. This component can then be directly rendered in the application.
20
+
21
+ ## Basic Usage
22
+
23
+ ```jsx
24
+ const SettingsContainer = () => {
25
+ const {
26
+ isLoading,
27
+ SettingsComponent,
28
+ pageList,
29
+ sections,
30
+ SettingsContextProvider,
31
+ } = useSettings({
32
+ sectionName: 'general',
33
+ settingEndpoint: '/settings',
34
+ refdataEndpoint: '/refdata',
35
+ // Other optional props for customization...
36
+ });
37
+
38
+ if (isLoading) return <div>Loading settings...</div>;
39
+
40
+ return <SettingsComponent />;
41
+ };
42
+ ```
43
+
44
+ ## Parameters
45
+
46
+ `useSettings` accepts an optional settings props object (`settingsProps`) that may include the following properties:
47
+
48
+ | Name | Type | Description | Required |
49
+ |------------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|
50
+ | `allowGlobalEdit` | `boolean`| Global flag to enable or disable editing of settings. Defaults to `true`. | ✕ |
51
+ | `dynamicPageExclusions`| `array` | Array of section names to be excluded from dynamic page generation. | ✕ |
52
+ | `intlKey` | `string` | Base internationalization key for resolving labels. | ✕ |
53
+ | `intlNS` | `string` | Internationalization namespace. | ✕ |
54
+ | `labelOverrides` | `object` | Custom label overrides for specific settings keys. | ✕ |
55
+ | `pages` | `array` | If provided, acts as a passthrough configuration for either sections or standalone pages, disabling query execution. | ✕ |
56
+ | `persistentPages` | `array` | Array of pages that persist regardless of dynamic query results. | ✕ |
57
+ | `preventQueries` | `boolean`| Flag to disable querying if settings data is provided externally. | ✕ |
58
+ | `refdataEndpoint` | `string` | The API endpoint to retrieve reference data for settings. | ✕ |
59
+ | `renderSingleSection` | `boolean`| When only one section is available, determines if it should be rendered as a single section with its own label. | ✕ |
60
+ | `sectionPermission` | `string` | Permission string that, if provided, restricts the rendering of a section unless the current user has the specified permission. | ✕ |
61
+ | `sectionRoute` | `string` | Base route path to be used when constructing dynamic page routes for settings sections. | ✕ |
62
+ | `sections` | `array` | Array of section configuration objects. Each object can override or extend the global settings props for that specific section. This means that the shape here matches more or less the shape for the props of `useSettings` itself. | ✕ |
63
+ | `settingEndpoint` | `string` | The base URL endpoint for fetching and updating settings. | ✕ |
64
+ | `templateEndpoint` | `string` | The endpoint for retrieving settings templates (if applicable). | ✕ |
65
+ | `render` | `func` | A custom render function for rendering individual setting fields. This function is passed down to the underlying settings components for specialized display logic. See `EditableSettingsList` for more information on how render is used. | ✕ |
66
+
67
+ ## Returns
68
+
69
+ The hook returns an object containing:
70
+
71
+ - **`isLoading`** (`boolean`):
72
+ Indicates if any of the settings queries are still loading.
73
+
74
+ - **`pageList`** (`array`):
75
+ An array of pages to be rendered by the `Settings` component. This is the final list of pages for a single section, if applicable.
76
+
77
+ - **`sections`** (`array`):
78
+ An array of fully configured sections. Each section includes its own `SettingsContextProvider`, dynamic pages, and additional configuration data.
79
+
80
+ - **`SettingsComponent`** (`React Component`):
81
+ A component pre-configured with the pages and sections that renders the settings UI. This component wraps the settings in the required `Settings` component from `@folio/stripes/smart-components`.
82
+
83
+ - **`SettingsContextProvider`** (`React Component`):
84
+ A context provider component for settings configuration. If only one section is available, it is provided directly; otherwise, it defaults to a pass-through provider.
@@ -0,0 +1 @@
1
+ export { default } from './useSettings';
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
 
4
4
  import { useQueries } from 'react-query';
@@ -6,11 +6,11 @@ import { useOkapiKy, useStripes } from '@folio/stripes/core';
6
6
 
7
7
  import { Settings } from '@folio/stripes/smart-components';
8
8
 
9
- import { SettingPage, SettingPagePane } from '../SettingPage';
10
- import { SettingsContext } from '../contexts';
9
+ import { SettingPage, SettingPagePane } from '../../SettingPage';
10
+ import { SettingsContext } from '../../contexts';
11
11
 
12
- import { generateKiwtQueryParams, sortByLabel, toCamelCase } from '../utils';
13
- import { useKintIntl, useIntlKey } from '../hooks';
12
+ import { generateKiwtQueryParams, sortByLabel, toCamelCase } from '../../utils';
13
+ import { useKintIntl, useIntlKey } from '../../hooks';
14
14
 
15
15
  // TODO work underway to make the settings hook more useful when trying to render multiple sections at a time
16
16
  const useSettings = (settingsProps = {}) => {
@@ -30,7 +30,8 @@ const useSettings = (settingsProps = {}) => {
30
30
  sections, // if present, should be an array of objects with the SAME shape as the overall props here.
31
31
  // If a prop isn't present it can fall back to the "global" prop.
32
32
  settingEndpoint,
33
- templateEndpoint
33
+ templateEndpoint,
34
+ render, // Allow setting-by-setting rendering of fields, to allow for special treatment for a given setting key etc
34
35
  } = settingsProps;
35
36
 
36
37
  const ky = useOkapiKy();
@@ -100,6 +101,7 @@ const useSettings = (settingsProps = {}) => {
100
101
  const getDynamicPages = useCallback((qra, sma) => {
101
102
  const pagesFromQueryReturn = [...new Set(qra.data?.map(s => s.section))];
102
103
  const dynamic = pagesFromQueryReturn.map(page => {
104
+ const finalRender = sma.render ?? render;
103
105
  const finalSectionRoute = sma.sectionRoute ?? sectionRoute;
104
106
  const pageRoute = (finalSectionRoute ? `${finalSectionRoute}/` : '') + page;
105
107
  return (
@@ -120,6 +122,7 @@ const useSettings = (settingsProps = {}) => {
120
122
  intlKey={passedIntlKey}
121
123
  intlNS={passedIntlNS}
122
124
  labelOverrides={labelOverrides}
125
+ render={finalRender}
123
126
  sectionName={page}
124
127
  {...props}
125
128
  />
@@ -133,7 +136,7 @@ const useSettings = (settingsProps = {}) => {
133
136
  pages: pagesFromQueryReturn,
134
137
  dynamic,
135
138
  };
136
- }, [allowGlobalEdit, kintIntl, labelOverrides, passedIntlKey, passedIntlNS, sectionRoute]);
139
+ }, [allowGlobalEdit, kintIntl, labelOverrides, passedIntlKey, passedIntlNS, render, sectionRoute]);
137
140
 
138
141
  useEffect(() => {
139
142
  // Handle loading