@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 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
+ [![See it on NPM!](https://img.shields.io/npm/v/@launchpad-ui/inline-edit?style=for-the-badge)](https://www.npmjs.com/package/@launchpad-ui/inline-edit)
6
+ [![How big is this package in your project?](https://img.shields.io/bundlephobia/minzip/@launchpad-ui/inline-edit?style=for-the-badge)](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"}
@@ -0,0 +1,3 @@
1
+ export type { InlineEditProps } from './InlineEdit';
2
+ export { InlineEdit } from './InlineEdit';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -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":"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"}
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
+ }