@launchpad-ui/inline-edit 0.1.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/LICENSE +13 -0
- package/README.md +14 -0
- package/dist/InlineEdit.d.ts +19 -0
- package/dist/InlineEdit.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.es.js +133 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.js +133 -0
- package/dist/index.js.map +1 -0
- package/dist/style.css +31 -0
- package/dist/styles/InlineEdit.css.d.ts +19 -0
- package/dist/styles/InlineEdit.css.d.ts.map +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2022 Catamorphic, Co.
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
package/README.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# @launchpad-ui/inline-edit
|
2
|
+
|
3
|
+
> An element used to display and allow inline editing of a form element value.
|
4
|
+
|
5
|
+
[](https://www.npmjs.com/package/@launchpad-ui/inline-edit)
|
6
|
+
[](https://bundlephobia.com/result?p=@launchpad-ui/inline-edit)
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
```sh
|
11
|
+
$ yarn add @launchpad-ui/inline-edit
|
12
|
+
# or
|
13
|
+
$ npm install @launchpad-ui/inline-edit
|
14
|
+
```
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import type { InlineVariants } from './styles/InlineEdit.css';
|
2
|
+
import type { TextAreaProps, TextFieldProps } from '@launchpad-ui/form';
|
3
|
+
import type { ComponentProps, Dispatch, ReactElement, SetStateAction } from 'react';
|
4
|
+
type InlineEditProps = ComponentProps<'div'> & InlineVariants & Pick<ComponentProps<'input'>, 'defaultValue'> & {
|
5
|
+
'data-test-id'?: string;
|
6
|
+
onConfirm: Dispatch<SetStateAction<string>>;
|
7
|
+
hideEdit?: boolean;
|
8
|
+
renderInput?: ReactElement<TextFieldProps | TextAreaProps>;
|
9
|
+
isEditing?: boolean;
|
10
|
+
onCancel?: () => void;
|
11
|
+
onEdit?: () => void;
|
12
|
+
cancelButtonLabel?: string;
|
13
|
+
editButtonLabel?: string;
|
14
|
+
confirmButtonLabel?: string;
|
15
|
+
};
|
16
|
+
declare const InlineEdit: ({ "data-test-id": testId, layout, children, defaultValue, onConfirm, hideEdit, renderInput, "aria-label": ariaLabel, isEditing: isEditingProp, onCancel, onEdit, cancelButtonLabel, editButtonLabel, confirmButtonLabel, }: InlineEditProps) => import("react/jsx-runtime").JSX.Element;
|
17
|
+
export { InlineEdit };
|
18
|
+
export type { InlineEditProps };
|
19
|
+
//# sourceMappingURL=InlineEdit.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"InlineEdit.d.ts","sourceRoot":"","sources":["../src/InlineEdit.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,KAAK,EACV,cAAc,EACd,QAAQ,EAER,YAAY,EACZ,cAAc,EACf,MAAM,OAAO,CAAC;AAcf,KAAK,eAAe,GAAG,cAAc,CAAC,KAAK,CAAC,GAC1C,cAAc,GACd,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC,GAAG;IAC9C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,YAAY,CAAC,cAAc,GAAG,aAAa,CAAC,CAAC;IAC3D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEJ,QAAA,MAAM,UAAU,+NAeb,eAAe,4CA+GjB,CAAC;AAEF,OAAO,EAAE,UAAU,EAAE,CAAC;AACtB,YAAY,EAAE,eAAe,EAAE,CAAC"}
|
package/dist/index.d.ts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.es.js
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
import './style.css';
|
2
|
+
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
3
|
+
import { IconButton, ButtonGroup } from "@launchpad-ui/button";
|
4
|
+
import { TextField } from "@launchpad-ui/form";
|
5
|
+
import { Icon } from "@launchpad-ui/icons";
|
6
|
+
import { useButton } from "@react-aria/button";
|
7
|
+
import { focusSafely } from "@react-aria/focus";
|
8
|
+
import { useFocusWithin } from "@react-aria/interactions";
|
9
|
+
import { useUpdateEffect, mergeProps } from "@react-aria/utils";
|
10
|
+
import { cx } from "classix";
|
11
|
+
import { useState, useRef, cloneElement } from "react";
|
12
|
+
import { createRuntimeFn } from "@vanilla-extract/recipes/createRuntimeFn";
|
13
|
+
const InlineEdit_css_ts_vanilla = "";
|
14
|
+
var cancelButton = "_1oig0624";
|
15
|
+
var container = "_1oig0620";
|
16
|
+
var inline = createRuntimeFn({ defaultClassName: "_1oig0621", variantClassNames: { layout: { vertical: "_1oig0622", horizontal: "_1oig0623" } }, defaultVariants: {}, compoundVariants: [] });
|
17
|
+
var readButton = "_1oig0625";
|
18
|
+
const InlineEdit = ({
|
19
|
+
"data-test-id": testId = "inline-edit",
|
20
|
+
layout = "horizontal",
|
21
|
+
children,
|
22
|
+
defaultValue,
|
23
|
+
onConfirm,
|
24
|
+
hideEdit = false,
|
25
|
+
renderInput = /* @__PURE__ */ jsx(TextField, {}),
|
26
|
+
"aria-label": ariaLabel,
|
27
|
+
isEditing: isEditingProp,
|
28
|
+
onCancel,
|
29
|
+
onEdit,
|
30
|
+
cancelButtonLabel = "cancel",
|
31
|
+
editButtonLabel = "edit",
|
32
|
+
confirmButtonLabel = "confirm"
|
33
|
+
}) => {
|
34
|
+
const [isEditing, setEditing] = useState(isEditingProp ?? false);
|
35
|
+
const [isFocusWithin, setFocusWithin] = useState(false);
|
36
|
+
const inputRef = useRef(null);
|
37
|
+
const editRef = useRef(null);
|
38
|
+
const controlled = isEditingProp !== void 0;
|
39
|
+
useUpdateEffect(() => {
|
40
|
+
if (controlled) {
|
41
|
+
setEditing(isEditingProp);
|
42
|
+
}
|
43
|
+
}, [isEditingProp]);
|
44
|
+
useUpdateEffect(() => {
|
45
|
+
if (isFocusWithin) {
|
46
|
+
isEditing ? inputRef.current && focusSafely(inputRef.current) : editRef.current && focusSafely(editRef.current);
|
47
|
+
}
|
48
|
+
}, [isEditing]);
|
49
|
+
const handleEdit = () => {
|
50
|
+
!controlled && setEditing(true);
|
51
|
+
onEdit == null ? void 0 : onEdit();
|
52
|
+
};
|
53
|
+
const handleCancel = () => {
|
54
|
+
!controlled && setEditing(false);
|
55
|
+
onCancel == null ? void 0 : onCancel();
|
56
|
+
};
|
57
|
+
const handleConfirm = () => {
|
58
|
+
var _a;
|
59
|
+
onConfirm(((_a = inputRef.current) == null ? void 0 : _a.value) || "");
|
60
|
+
!controlled && setEditing(false);
|
61
|
+
};
|
62
|
+
const handleKeyDown = (event) => {
|
63
|
+
if (event.key === "Enter") {
|
64
|
+
event.preventDefault();
|
65
|
+
handleConfirm();
|
66
|
+
} else if (event.key === "Escape") {
|
67
|
+
event.preventDefault();
|
68
|
+
handleCancel();
|
69
|
+
}
|
70
|
+
};
|
71
|
+
const { focusWithinProps } = useFocusWithin({
|
72
|
+
onBlurWithin: () => isEditing && handleCancel(),
|
73
|
+
onFocusWithinChange: (isFocusWithin2) => setFocusWithin(isFocusWithin2)
|
74
|
+
});
|
75
|
+
const { buttonProps } = useButton(
|
76
|
+
{
|
77
|
+
"aria-label": editButtonLabel,
|
78
|
+
elementType: "span",
|
79
|
+
onPress: handleEdit
|
80
|
+
},
|
81
|
+
editRef
|
82
|
+
);
|
83
|
+
const renderReadContent = hideEdit ? /* @__PURE__ */ jsx("span", { ref: editRef, ...buttonProps, className: readButton, children }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
84
|
+
children,
|
85
|
+
/* @__PURE__ */ jsx(
|
86
|
+
IconButton,
|
87
|
+
{
|
88
|
+
ref: editRef,
|
89
|
+
icon: /* @__PURE__ */ jsx(Icon, { name: "edit" }),
|
90
|
+
"aria-label": editButtonLabel,
|
91
|
+
size: "small",
|
92
|
+
onClick: handleEdit
|
93
|
+
}
|
94
|
+
)
|
95
|
+
] });
|
96
|
+
const input = cloneElement(
|
97
|
+
renderInput,
|
98
|
+
mergeProps(renderInput.props, {
|
99
|
+
ref: inputRef,
|
100
|
+
defaultValue,
|
101
|
+
onKeyDown: handleKeyDown,
|
102
|
+
"aria-label": ariaLabel
|
103
|
+
})
|
104
|
+
);
|
105
|
+
return isEditing ? /* @__PURE__ */ jsxs("div", { className: cx(container, inline({ layout })), "data-test-id": testId, ...focusWithinProps, children: [
|
106
|
+
input,
|
107
|
+
/* @__PURE__ */ jsxs(ButtonGroup, { spacing: "compact", children: [
|
108
|
+
/* @__PURE__ */ jsx(
|
109
|
+
IconButton,
|
110
|
+
{
|
111
|
+
kind: "primary",
|
112
|
+
icon: /* @__PURE__ */ jsx(Icon, { name: "check" }),
|
113
|
+
"aria-label": confirmButtonLabel,
|
114
|
+
onClick: handleConfirm
|
115
|
+
}
|
116
|
+
),
|
117
|
+
/* @__PURE__ */ jsx(
|
118
|
+
IconButton,
|
119
|
+
{
|
120
|
+
kind: "default",
|
121
|
+
icon: /* @__PURE__ */ jsx(Icon, { name: "close" }),
|
122
|
+
"aria-label": cancelButtonLabel,
|
123
|
+
className: cancelButton,
|
124
|
+
onClick: handleCancel
|
125
|
+
}
|
126
|
+
)
|
127
|
+
] })
|
128
|
+
] }) : /* @__PURE__ */ jsx("div", { className: cx(!hideEdit && container), "data-test-id": testId, ...focusWithinProps, children: renderReadContent });
|
129
|
+
};
|
130
|
+
export {
|
131
|
+
InlineEdit
|
132
|
+
};
|
133
|
+
//# sourceMappingURL=index.es.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.es.js","sources":["../src/InlineEdit.tsx"],"sourcesContent":["import type { InlineVariants } from './styles/InlineEdit.css';\nimport type { TextAreaProps, TextFieldProps } from '@launchpad-ui/form';\nimport type {\n ComponentProps,\n Dispatch,\n KeyboardEventHandler,\n ReactElement,\n SetStateAction,\n} from 'react';\n\nimport { ButtonGroup, IconButton } from '@launchpad-ui/button';\nimport { TextField } from '@launchpad-ui/form';\nimport { Icon } from '@launchpad-ui/icons';\nimport { useButton } from '@react-aria/button';\nimport { focusSafely } from '@react-aria/focus';\nimport { useFocusWithin } from '@react-aria/interactions';\nimport { mergeProps, useUpdateEffect } from '@react-aria/utils';\nimport { cx } from 'classix';\nimport { cloneElement, useRef, useState } from 'react';\n\nimport { container, cancelButton, inline, readButton } from './styles/InlineEdit.css';\n\ntype InlineEditProps = ComponentProps<'div'> &\n InlineVariants &\n Pick<ComponentProps<'input'>, 'defaultValue'> & {\n 'data-test-id'?: string;\n onConfirm: Dispatch<SetStateAction<string>>;\n hideEdit?: boolean;\n renderInput?: ReactElement<TextFieldProps | TextAreaProps>;\n isEditing?: boolean;\n onCancel?: () => void;\n onEdit?: () => void;\n cancelButtonLabel?: string;\n editButtonLabel?: string;\n confirmButtonLabel?: string;\n };\n\nconst InlineEdit = ({\n 'data-test-id': testId = 'inline-edit',\n layout = 'horizontal',\n children,\n defaultValue,\n onConfirm,\n hideEdit = false,\n renderInput = <TextField />,\n 'aria-label': ariaLabel,\n isEditing: isEditingProp,\n onCancel,\n onEdit,\n cancelButtonLabel = 'cancel',\n editButtonLabel = 'edit',\n confirmButtonLabel = 'confirm',\n}: InlineEditProps) => {\n const [isEditing, setEditing] = useState(isEditingProp ?? false);\n const [isFocusWithin, setFocusWithin] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n const editRef = useRef<HTMLButtonElement>(null);\n const controlled = isEditingProp !== undefined;\n\n useUpdateEffect(() => {\n if (controlled) {\n setEditing(isEditingProp);\n }\n }, [isEditingProp]);\n\n useUpdateEffect(() => {\n if (isFocusWithin) {\n isEditing\n ? inputRef.current && focusSafely(inputRef.current)\n : editRef.current && focusSafely(editRef.current);\n }\n }, [isEditing]);\n\n const handleEdit = () => {\n !controlled && setEditing(true);\n onEdit?.();\n };\n\n const handleCancel = () => {\n !controlled && setEditing(false);\n onCancel?.();\n };\n\n const handleConfirm = () => {\n onConfirm(inputRef.current?.value || '');\n !controlled && setEditing(false);\n };\n\n const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (event) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n handleConfirm();\n } else if (event.key === 'Escape') {\n event.preventDefault();\n handleCancel();\n }\n };\n\n const { focusWithinProps } = useFocusWithin({\n onBlurWithin: () => isEditing && handleCancel(),\n onFocusWithinChange: (isFocusWithin) => setFocusWithin(isFocusWithin),\n });\n\n const { buttonProps } = useButton(\n {\n 'aria-label': editButtonLabel,\n elementType: 'span',\n onPress: handleEdit,\n },\n editRef\n );\n\n const renderReadContent = hideEdit ? (\n <span ref={editRef} {...buttonProps} className={readButton}>\n {children}\n </span>\n ) : (\n <>\n {children}\n <IconButton\n ref={editRef}\n icon={<Icon name=\"edit\" />}\n aria-label={editButtonLabel}\n size=\"small\"\n onClick={handleEdit}\n />\n </>\n );\n\n const input = cloneElement(\n renderInput,\n mergeProps(renderInput.props, {\n ref: inputRef,\n defaultValue,\n onKeyDown: handleKeyDown,\n 'aria-label': ariaLabel,\n })\n );\n\n return isEditing ? (\n <div className={cx(container, inline({ layout }))} data-test-id={testId} {...focusWithinProps}>\n {input}\n <ButtonGroup spacing=\"compact\">\n <IconButton\n kind=\"primary\"\n icon={<Icon name=\"check\" />}\n aria-label={confirmButtonLabel}\n onClick={handleConfirm}\n />\n <IconButton\n kind=\"default\"\n icon={<Icon name=\"close\" />}\n aria-label={cancelButtonLabel}\n className={cancelButton}\n onClick={handleCancel}\n />\n </ButtonGroup>\n </div>\n ) : (\n <div className={cx(!hideEdit && container)} data-test-id={testId} {...focusWithinProps}>\n {renderReadContent}\n </div>\n );\n};\n\nexport { InlineEdit };\nexport type { InlineEditProps };\n"],"names":["isFocusWithin"],"mappings":";;;;;;;;;;;;;;;;AAqCA,MAAM,aAAa,CAAC;AAAA,EAClB,gBAAgB,SAAS;AAAA,EACzB,SAAS;AAAA,EACT;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,kCAAe,WAAU,EAAA;AAAA,EACzB,cAAc;AAAA,EACd,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB,qBAAqB;AACvB,MAAuB;AACrB,QAAM,CAAC,WAAW,UAAU,IAAI,SAAS,iBAAiB,KAAK;AAC/D,QAAM,CAAC,eAAe,cAAc,IAAI,SAAS,KAAK;AAChD,QAAA,WAAW,OAAyB,IAAI;AACxC,QAAA,UAAU,OAA0B,IAAI;AAC9C,QAAM,aAAa,kBAAkB;AAErC,kBAAgB,MAAM;AACpB,QAAI,YAAY;AACd,iBAAW,aAAa;AAAA,IAC1B;AAAA,EAAA,GACC,CAAC,aAAa,CAAC;AAElB,kBAAgB,MAAM;AACpB,QAAI,eAAe;AAEb,kBAAA,SAAS,WAAW,YAAY,SAAS,OAAO,IAChD,QAAQ,WAAW,YAAY,QAAQ,OAAO;AAAA,IACpD;AAAA,EAAA,GACC,CAAC,SAAS,CAAC;AAEd,QAAM,aAAa,MAAM;AACtB,KAAA,cAAc,WAAW,IAAI;AACrB;AAAA,EAAA;AAGX,QAAM,eAAe,MAAM;AACxB,KAAA,cAAc,WAAW,KAAK;AACpB;AAAA,EAAA;AAGb,QAAM,gBAAgB,MAAM;;AAChB,gBAAA,cAAS,YAAT,mBAAkB,UAAS,EAAE;AACtC,KAAA,cAAc,WAAW,KAAK;AAAA,EAAA;AAG3B,QAAA,gBAAwD,CAAC,UAAU;AACnE,QAAA,MAAM,QAAQ,SAAS;AACzB,YAAM,eAAe;AACP;IAAA,WACL,MAAM,QAAQ,UAAU;AACjC,YAAM,eAAe;AACR;IACf;AAAA,EAAA;AAGI,QAAA,EAAE,iBAAiB,IAAI,eAAe;AAAA,IAC1C,cAAc,MAAM,aAAa,aAAa;AAAA,IAC9C,qBAAqB,CAACA,mBAAkB,eAAeA,cAAa;AAAA,EAAA,CACrE;AAEK,QAAA,EAAE,gBAAgB;AAAA,IACtB;AAAA,MACE,cAAc;AAAA,MACd,aAAa;AAAA,MACb,SAAS;AAAA,IACX;AAAA,IACA;AAAA,EAAA;AAGF,QAAM,oBAAoB,WACvB,oBAAA,QAAA,EAAK,KAAK,SAAU,GAAG,aAAa,WAAW,YAC7C,SACH,CAAA,IAGG,qBAAA,UAAA,EAAA,UAAA;AAAA,IAAA;AAAA,IACD;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK;AAAA,QACL,MAAM,oBAAC,MAAK,EAAA,MAAK,OAAO,CAAA;AAAA,QACxB,cAAY;AAAA,QACZ,MAAK;AAAA,QACL,SAAS;AAAA,MAAA;AAAA,IACX;AAAA,EACF,EAAA,CAAA;AAGF,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA,WAAW,YAAY,OAAO;AAAA,MAC5B,KAAK;AAAA,MACL;AAAA,MACA,WAAW;AAAA,MACX,cAAc;AAAA,IAAA,CACf;AAAA,EAAA;AAGH,SAAO,YACL,qBAAC,OAAI,EAAA,WAAW,GAAG,WAAW,OAAO,EAAE,OAAA,CAAQ,CAAC,GAAG,gBAAc,QAAS,GAAG,kBAC1E,UAAA;AAAA,IAAA;AAAA,IACD,qBAAC,aAAY,EAAA,SAAQ,WACnB,UAAA;AAAA,MAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,MAAM,oBAAC,MAAK,EAAA,MAAK,QAAQ,CAAA;AAAA,UACzB,cAAY;AAAA,UACZ,SAAS;AAAA,QAAA;AAAA,MACX;AAAA,MACA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,MAAM,oBAAC,MAAK,EAAA,MAAK,QAAQ,CAAA;AAAA,UACzB,cAAY;AAAA,UACZ,WAAW;AAAA,UACX,SAAS;AAAA,QAAA;AAAA,MACX;AAAA,IAAA,GACF;AAAA,EAAA,EACF,CAAA,IAEA,oBAAC,OAAI,EAAA,WAAW,GAAG,CAAC,YAAY,SAAS,GAAG,gBAAc,QAAS,GAAG,kBACnE,UACH,kBAAA,CAAA;AAEJ;"}
|
package/dist/index.js
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require('./style.css');
|
2
|
+
"use strict";
|
3
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
4
|
+
const jsxRuntime = require("react/jsx-runtime");
|
5
|
+
const button$1 = require("@launchpad-ui/button");
|
6
|
+
const form = require("@launchpad-ui/form");
|
7
|
+
const icons = require("@launchpad-ui/icons");
|
8
|
+
const button = require("@react-aria/button");
|
9
|
+
const focus = require("@react-aria/focus");
|
10
|
+
const interactions = require("@react-aria/interactions");
|
11
|
+
const utils = require("@react-aria/utils");
|
12
|
+
const classix = require("classix");
|
13
|
+
const react = require("react");
|
14
|
+
const createRuntimeFn = require("@vanilla-extract/recipes/createRuntimeFn");
|
15
|
+
const InlineEdit_css_ts_vanilla = "";
|
16
|
+
var cancelButton = "_1oig0624";
|
17
|
+
var container = "_1oig0620";
|
18
|
+
var inline = createRuntimeFn.createRuntimeFn({ defaultClassName: "_1oig0621", variantClassNames: { layout: { vertical: "_1oig0622", horizontal: "_1oig0623" } }, defaultVariants: {}, compoundVariants: [] });
|
19
|
+
var readButton = "_1oig0625";
|
20
|
+
const InlineEdit = ({
|
21
|
+
"data-test-id": testId = "inline-edit",
|
22
|
+
layout = "horizontal",
|
23
|
+
children,
|
24
|
+
defaultValue,
|
25
|
+
onConfirm,
|
26
|
+
hideEdit = false,
|
27
|
+
renderInput = /* @__PURE__ */ jsxRuntime.jsx(form.TextField, {}),
|
28
|
+
"aria-label": ariaLabel,
|
29
|
+
isEditing: isEditingProp,
|
30
|
+
onCancel,
|
31
|
+
onEdit,
|
32
|
+
cancelButtonLabel = "cancel",
|
33
|
+
editButtonLabel = "edit",
|
34
|
+
confirmButtonLabel = "confirm"
|
35
|
+
}) => {
|
36
|
+
const [isEditing, setEditing] = react.useState(isEditingProp ?? false);
|
37
|
+
const [isFocusWithin, setFocusWithin] = react.useState(false);
|
38
|
+
const inputRef = react.useRef(null);
|
39
|
+
const editRef = react.useRef(null);
|
40
|
+
const controlled = isEditingProp !== void 0;
|
41
|
+
utils.useUpdateEffect(() => {
|
42
|
+
if (controlled) {
|
43
|
+
setEditing(isEditingProp);
|
44
|
+
}
|
45
|
+
}, [isEditingProp]);
|
46
|
+
utils.useUpdateEffect(() => {
|
47
|
+
if (isFocusWithin) {
|
48
|
+
isEditing ? inputRef.current && focus.focusSafely(inputRef.current) : editRef.current && focus.focusSafely(editRef.current);
|
49
|
+
}
|
50
|
+
}, [isEditing]);
|
51
|
+
const handleEdit = () => {
|
52
|
+
!controlled && setEditing(true);
|
53
|
+
onEdit == null ? void 0 : onEdit();
|
54
|
+
};
|
55
|
+
const handleCancel = () => {
|
56
|
+
!controlled && setEditing(false);
|
57
|
+
onCancel == null ? void 0 : onCancel();
|
58
|
+
};
|
59
|
+
const handleConfirm = () => {
|
60
|
+
var _a;
|
61
|
+
onConfirm(((_a = inputRef.current) == null ? void 0 : _a.value) || "");
|
62
|
+
!controlled && setEditing(false);
|
63
|
+
};
|
64
|
+
const handleKeyDown = (event) => {
|
65
|
+
if (event.key === "Enter") {
|
66
|
+
event.preventDefault();
|
67
|
+
handleConfirm();
|
68
|
+
} else if (event.key === "Escape") {
|
69
|
+
event.preventDefault();
|
70
|
+
handleCancel();
|
71
|
+
}
|
72
|
+
};
|
73
|
+
const { focusWithinProps } = interactions.useFocusWithin({
|
74
|
+
onBlurWithin: () => isEditing && handleCancel(),
|
75
|
+
onFocusWithinChange: (isFocusWithin2) => setFocusWithin(isFocusWithin2)
|
76
|
+
});
|
77
|
+
const { buttonProps } = button.useButton(
|
78
|
+
{
|
79
|
+
"aria-label": editButtonLabel,
|
80
|
+
elementType: "span",
|
81
|
+
onPress: handleEdit
|
82
|
+
},
|
83
|
+
editRef
|
84
|
+
);
|
85
|
+
const renderReadContent = hideEdit ? /* @__PURE__ */ jsxRuntime.jsx("span", { ref: editRef, ...buttonProps, className: readButton, children }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
86
|
+
children,
|
87
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
88
|
+
button$1.IconButton,
|
89
|
+
{
|
90
|
+
ref: editRef,
|
91
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(icons.Icon, { name: "edit" }),
|
92
|
+
"aria-label": editButtonLabel,
|
93
|
+
size: "small",
|
94
|
+
onClick: handleEdit
|
95
|
+
}
|
96
|
+
)
|
97
|
+
] });
|
98
|
+
const input = react.cloneElement(
|
99
|
+
renderInput,
|
100
|
+
utils.mergeProps(renderInput.props, {
|
101
|
+
ref: inputRef,
|
102
|
+
defaultValue,
|
103
|
+
onKeyDown: handleKeyDown,
|
104
|
+
"aria-label": ariaLabel
|
105
|
+
})
|
106
|
+
);
|
107
|
+
return isEditing ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classix.cx(container, inline({ layout })), "data-test-id": testId, ...focusWithinProps, children: [
|
108
|
+
input,
|
109
|
+
/* @__PURE__ */ jsxRuntime.jsxs(button$1.ButtonGroup, { spacing: "compact", children: [
|
110
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
111
|
+
button$1.IconButton,
|
112
|
+
{
|
113
|
+
kind: "primary",
|
114
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(icons.Icon, { name: "check" }),
|
115
|
+
"aria-label": confirmButtonLabel,
|
116
|
+
onClick: handleConfirm
|
117
|
+
}
|
118
|
+
),
|
119
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
120
|
+
button$1.IconButton,
|
121
|
+
{
|
122
|
+
kind: "default",
|
123
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(icons.Icon, { name: "close" }),
|
124
|
+
"aria-label": cancelButtonLabel,
|
125
|
+
className: cancelButton,
|
126
|
+
onClick: handleCancel
|
127
|
+
}
|
128
|
+
)
|
129
|
+
] })
|
130
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: classix.cx(!hideEdit && container), "data-test-id": testId, ...focusWithinProps, children: renderReadContent });
|
131
|
+
};
|
132
|
+
exports.InlineEdit = InlineEdit;
|
133
|
+
//# sourceMappingURL=index.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/InlineEdit.tsx"],"sourcesContent":["import type { InlineVariants } from './styles/InlineEdit.css';\nimport type { TextAreaProps, TextFieldProps } from '@launchpad-ui/form';\nimport type {\n ComponentProps,\n Dispatch,\n KeyboardEventHandler,\n ReactElement,\n SetStateAction,\n} from 'react';\n\nimport { ButtonGroup, IconButton } from '@launchpad-ui/button';\nimport { TextField } from '@launchpad-ui/form';\nimport { Icon } from '@launchpad-ui/icons';\nimport { useButton } from '@react-aria/button';\nimport { focusSafely } from '@react-aria/focus';\nimport { useFocusWithin } from '@react-aria/interactions';\nimport { mergeProps, useUpdateEffect } from '@react-aria/utils';\nimport { cx } from 'classix';\nimport { cloneElement, useRef, useState } from 'react';\n\nimport { container, cancelButton, inline, readButton } from './styles/InlineEdit.css';\n\ntype InlineEditProps = ComponentProps<'div'> &\n InlineVariants &\n Pick<ComponentProps<'input'>, 'defaultValue'> & {\n 'data-test-id'?: string;\n onConfirm: Dispatch<SetStateAction<string>>;\n hideEdit?: boolean;\n renderInput?: ReactElement<TextFieldProps | TextAreaProps>;\n isEditing?: boolean;\n onCancel?: () => void;\n onEdit?: () => void;\n cancelButtonLabel?: string;\n editButtonLabel?: string;\n confirmButtonLabel?: string;\n };\n\nconst InlineEdit = ({\n 'data-test-id': testId = 'inline-edit',\n layout = 'horizontal',\n children,\n defaultValue,\n onConfirm,\n hideEdit = false,\n renderInput = <TextField />,\n 'aria-label': ariaLabel,\n isEditing: isEditingProp,\n onCancel,\n onEdit,\n cancelButtonLabel = 'cancel',\n editButtonLabel = 'edit',\n confirmButtonLabel = 'confirm',\n}: InlineEditProps) => {\n const [isEditing, setEditing] = useState(isEditingProp ?? false);\n const [isFocusWithin, setFocusWithin] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n const editRef = useRef<HTMLButtonElement>(null);\n const controlled = isEditingProp !== undefined;\n\n useUpdateEffect(() => {\n if (controlled) {\n setEditing(isEditingProp);\n }\n }, [isEditingProp]);\n\n useUpdateEffect(() => {\n if (isFocusWithin) {\n isEditing\n ? inputRef.current && focusSafely(inputRef.current)\n : editRef.current && focusSafely(editRef.current);\n }\n }, [isEditing]);\n\n const handleEdit = () => {\n !controlled && setEditing(true);\n onEdit?.();\n };\n\n const handleCancel = () => {\n !controlled && setEditing(false);\n onCancel?.();\n };\n\n const handleConfirm = () => {\n onConfirm(inputRef.current?.value || '');\n !controlled && setEditing(false);\n };\n\n const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (event) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n handleConfirm();\n } else if (event.key === 'Escape') {\n event.preventDefault();\n handleCancel();\n }\n };\n\n const { focusWithinProps } = useFocusWithin({\n onBlurWithin: () => isEditing && handleCancel(),\n onFocusWithinChange: (isFocusWithin) => setFocusWithin(isFocusWithin),\n });\n\n const { buttonProps } = useButton(\n {\n 'aria-label': editButtonLabel,\n elementType: 'span',\n onPress: handleEdit,\n },\n editRef\n );\n\n const renderReadContent = hideEdit ? (\n <span ref={editRef} {...buttonProps} className={readButton}>\n {children}\n </span>\n ) : (\n <>\n {children}\n <IconButton\n ref={editRef}\n icon={<Icon name=\"edit\" />}\n aria-label={editButtonLabel}\n size=\"small\"\n onClick={handleEdit}\n />\n </>\n );\n\n const input = cloneElement(\n renderInput,\n mergeProps(renderInput.props, {\n ref: inputRef,\n defaultValue,\n onKeyDown: handleKeyDown,\n 'aria-label': ariaLabel,\n })\n );\n\n return isEditing ? (\n <div className={cx(container, inline({ layout }))} data-test-id={testId} {...focusWithinProps}>\n {input}\n <ButtonGroup spacing=\"compact\">\n <IconButton\n kind=\"primary\"\n icon={<Icon name=\"check\" />}\n aria-label={confirmButtonLabel}\n onClick={handleConfirm}\n />\n <IconButton\n kind=\"default\"\n icon={<Icon name=\"close\" />}\n aria-label={cancelButtonLabel}\n className={cancelButton}\n onClick={handleCancel}\n />\n </ButtonGroup>\n </div>\n ) : (\n <div className={cx(!hideEdit && container)} data-test-id={testId} {...focusWithinProps}>\n {renderReadContent}\n </div>\n );\n};\n\nexport { InlineEdit };\nexport type { InlineEditProps };\n"],"names":["TextField","useState","useRef","useUpdateEffect","focusSafely","useFocusWithin","isFocusWithin","useButton","jsx","jsxs","Fragment","IconButton","Icon","cloneElement","mergeProps","cx","ButtonGroup"],"mappings":"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"}
|
package/dist/style.css
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
._1oig0620 {
|
2
|
+
display: flex;
|
3
|
+
gap: var(--lp-spacing-300);
|
4
|
+
align-items: center;
|
5
|
+
}
|
6
|
+
._1oig0622 {
|
7
|
+
flex-direction: column;
|
8
|
+
align-items: flex-start;
|
9
|
+
}
|
10
|
+
._1oig0623 {
|
11
|
+
flex-direction: row;
|
12
|
+
}
|
13
|
+
.Button--icon._1oig0624 {
|
14
|
+
height: 3rem;
|
15
|
+
width: 3rem;
|
16
|
+
}
|
17
|
+
._1oig0625 {
|
18
|
+
display: inline-block;
|
19
|
+
padding: var(--lp-spacing-200) var(--lp-spacing-300);
|
20
|
+
border-radius: var(--lp-border-radius-regular);
|
21
|
+
}
|
22
|
+
._1oig0625:hover {
|
23
|
+
background: var(--lp-color-bg-interactive-tertiary-hover);
|
24
|
+
cursor: pointer;
|
25
|
+
}
|
26
|
+
._1oig0625:focus-visible {
|
27
|
+
border-radius: var(--lp-border-radius-medium);
|
28
|
+
box-shadow: 0 0 0 2px var(--lp-color-bg-ui-primary), 0 0 0 4px var(--lp-color-shadow-interactive-focus);
|
29
|
+
outline: 0;
|
30
|
+
z-index: 2;
|
31
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import type { RecipeVariants } from '@vanilla-extract/recipes';
|
2
|
+
declare const container: string;
|
3
|
+
declare const inline: import("@vanilla-extract/recipes").RuntimeFn<{
|
4
|
+
layout: {
|
5
|
+
vertical: {
|
6
|
+
flexDirection: "column";
|
7
|
+
alignItems: "flex-start";
|
8
|
+
};
|
9
|
+
horizontal: {
|
10
|
+
flexDirection: "row";
|
11
|
+
};
|
12
|
+
};
|
13
|
+
}>;
|
14
|
+
declare const cancelButton: string;
|
15
|
+
declare const readButton: string;
|
16
|
+
type InlineVariants = RecipeVariants<typeof inline>;
|
17
|
+
export { container, cancelButton, inline, readButton };
|
18
|
+
export type { InlineVariants };
|
19
|
+
//# sourceMappingURL=InlineEdit.css.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"InlineEdit.css.d.ts","sourceRoot":"","sources":["../../src/styles/InlineEdit.css.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAM/D,QAAA,MAAM,SAAS,QAIb,CAAC;AAEH,QAAA,MAAM,MAAM;;;;;;;;;;EAOV,CAAC;AAEH,QAAA,MAAM,YAAY,QAOhB,CAAC;AAEH,QAAA,MAAM,UAAU,QAcd,CAAC;AAEH,KAAK,cAAc,GAAG,cAAc,CAAC,OAAO,MAAM,CAAC,CAAC;AAEpD,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AACvD,YAAY,EAAE,cAAc,EAAE,CAAC"}
|
package/package.json
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
{
|
2
|
+
"name": "@launchpad-ui/inline-edit",
|
3
|
+
"version": "0.1.0",
|
4
|
+
"status": "alpha",
|
5
|
+
"publishConfig": {
|
6
|
+
"access": "public"
|
7
|
+
},
|
8
|
+
"description": "An element used to display and allow inline editing of a form element value.",
|
9
|
+
"files": [
|
10
|
+
"dist"
|
11
|
+
],
|
12
|
+
"main": "dist/index.js",
|
13
|
+
"module": "dist/index.es.js",
|
14
|
+
"types": "dist/index.d.ts",
|
15
|
+
"sideEffects": [
|
16
|
+
"**/*.css"
|
17
|
+
],
|
18
|
+
"exports": {
|
19
|
+
".": {
|
20
|
+
"types": "./dist/index.d.ts",
|
21
|
+
"import": "./dist/index.es.js",
|
22
|
+
"require": "./dist/index.js"
|
23
|
+
},
|
24
|
+
"./package.json": "./package.json",
|
25
|
+
"./style.css": "./dist/style.css"
|
26
|
+
},
|
27
|
+
"source": "src/index.ts",
|
28
|
+
"dependencies": {
|
29
|
+
"@react-aria/button": "3.8.0",
|
30
|
+
"@react-aria/focus": "3.13.0",
|
31
|
+
"@react-aria/interactions": "3.16.0",
|
32
|
+
"@react-aria/utils": "3.18.0",
|
33
|
+
"@vanilla-extract/recipes": "^0.5.0",
|
34
|
+
"classix": "2.1.17",
|
35
|
+
"@launchpad-ui/button": "~0.9.2",
|
36
|
+
"@launchpad-ui/form": "~0.9.2",
|
37
|
+
"@launchpad-ui/icons": "~0.9.2",
|
38
|
+
"@launchpad-ui/tokens": "~0.6.0",
|
39
|
+
"@launchpad-ui/vars": "~0.2.0"
|
40
|
+
},
|
41
|
+
"peerDependencies": {
|
42
|
+
"@vanilla-extract/css": "^1.12.0",
|
43
|
+
"react": "18.2.0",
|
44
|
+
"react-dom": "18.2.0"
|
45
|
+
},
|
46
|
+
"devDependencies": {
|
47
|
+
"react": "18.2.0",
|
48
|
+
"react-dom": "18.2.0"
|
49
|
+
},
|
50
|
+
"scripts": {
|
51
|
+
"build": "vite build -c ../../vite.config.ts && tsc --project tsconfig.build.json",
|
52
|
+
"clean": "rm -rf dist",
|
53
|
+
"lint": "eslint '**/*.{ts,tsx,js}'",
|
54
|
+
"test": "vitest run --coverage"
|
55
|
+
}
|
56
|
+
}
|