@payfit/unity-components 2.1.4 → 2.2.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/dist/esm/components/avatar/Avatar.context.d.ts +2 -1
- package/dist/esm/components/avatar/Avatar.context.js +13 -11
- package/dist/esm/components/avatar/Avatar.d.ts +126 -0
- package/dist/esm/components/avatar/Avatar.js +34 -20
- package/dist/esm/components/avatar/Avatar.variants.d.ts +39 -0
- package/dist/esm/components/avatar/Avatar.variants.js +22 -4
- package/dist/esm/components/avatar/parts/AvatarFallback.d.ts +52 -0
- package/dist/esm/components/avatar/parts/AvatarIcon.d.ts +31 -0
- package/dist/esm/components/avatar/parts/AvatarIcon.js +40 -0
- package/dist/esm/components/button/Button.js +8 -7
- package/dist/esm/components/inline-field-group/InlineFieldGroup.context.d.ts +23 -0
- package/dist/esm/components/inline-field-group/InlineFieldGroup.context.js +6 -0
- package/dist/esm/components/inline-field-group/InlineFieldGroup.d.ts +119 -0
- package/dist/esm/components/inline-field-group/InlineFieldGroup.js +185 -0
- package/dist/esm/components/inline-field-group/hooks/useInlineFieldGroupMode.d.ts +46 -0
- package/dist/esm/components/inline-field-group/hooks/useInlineFieldGroupMode.js +27 -0
- package/dist/esm/components/inline-field-group/parts/InlineFieldGroupEditView.d.ts +64 -0
- package/dist/esm/components/inline-field-group/parts/InlineFieldGroupEditView.js +56 -0
- package/dist/esm/components/inline-field-group/parts/InlineFieldGroupHeader.d.ts +95 -0
- package/dist/esm/components/inline-field-group/parts/InlineFieldGroupHeader.js +106 -0
- package/dist/esm/components/inline-field-group/parts/InlineFieldGroupReadView.d.ts +56 -0
- package/dist/esm/components/inline-field-group/parts/InlineFieldGroupReadView.js +28 -0
- package/dist/esm/components/side-panel/parts/SidePanelFooter.js +19 -10
- package/dist/esm/hooks/tanstack-form-context.d.ts +1 -1
- package/dist/esm/hooks/use-tanstack-form.d.ts +32 -8
- package/dist/esm/hooks/use-tanstack-form.js +71 -48
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +445 -443
- package/i18n/en-GB.json +6 -0
- package/i18n/es-ES.json +6 -0
- package/i18n/fr-FR.json +6 -0
- package/package.json +21 -21
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { default as React, PropsWithChildren } from 'react';
|
|
2
|
+
import { InlineFieldGroupMode } from './InlineFieldGroup.context.js';
|
|
3
|
+
/**
|
|
4
|
+
* Imperative handle for InlineFieldGroup component.
|
|
5
|
+
* Provides methods for programmatic control when needed for advanced use cases.
|
|
6
|
+
*/
|
|
7
|
+
export interface InlineFieldGroupHandle {
|
|
8
|
+
/**
|
|
9
|
+
* Focuses the first invalid field in the edit view.
|
|
10
|
+
* Useful for custom validation error handling.
|
|
11
|
+
*/
|
|
12
|
+
focusFirstInvalidField: () => void;
|
|
13
|
+
/**
|
|
14
|
+
* Exits edit mode programmatically.
|
|
15
|
+
*/
|
|
16
|
+
exitEditMode: () => void;
|
|
17
|
+
/**
|
|
18
|
+
* Enters edit mode programmatically.
|
|
19
|
+
*/
|
|
20
|
+
enterEditMode: () => void;
|
|
21
|
+
}
|
|
22
|
+
export interface InlineFieldGroupProps extends PropsWithChildren {
|
|
23
|
+
/**
|
|
24
|
+
* Controlled mode value. When provided, the component operates in controlled mode.
|
|
25
|
+
*/
|
|
26
|
+
mode?: InlineFieldGroupMode;
|
|
27
|
+
/**
|
|
28
|
+
* Default mode value for uncontrolled mode.
|
|
29
|
+
* @default 'read'
|
|
30
|
+
*/
|
|
31
|
+
defaultMode?: InlineFieldGroupMode;
|
|
32
|
+
/**
|
|
33
|
+
* Callback fired when mode changes.
|
|
34
|
+
*/
|
|
35
|
+
onModeChange?: (mode: InlineFieldGroupMode) => void;
|
|
36
|
+
/**
|
|
37
|
+
* Callback to intercept and potentially prevent mode changes.
|
|
38
|
+
* Return `false` to prevent the mode transition.
|
|
39
|
+
* Return `true` or `undefined` to allow the transition.
|
|
40
|
+
*/
|
|
41
|
+
shouldModeChange?: (fromMode: InlineFieldGroupMode, toMode: InlineFieldGroupMode) => boolean | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Whether the component is in a loading state (e.g., during async save).
|
|
44
|
+
*/
|
|
45
|
+
isLoading?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Optional className for custom styling
|
|
48
|
+
*/
|
|
49
|
+
className?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Optional aria-label for the form element.
|
|
52
|
+
* If not provided, aria-labelledby will reference the header title.
|
|
53
|
+
*/
|
|
54
|
+
'aria-label'?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Success message to announce when save succeeds.
|
|
57
|
+
* If not provided, no success announcement is made.
|
|
58
|
+
*/
|
|
59
|
+
successMessage?: string;
|
|
60
|
+
/**
|
|
61
|
+
* Error message to announce when save or validation fails.
|
|
62
|
+
* If not provided, generic validation errors are announced.
|
|
63
|
+
*/
|
|
64
|
+
errorMessage?: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* InlineFieldGroup enables group-level inline editing with read/edit mode switching.
|
|
68
|
+
* It integrates with TanStack Form for validation and state management, providing
|
|
69
|
+
* a complete pattern for displaying data in read mode and editing it in edit mode.
|
|
70
|
+
* The component handles the full edit lifecycle: mode transitions, form submission,
|
|
71
|
+
* validation error handling, and accessibility announcements.
|
|
72
|
+
* @example
|
|
73
|
+
* ```tsx
|
|
74
|
+
* import { useTanstackUnityForm } from '@payfit/unity-components'
|
|
75
|
+
*
|
|
76
|
+
* function ContactForm() {
|
|
77
|
+
* const form = useTanstackUnityForm({
|
|
78
|
+
* defaultValues: { email: 'user@example.com', phone: '+1234567890' },
|
|
79
|
+
* onSubmit: async ({ value }) => {
|
|
80
|
+
* await saveContact(value)
|
|
81
|
+
* }
|
|
82
|
+
* })
|
|
83
|
+
*
|
|
84
|
+
* return (
|
|
85
|
+
* <form.AppForm>
|
|
86
|
+
* <form.InlineFieldGroup successMessage="Contact saved!">
|
|
87
|
+
* <form.InlineFieldGroupHeader title="Contact Information" />
|
|
88
|
+
* <form.InlineFieldGroupReadView>
|
|
89
|
+
* <DefinitionList>
|
|
90
|
+
* <DefinitionItem term="Email" description={form.state.values.email} />
|
|
91
|
+
* </DefinitionList>
|
|
92
|
+
* </form.InlineFieldGroupReadView>
|
|
93
|
+
* <form.InlineFieldGroupEditView legend="Edit contact">
|
|
94
|
+
* <form.AppField name="email">
|
|
95
|
+
* {field => <field.TextField label="Email" />}
|
|
96
|
+
* </form.AppField>
|
|
97
|
+
* </form.InlineFieldGroupEditView>
|
|
98
|
+
* </form.InlineFieldGroup>
|
|
99
|
+
* </form.AppForm>
|
|
100
|
+
* )
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
* @remarks
|
|
104
|
+
* - The component automatically exits edit mode on successful form submission
|
|
105
|
+
* - Press Escape to cancel editing and reset form values
|
|
106
|
+
* - Focus moves to the first form field when entering edit mode
|
|
107
|
+
* - Focus returns to the edit button when exiting edit mode
|
|
108
|
+
* - Focus is retained under a scope when in edit mode, to prevent users for leaving unfinished changes
|
|
109
|
+
* - Use `shouldModeChange` to intercept and conditionally prevent mode transitions
|
|
110
|
+
* - Use `successMessage` and `errorMessage` for accessible announcements via live regions
|
|
111
|
+
* @see {@link InlineFieldGroupProps} for all available props
|
|
112
|
+
* @see {@link InlineFieldGroupHandle} for imperative handle methods
|
|
113
|
+
* @see {@link InlineFieldGroupHeader} for the header component with action buttons
|
|
114
|
+
* @see {@link InlineFieldGroupReadView} for read mode content
|
|
115
|
+
* @see {@link InlineFieldGroupEditView} for edit mode form fields
|
|
116
|
+
* @see Source code in {@link https://github.com/PayFit/hr-apps/tree/master/libs/shared/unity/components/src/components/inline-field-group GitHub}
|
|
117
|
+
* @see Developer docs in {@link https://unity-components.payfit.io/?path=/docs/forms-reference-inlinefieldgroup--docs unity-components.payfit.io}
|
|
118
|
+
*/
|
|
119
|
+
export declare const InlineFieldGroup: React.ForwardRefExoticComponent<InlineFieldGroupProps & React.RefAttributes<InlineFieldGroupHandle>>;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { jsx as x, jsxs as z } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef as J, useRef as l, useState as V, useEffect as r, useCallback as P, useImperativeHandle as Q, useMemo as W } from "react";
|
|
3
|
+
import { uyTv as X } from "@payfit/unity-themes";
|
|
4
|
+
import { useStore as Y } from "@tanstack/react-form";
|
|
5
|
+
import { useId as Z, useKeyboard as L, FocusScope as ee } from "react-aria";
|
|
6
|
+
import { useIntl as te } from "react-intl";
|
|
7
|
+
import { useFormContext as ie } from "../../hooks/tanstack-form-context.js";
|
|
8
|
+
import { useInlineFieldGroupMode as re } from "./hooks/useInlineFieldGroupMode.js";
|
|
9
|
+
import { InlineFieldGroupContext as oe } from "./InlineFieldGroup.context.js";
|
|
10
|
+
const se = X({
|
|
11
|
+
slots: {
|
|
12
|
+
form: "uy:flex uy:flex-col uy:gap-300"
|
|
13
|
+
}
|
|
14
|
+
}), D = (a) => {
|
|
15
|
+
if (!a.current) return;
|
|
16
|
+
const c = a.current.querySelector(
|
|
17
|
+
'[aria-invalid="true"]'
|
|
18
|
+
);
|
|
19
|
+
c && c.focus();
|
|
20
|
+
}, k = 5e3, ne = J(
|
|
21
|
+
({
|
|
22
|
+
children: a,
|
|
23
|
+
mode: c,
|
|
24
|
+
defaultMode: j,
|
|
25
|
+
onModeChange: q,
|
|
26
|
+
shouldModeChange: m,
|
|
27
|
+
isLoading: N,
|
|
28
|
+
className: K,
|
|
29
|
+
"aria-label": _,
|
|
30
|
+
successMessage: g,
|
|
31
|
+
errorMessage: d
|
|
32
|
+
}, O) => {
|
|
33
|
+
const s = Z(), A = `unity-InlineFieldGroup-${s}__header`, C = `unity-InlineFieldGroup-${s}__edit-view`, E = l(null), n = l(null), M = l(!0), [G, R] = V(""), [h, f] = V(""), p = te(), u = ie(), { mode: t, enterEditMode: b, exitEditMode: o } = re({
|
|
34
|
+
mode: c,
|
|
35
|
+
defaultMode: j,
|
|
36
|
+
onModeChange: q,
|
|
37
|
+
shouldModeChange: m
|
|
38
|
+
}), { isSubmitting: i, isValid: v, isSubmitSuccessful: S, submissionAttempts: y } = Y(u.store, (e) => ({
|
|
39
|
+
isSubmitting: e.isSubmitting,
|
|
40
|
+
isValid: e.isValid,
|
|
41
|
+
isSubmitSuccessful: e.isSubmitSuccessful,
|
|
42
|
+
submissionAttempts: e.submissionAttempts
|
|
43
|
+
})), F = l(i), T = l(y);
|
|
44
|
+
r(() => {
|
|
45
|
+
i && !F.current && (R(""), f(""));
|
|
46
|
+
}, [i]), r(() => {
|
|
47
|
+
if (y > T.current && !v) {
|
|
48
|
+
const I = d ?? p.formatMessage({
|
|
49
|
+
id: "unity:component:inline-field-group:validation-error",
|
|
50
|
+
defaultMessage: "Please fix the errors before saving."
|
|
51
|
+
});
|
|
52
|
+
f(I), D(n);
|
|
53
|
+
}
|
|
54
|
+
}, [y, v, d, p]), r(() => {
|
|
55
|
+
F.current && !i && S && (g && R(g), o());
|
|
56
|
+
}, [i, S, g, o]), r(() => {
|
|
57
|
+
if (F.current && !i && !S && v) {
|
|
58
|
+
const I = d ?? p.formatMessage({
|
|
59
|
+
id: "unity:component:inline-field-group:save-error",
|
|
60
|
+
defaultMessage: "An error occurred while saving. Please try again."
|
|
61
|
+
});
|
|
62
|
+
f(I);
|
|
63
|
+
}
|
|
64
|
+
}, [i, S, v, d, p]), r(() => {
|
|
65
|
+
F.current = i, T.current = y;
|
|
66
|
+
});
|
|
67
|
+
const $ = P(
|
|
68
|
+
(e) => {
|
|
69
|
+
e.preventDefault(), e.stopPropagation(), u.handleSubmit();
|
|
70
|
+
},
|
|
71
|
+
[u]
|
|
72
|
+
), w = P(() => {
|
|
73
|
+
m !== void 0 && !m(t, "read") || (u.reset(), o());
|
|
74
|
+
}, [u, t, o, m]);
|
|
75
|
+
Q(
|
|
76
|
+
O,
|
|
77
|
+
() => ({
|
|
78
|
+
focusFirstInvalidField: () => {
|
|
79
|
+
D(n);
|
|
80
|
+
},
|
|
81
|
+
exitEditMode: o,
|
|
82
|
+
enterEditMode: b
|
|
83
|
+
}),
|
|
84
|
+
[o, b]
|
|
85
|
+
), r(() => {
|
|
86
|
+
if (M.current) {
|
|
87
|
+
M.current = !1;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (t === "edit") {
|
|
91
|
+
if (n.current) {
|
|
92
|
+
const e = n.current.querySelectorAll(
|
|
93
|
+
'input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
94
|
+
);
|
|
95
|
+
e.length > 0 && e[0].focus();
|
|
96
|
+
}
|
|
97
|
+
} else
|
|
98
|
+
E.current && E.current.focus();
|
|
99
|
+
}, [t]);
|
|
100
|
+
const { keyboardProps: B } = L({
|
|
101
|
+
onKeyDown: (e) => {
|
|
102
|
+
e.key === "Escape" && t === "edit" && (e.preventDefault(), w());
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
r(() => {
|
|
106
|
+
if (G) {
|
|
107
|
+
const e = setTimeout(() => {
|
|
108
|
+
R("");
|
|
109
|
+
}, k);
|
|
110
|
+
return () => {
|
|
111
|
+
clearTimeout(e);
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}, [G]), r(() => {
|
|
115
|
+
if (h) {
|
|
116
|
+
const e = setTimeout(() => {
|
|
117
|
+
f("");
|
|
118
|
+
}, k);
|
|
119
|
+
return () => {
|
|
120
|
+
clearTimeout(e);
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}, [h]);
|
|
124
|
+
const H = W(
|
|
125
|
+
() => ({
|
|
126
|
+
mode: t,
|
|
127
|
+
enterEditMode: b,
|
|
128
|
+
exitEditMode: w,
|
|
129
|
+
groupId: s,
|
|
130
|
+
headerId: A,
|
|
131
|
+
editViewId: C,
|
|
132
|
+
isLoading: N,
|
|
133
|
+
editButtonRef: E,
|
|
134
|
+
editViewRef: n
|
|
135
|
+
}),
|
|
136
|
+
[
|
|
137
|
+
t,
|
|
138
|
+
b,
|
|
139
|
+
w,
|
|
140
|
+
s,
|
|
141
|
+
A,
|
|
142
|
+
C,
|
|
143
|
+
N
|
|
144
|
+
]
|
|
145
|
+
), { form: U } = se();
|
|
146
|
+
return /* @__PURE__ */ x(oe.Provider, { value: H, children: /* @__PURE__ */ x(
|
|
147
|
+
"form",
|
|
148
|
+
{
|
|
149
|
+
...B,
|
|
150
|
+
id: s,
|
|
151
|
+
className: U({ className: K }),
|
|
152
|
+
onSubmit: $,
|
|
153
|
+
"aria-label": _,
|
|
154
|
+
"aria-labelledby": _ ? void 0 : A,
|
|
155
|
+
children: /* @__PURE__ */ z(ee, { contain: t === "edit", children: [
|
|
156
|
+
a,
|
|
157
|
+
/* @__PURE__ */ x(
|
|
158
|
+
"div",
|
|
159
|
+
{
|
|
160
|
+
role: "status",
|
|
161
|
+
"aria-live": "polite",
|
|
162
|
+
"aria-atomic": "true",
|
|
163
|
+
className: "uy:sr-only",
|
|
164
|
+
children: G
|
|
165
|
+
}
|
|
166
|
+
),
|
|
167
|
+
/* @__PURE__ */ x(
|
|
168
|
+
"div",
|
|
169
|
+
{
|
|
170
|
+
role: "alert",
|
|
171
|
+
"aria-live": "assertive",
|
|
172
|
+
"aria-atomic": "true",
|
|
173
|
+
className: "uy:sr-only",
|
|
174
|
+
children: h
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
] })
|
|
178
|
+
}
|
|
179
|
+
) });
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
ne.displayName = "InlineFieldGroup";
|
|
183
|
+
export {
|
|
184
|
+
ne as InlineFieldGroup
|
|
185
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { InlineFieldGroupMode } from '../InlineFieldGroup.context.js';
|
|
2
|
+
export interface UseInlineFieldGroupModeProps {
|
|
3
|
+
/**
|
|
4
|
+
* Controlled mode value. When provided, the component operates in controlled mode.
|
|
5
|
+
*/
|
|
6
|
+
mode?: InlineFieldGroupMode;
|
|
7
|
+
/**
|
|
8
|
+
* Default mode value for uncontrolled mode.
|
|
9
|
+
* @default 'read'
|
|
10
|
+
*/
|
|
11
|
+
defaultMode?: InlineFieldGroupMode;
|
|
12
|
+
/**
|
|
13
|
+
* Callback fired when mode changes.
|
|
14
|
+
*/
|
|
15
|
+
onModeChange?: (mode: InlineFieldGroupMode) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Callback to intercept and potentially prevent mode changes.
|
|
18
|
+
* Return `false` to prevent the mode transition.
|
|
19
|
+
* Return `true` or `undefined` to allow the transition.
|
|
20
|
+
*/
|
|
21
|
+
shouldModeChange?: (fromMode: InlineFieldGroupMode, toMode: InlineFieldGroupMode) => boolean | undefined;
|
|
22
|
+
}
|
|
23
|
+
export interface UseInlineFieldGroupModeReturn {
|
|
24
|
+
/** Current mode value */
|
|
25
|
+
mode: InlineFieldGroupMode;
|
|
26
|
+
/** Switch to edit mode */
|
|
27
|
+
enterEditMode: () => void;
|
|
28
|
+
/** Exit edit mode (return to read mode) */
|
|
29
|
+
exitEditMode: () => void;
|
|
30
|
+
/** Check if component is in controlled mode */
|
|
31
|
+
isControlled: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Hook to manage the mode state (read/edit) for InlineFieldGroup.
|
|
35
|
+
* Supports both controlled and uncontrolled modes, following the pattern
|
|
36
|
+
* established by NavGroup.tsx
|
|
37
|
+
* @param props - Configuration for the mode hook
|
|
38
|
+
* @param props.mode - Controlled mode value. When provided, the component operates in controlled mode.
|
|
39
|
+
* @param props.defaultMode - Default mode value for uncontrolled mode.
|
|
40
|
+
* @param props.onModeChange - Callback fired when mode changes.
|
|
41
|
+
* @param props.shouldModeChange - Callback to intercept and potentially prevent mode changes.
|
|
42
|
+
* Return `false` to prevent the mode transition.
|
|
43
|
+
* Return `true` or `undefined` to allow the transition.
|
|
44
|
+
* @returns Mode state and handlers
|
|
45
|
+
*/
|
|
46
|
+
export declare function useInlineFieldGroupMode({ mode: controlledMode, defaultMode, onModeChange, shouldModeChange, }: UseInlineFieldGroupModeProps): UseInlineFieldGroupModeReturn;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useState as m, useCallback as i } from "react";
|
|
2
|
+
function x({
|
|
3
|
+
mode: d,
|
|
4
|
+
defaultMode: c = "read",
|
|
5
|
+
onModeChange: s,
|
|
6
|
+
shouldModeChange: o
|
|
7
|
+
}) {
|
|
8
|
+
const [l, u] = m(c), t = d !== void 0, n = t ? d : l, e = i(
|
|
9
|
+
(r) => {
|
|
10
|
+
o && o(n, r) === !1 || (t || u(r), s?.(r));
|
|
11
|
+
},
|
|
12
|
+
[t, n, s, o]
|
|
13
|
+
), a = i(() => {
|
|
14
|
+
e("edit");
|
|
15
|
+
}, [e]), f = i(() => {
|
|
16
|
+
e("read");
|
|
17
|
+
}, [e]);
|
|
18
|
+
return {
|
|
19
|
+
mode: n,
|
|
20
|
+
enterEditMode: a,
|
|
21
|
+
exitEditMode: f,
|
|
22
|
+
isControlled: t
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
x as useInlineFieldGroupMode
|
|
27
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { PropsWithChildren, ReactNode } from 'react';
|
|
2
|
+
export interface InlineFieldGroupEditViewProps extends PropsWithChildren {
|
|
3
|
+
/**
|
|
4
|
+
* Optional className for custom styling
|
|
5
|
+
*/
|
|
6
|
+
className?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Legend text for the fieldset. Should describe the group of fields.
|
|
9
|
+
* This is required for accessibility but will be visually hidden.
|
|
10
|
+
*/
|
|
11
|
+
legend?: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* InlineFieldGroupEditView displays form fields only when the parent InlineFieldGroup is in edit mode.
|
|
15
|
+
* Wrap form field components in this component to show them when editing.
|
|
16
|
+
* The component uses a semantic `<fieldset>` with a visually hidden `<legend>` for accessibility.
|
|
17
|
+
* It automatically disables all fields during loading states and hides content in read mode.
|
|
18
|
+
* @example Basic usage with form fields
|
|
19
|
+
* ```tsx
|
|
20
|
+
* import { useTanstackUnityForm } from '@payfit/unity-components'
|
|
21
|
+
*
|
|
22
|
+
* function Example() {
|
|
23
|
+
* const form = useTanstackUnityForm({
|
|
24
|
+
* defaultValues: { email: 'john@example.com', phone: '+33123456789' }
|
|
25
|
+
* })
|
|
26
|
+
*
|
|
27
|
+
* return (
|
|
28
|
+
* <form.AppForm>
|
|
29
|
+
* <form.InlineFieldGroup>
|
|
30
|
+
* <form.InlineFieldGroupHeader title="Contact" />
|
|
31
|
+
* <form.InlineFieldGroupReadView>
|
|
32
|
+
* <DefinitionList>
|
|
33
|
+
* <DefinitionItem term="Email" description={form.state.values.email} />
|
|
34
|
+
* </DefinitionList>
|
|
35
|
+
* </form.InlineFieldGroupReadView>
|
|
36
|
+
* <form.InlineFieldGroupEditView legend="Edit contact information">
|
|
37
|
+
* <form.AppField name="email">
|
|
38
|
+
* {field => <field.TextField label="Email" isRequired />}
|
|
39
|
+
* </form.AppField>
|
|
40
|
+
* <form.AppField name="phone">
|
|
41
|
+
* {field => <field.PhoneNumberField label="Phone" />}
|
|
42
|
+
* </form.AppField>
|
|
43
|
+
* </form.InlineFieldGroupEditView>
|
|
44
|
+
* </form.InlineFieldGroup>
|
|
45
|
+
* </form.AppForm>
|
|
46
|
+
* )
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
* @remarks
|
|
50
|
+
* - Must be used as a child of InlineFieldGroup
|
|
51
|
+
* - Set the `legend` prop to describe the group of fields for screen readers
|
|
52
|
+
* - The fieldset is disabled automatically when the parent's `isLoading` prop is true
|
|
53
|
+
* - Uses `aria-busy` to indicate loading state to assistive technologies
|
|
54
|
+
* - Content remains in the DOM but is hidden with `hidden` and `inert` attributes in read mode
|
|
55
|
+
* @see {@link InlineFieldGroupEditViewProps} for all available props
|
|
56
|
+
* @see {@link InlineFieldGroup} for the parent container component
|
|
57
|
+
* @see {@link InlineFieldGroupReadView} for the corresponding read mode component
|
|
58
|
+
* @see Source code in {@link https://github.com/PayFit/hr-apps/tree/master/libs/shared/unity/components/src/components/inline-field-group GitHub}
|
|
59
|
+
* @see Developer docs in {@link https://unity-components.payfit.io/?path=/docs/forms-reference-inlinefieldgroup--docs unity-components.payfit.io}
|
|
60
|
+
*/
|
|
61
|
+
export declare function InlineFieldGroupEditView({ children, className, legend, }: InlineFieldGroupEditViewProps): import("react/jsx-runtime").JSX.Element;
|
|
62
|
+
export declare namespace InlineFieldGroupEditView {
|
|
63
|
+
var displayName: string;
|
|
64
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsxs as h, jsx as e } from "react/jsx-runtime";
|
|
2
|
+
import { useContext as g } from "react";
|
|
3
|
+
import { uyTv as w } from "@payfit/unity-themes";
|
|
4
|
+
import { useIntl as x } from "react-intl";
|
|
5
|
+
import { Button as v } from "../../button/Button.js";
|
|
6
|
+
import { InlineFieldGroupContext as I } from "../InlineFieldGroup.context.js";
|
|
7
|
+
const N = w({
|
|
8
|
+
slots: {
|
|
9
|
+
fieldset: "uy:border-0 uy:p-0 uy:m-0",
|
|
10
|
+
legend: "uy:sr-only",
|
|
11
|
+
fieldsContainer: "uy:flex uy:flex-col uy:gap-250",
|
|
12
|
+
hiddenButtons: "uy:sr-only"
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
function b({
|
|
16
|
+
children: d,
|
|
17
|
+
className: s,
|
|
18
|
+
legend: o
|
|
19
|
+
}) {
|
|
20
|
+
const l = x(), t = g(I);
|
|
21
|
+
if (!t)
|
|
22
|
+
throw new Error(
|
|
23
|
+
"InlineFieldGroupEditView must be used within InlineFieldGroup"
|
|
24
|
+
);
|
|
25
|
+
const { mode: r, editViewId: a, isLoading: n, editViewRef: u } = t, i = r === "edit", {
|
|
26
|
+
fieldset: m,
|
|
27
|
+
legend: f,
|
|
28
|
+
fieldsContainer: c,
|
|
29
|
+
hiddenButtons: p
|
|
30
|
+
} = N(), y = l.formatMessage({
|
|
31
|
+
id: "unity:component:inline-field-group:save",
|
|
32
|
+
defaultMessage: "Save"
|
|
33
|
+
});
|
|
34
|
+
return /* @__PURE__ */ h(
|
|
35
|
+
"fieldset",
|
|
36
|
+
{
|
|
37
|
+
ref: u,
|
|
38
|
+
id: a,
|
|
39
|
+
className: m({ className: s }),
|
|
40
|
+
disabled: n,
|
|
41
|
+
hidden: !i,
|
|
42
|
+
"aria-hidden": !i,
|
|
43
|
+
"aria-busy": n,
|
|
44
|
+
...!i && { inert: "" },
|
|
45
|
+
children: [
|
|
46
|
+
/* @__PURE__ */ e("legend", { className: f(), children: o }),
|
|
47
|
+
/* @__PURE__ */ e("div", { className: c(), children: d }),
|
|
48
|
+
/* @__PURE__ */ e("div", { className: p(), children: /* @__PURE__ */ e(v, { type: "submit", variant: "primary", children: y }) })
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
b.displayName = "InlineFieldGroupEditView";
|
|
54
|
+
export {
|
|
55
|
+
b as InlineFieldGroupEditView
|
|
56
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
export interface InlineFieldGroupHeaderProps {
|
|
3
|
+
/**
|
|
4
|
+
* The title to display in the header.
|
|
5
|
+
* This will be used for aria-labelledby on the form element.
|
|
6
|
+
*/
|
|
7
|
+
title: ReactNode;
|
|
8
|
+
/**
|
|
9
|
+
* Custom label for the edit button.
|
|
10
|
+
* @default "Edit" (translated)
|
|
11
|
+
*/
|
|
12
|
+
editLabel?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Custom label for the save button.
|
|
15
|
+
* @default "Save" (translated)
|
|
16
|
+
*/
|
|
17
|
+
saveLabel?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Custom label for the cancel button.
|
|
20
|
+
* @default "Cancel" (translated)
|
|
21
|
+
*/
|
|
22
|
+
cancelLabel?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Additional custom actions to render alongside the default buttons.
|
|
25
|
+
* These will be displayed between the title and the edit/save/cancel buttons.
|
|
26
|
+
*/
|
|
27
|
+
customActions?: ReactNode;
|
|
28
|
+
/**
|
|
29
|
+
* Optional callback fired when the edit button is clicked.
|
|
30
|
+
* This is called before entering edit mode and is useful for analytics or side effects.
|
|
31
|
+
*/
|
|
32
|
+
onEditPress?: () => void;
|
|
33
|
+
/**
|
|
34
|
+
* Optional callback fired when the save button is clicked.
|
|
35
|
+
* This is called before form submission and is useful for analytics or side effects.
|
|
36
|
+
* Note: Form submission is handled via the form's onSubmit configuration.
|
|
37
|
+
*/
|
|
38
|
+
onSavePress?: () => void;
|
|
39
|
+
/**
|
|
40
|
+
* Optional callback fired when the cancel button is clicked.
|
|
41
|
+
* This is called before canceling and is useful for analytics or side effects.
|
|
42
|
+
* The form will be reset and edit mode exited automatically.
|
|
43
|
+
*/
|
|
44
|
+
onCancelPress?: () => void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* InlineFieldGroupHeader renders the title and contextual action buttons for an InlineFieldGroup.
|
|
48
|
+
* It automatically switches between Edit button (read mode) and Save/Cancel buttons (edit mode)
|
|
49
|
+
* based on the parent InlineFieldGroup's current mode.
|
|
50
|
+
* The Save button integrates with TanStack Form and remains disabled until the form has changes.
|
|
51
|
+
* Cancel resets the form and returns to read mode.
|
|
52
|
+
* @example Basic usage
|
|
53
|
+
* ```tsx
|
|
54
|
+
* import { useTanstackUnityForm } from '@payfit/unity-components'
|
|
55
|
+
*
|
|
56
|
+
* function Example() {
|
|
57
|
+
* const form = useTanstackUnityForm({ defaultValues: { name: 'John' } })
|
|
58
|
+
*
|
|
59
|
+
* return (
|
|
60
|
+
* <form.AppForm>
|
|
61
|
+
* <form.InlineFieldGroup>
|
|
62
|
+
* <form.InlineFieldGroupHeader title="Contact Information" />
|
|
63
|
+
* <form.InlineFieldGroupReadView>...</form.InlineFieldGroupReadView>
|
|
64
|
+
* <form.InlineFieldGroupEditView legend="Edit">...</form.InlineFieldGroupEditView>
|
|
65
|
+
* </form.InlineFieldGroup>
|
|
66
|
+
* </form.AppForm>
|
|
67
|
+
* )
|
|
68
|
+
* }
|
|
69
|
+
* ```
|
|
70
|
+
* @example Custom button labels and actions
|
|
71
|
+
* ```tsx
|
|
72
|
+
* <form.InlineFieldGroupHeader
|
|
73
|
+
* title="Contact Information"
|
|
74
|
+
* editLabel="Modify"
|
|
75
|
+
* saveLabel="Confirm"
|
|
76
|
+
* cancelLabel="Discard"
|
|
77
|
+
* customActions={<Button onPress={handleDelete}>Delete</Button>}
|
|
78
|
+
* onSavePress={() => trackAnalytics('save_clicked')}
|
|
79
|
+
* />
|
|
80
|
+
* ```
|
|
81
|
+
* @remarks
|
|
82
|
+
* - Must be used as a child of InlineFieldGroup
|
|
83
|
+
* - The title serves as the accessible label for the form via `aria-labelledby`
|
|
84
|
+
* - The Edit button uses `aria-expanded` and `aria-controls` for accessibility
|
|
85
|
+
* - Use `customActions` to add buttons that appear in both read and edit modes
|
|
86
|
+
* - Use `onEditPress`, `onSavePress`, `onCancelPress` for analytics or side effects
|
|
87
|
+
* @see {@link InlineFieldGroupHeaderProps} for all available props
|
|
88
|
+
* @see {@link InlineFieldGroup} for the parent container component
|
|
89
|
+
* @see Source code in {@link https://github.com/PayFit/hr-apps/tree/master/libs/shared/unity/components/src/components/inline-field-group GitHub}
|
|
90
|
+
* @see Developer docs in {@link https://unity-components.payfit.io/?path=/docs/forms-reference-inlinefieldgroup--docs unity-components.payfit.io}
|
|
91
|
+
*/
|
|
92
|
+
export declare function InlineFieldGroupHeader({ title, editLabel, saveLabel, cancelLabel, customActions, onEditPress, onSavePress, onCancelPress, }: InlineFieldGroupHeaderProps): import("react/jsx-runtime").JSX.Element;
|
|
93
|
+
export declare namespace InlineFieldGroupHeader {
|
|
94
|
+
var displayName: string;
|
|
95
|
+
}
|