@varialkit/editabletitle 0.1.1

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/docs.md ADDED
@@ -0,0 +1,51 @@
1
+ # EditableTitle
2
+
3
+ EditableTitle provides inline editing for short text labels such as document titles. It supports async saves, keyboard
4
+ shortcuts, and optional read-only mode.
5
+
6
+ ## How to Use
7
+
8
+ ```tsx
9
+ import { EditableTitle } from "@solara/editabletitle";
10
+
11
+ export function Example() {
12
+ const handleSave = async (nextTitle: string) => {
13
+ await api.updateTitle(nextTitle);
14
+ return true;
15
+ };
16
+
17
+ return <EditableTitle title="Quarterly Report" onSave={handleSave} />;
18
+ }
19
+ ```
20
+
21
+ ## Best Practices
22
+
23
+ - Keep titles short so they remain scannable when not focused.
24
+ - Return `false` from `onSave` to rollback on errors.
25
+ - Use `readOnly` when the title should not be editable.
26
+
27
+ ## Props
28
+
29
+ | Prop | Type | Default | Description |
30
+ | --- | --- | --- | --- |
31
+ | `title` | `string` | _Required_ | Current title text. |
32
+ | `onSave` | `(newTitle: string) => Promise<boolean>` | _Required_ | Async save handler; return `true` to accept changes. |
33
+ | `readOnly` | `boolean` | `false` | Prevent editing when true. |
34
+ | `size` | `"xs" \| "sm" \| "base" \| "lg" \| "xl" \| "2xl" \| "3xl" \| "4xl" \| "5xl" \| "6xl"` | `"lg"` | Typography size. |
35
+ | `weight` | `"normal" \| "medium" \| "semibold" \| "bold"` | | Font weight. |
36
+ | `fullWidth` | `boolean` | `false` | When `true`, the component will take up the full width of its container. |
37
+ | `iconLeft` | `SolaraIconName \| IconProps` | | Optional leading icon before the title. |
38
+ | `className` | `string` | | Custom class name. |
39
+
40
+ ## Accessibility
41
+
42
+ - The button uses a clear focus ring for keyboard navigation.
43
+ - The input preserves the current title value and supports Enter/Escape actions.
44
+
45
+ ## Icons
46
+
47
+ You can render a leading icon before the title. Icons inherit the title text color.
48
+
49
+ ```tsx
50
+ <EditableTitle title="Project Alpha" onSave={handleSave} iconLeft="data_spreadsheet_search_24" />
51
+ ```
@@ -0,0 +1,249 @@
1
+ import React from "react";
2
+ import type { ReactElement } from "react";
3
+ import { iconNames } from "@solara/icons";
4
+ import type { SolaraIconName } from "@solara/icons";
5
+ import { EditableTitle } from "../src/EditableTitle";
6
+ import type { EditableTitleSize, EditableTitleWeight } from "../src/EditableTitle.types";
7
+
8
+ type StoryControlOption = {
9
+ label: string;
10
+ value: string;
11
+ };
12
+
13
+ type StoryControl = {
14
+ name: string;
15
+ label?: string;
16
+ type: "select" | "text" | "boolean" | "number";
17
+ options?: Array<string | StoryControlOption>;
18
+ min?: number;
19
+ max?: number;
20
+ step?: number;
21
+ };
22
+
23
+ type StoryDefinition = {
24
+ title: string;
25
+ description?: string;
26
+ render: (props: Record<string, unknown>) => ReactElement;
27
+ controls?: StoryControl[];
28
+ initialProps?: Record<string, unknown>;
29
+ showProps?: boolean;
30
+ applyPropsToPreview?: boolean;
31
+ code?: string;
32
+ };
33
+
34
+ const simulateSave = async (nextTitle: string) => {
35
+ await new Promise((resolve) => setTimeout(resolve, 350));
36
+ return Boolean(nextTitle);
37
+ };
38
+
39
+ export const stories: Record<string, StoryDefinition> = {
40
+ playground: {
41
+ title: "Playground",
42
+ description: "Edit the title inline and explore typography options.",
43
+ render: (props) => (
44
+ <EditableTitlePlayground
45
+ title={(props.title as string) ?? "Project Alpha"}
46
+ size={props.size as EditableTitleSize}
47
+ weight={props.weight as EditableTitleWeight}
48
+ readOnly={props.readOnly as boolean}
49
+ fullWidth={props.fullWidth as boolean}
50
+ disabled={props.disabled as boolean}
51
+ iconLeft={(props.iconLeft as SolaraIconName) || undefined}
52
+ />
53
+ ),
54
+ controls: [
55
+ { name: "title", label: "Title", type: "text" },
56
+ {
57
+ name: "size",
58
+ label: "Size",
59
+ type: "select",
60
+ options: ["xs", "sm", "base", "lg", "xl", "2xl", "3xl"],
61
+ },
62
+ {
63
+ name: "weight",
64
+ label: "Weight",
65
+ type: "select",
66
+ options: ["normal", "medium", "semibold", "bold"],
67
+ },
68
+ { name: "readOnly", label: "Read Only", type: "boolean" },
69
+ { name: "fullWidth", label: "Full Width", type: "boolean" },
70
+ { name: "disabled", label: "Disabled", type: "boolean" },
71
+ {
72
+ name: "iconLeft",
73
+ label: "Icon Left",
74
+ type: "select",
75
+ options: ["", ...iconNames],
76
+ },
77
+ ],
78
+ initialProps: {
79
+ title: "Project Alpha",
80
+ size: "lg",
81
+ weight: "semibold",
82
+ readOnly: false,
83
+ fullWidth: false,
84
+ disabled: false,
85
+ iconLeft: "",
86
+ },
87
+ },
88
+ sizes: {
89
+ title: "Sizes",
90
+ showProps: false,
91
+ render: () => (
92
+ <div style={{ display: "grid", gap: "0.75rem" }}>
93
+ <EditableTitle title="Heading XS" onSave={simulateSave} size="xs" />
94
+ <EditableTitle title="Heading SM" onSave={simulateSave} size="sm" />
95
+ <EditableTitle title="Heading Base" onSave={simulateSave} size="base" />
96
+ <EditableTitle title="Heading LG" onSave={simulateSave} size="lg" />
97
+ <EditableTitle title="Heading XL" onSave={simulateSave} size="xl" />
98
+ <EditableTitle title="Heading 2XL" onSave={simulateSave} size="2xl" />
99
+ </div>
100
+ ),
101
+ code: `import { EditableTitle } from "@solara/editabletitle";
102
+
103
+ const simulateSave = async (nextTitle: string) => {
104
+ await new Promise((resolve) => setTimeout(resolve, 350));
105
+ return Boolean(nextTitle);
106
+ };
107
+
108
+ export function Example() {
109
+ return (
110
+ <div style={{ display: "grid", gap: "0.75rem" }}>
111
+ <EditableTitle title="Heading XS" onSave={simulateSave} size="xs" />
112
+ <EditableTitle title="Heading SM" onSave={simulateSave} size="sm" />
113
+ <EditableTitle title="Heading Base" onSave={simulateSave} size="base" />
114
+ <EditableTitle title="Heading LG" onSave={simulateSave} size="lg" />
115
+ <EditableTitle title="Heading XL" onSave={simulateSave} size="xl" />
116
+ <EditableTitle title="Heading 2XL" onSave={simulateSave} size="2xl" />
117
+ </div>
118
+ );
119
+ }
120
+ `,
121
+ },
122
+ readonly: {
123
+ title: "Read Only",
124
+ showProps: false,
125
+ render: () => (
126
+ <EditableTitle
127
+ title="Read-only title"
128
+ onSave={simulateSave}
129
+ readOnly
130
+ weight="medium"
131
+ />
132
+ ),
133
+ code: `import { EditableTitle } from "@solara/editabletitle";
134
+
135
+ const simulateSave = async (nextTitle: string) => {
136
+ await new Promise((resolve) => setTimeout(resolve, 350));
137
+ return Boolean(nextTitle);
138
+ };
139
+
140
+ export function Example() {
141
+ return (
142
+ <EditableTitle
143
+ title="Read-only title"
144
+ onSave={simulateSave}
145
+ readOnly
146
+ weight="medium"
147
+ />
148
+ );
149
+ }
150
+ `,
151
+ },
152
+ icons: {
153
+ title: "Icons",
154
+ showProps: false,
155
+ render: () => (
156
+ <div style={{ display: "grid", gap: "0.75rem" }}>
157
+ <EditableTitle title="Design systems" onSave={simulateSave} iconLeft="data_spreadsheet_search_24" />
158
+ <EditableTitle title="Launch plan" onSave={simulateSave} iconLeft="arrow_line_up_16" />
159
+ </div>
160
+ ),
161
+ code: `import { EditableTitle } from "@solara/editabletitle";
162
+
163
+ const simulateSave = async (nextTitle: string) => {
164
+ await new Promise((resolve) => setTimeout(resolve, 350));
165
+ return Boolean(nextTitle);
166
+ };
167
+
168
+ export function Example() {
169
+ return (
170
+ <div style={{ display: "grid", gap: "0.75rem" }}>
171
+ <EditableTitle title="Design systems" onSave={simulateSave} iconLeft="data_spreadsheet_search_24" />
172
+ <EditableTitle title="Launch plan" onSave={simulateSave} iconLeft="arrow_line_up_16" />
173
+ </div>
174
+ );
175
+ }
176
+ `,
177
+ },
178
+ fullWidth: {
179
+ title: "Full Width",
180
+ showProps: false,
181
+ render: () => (
182
+ <div style={{ display: "grid", gap: "0.75rem", width: "400px" }}>
183
+ <EditableTitle title="This is a long title that should wrap" onSave={simulateSave} fullWidth />
184
+ </div>
185
+ ),
186
+ code: `import { EditableTitle } from "@solara/editabletitle";
187
+
188
+ const simulateSave = async (nextTitle: string) => {
189
+ await new Promise((resolve) => setTimeout(resolve, 350));
190
+ return Boolean(nextTitle);
191
+ };
192
+
193
+ export function Example() {
194
+ return (
195
+ <div style={{ display: "grid", gap: "0.75rem", width: "400px" }}>
196
+ <EditableTitle title="This is a long title that should wrap" onSave={simulateSave} fullWidth />
197
+ </div>
198
+ );
199
+ }
200
+ `,
201
+ },
202
+ };
203
+
204
+ type EditableTitlePlaygroundProps = {
205
+ title: string;
206
+ size?: EditableTitleSize;
207
+ weight?: EditableTitleWeight;
208
+ readOnly?: boolean;
209
+ fullWidth?: boolean;
210
+ disabled?: boolean;
211
+ iconLeft?: SolaraIconName;
212
+ };
213
+
214
+ const EditableTitlePlayground = ({
215
+ title,
216
+ size,
217
+ weight,
218
+ readOnly,
219
+ fullWidth,
220
+ disabled,
221
+ iconLeft,
222
+ }: EditableTitlePlaygroundProps) => {
223
+ const [currentTitle, setCurrentTitle] = React.useState(title);
224
+
225
+ React.useEffect(() => {
226
+ setCurrentTitle(title);
227
+ }, [title]);
228
+
229
+ const handleSave = async (nextTitle: string) => {
230
+ const ok = await simulateSave(nextTitle);
231
+ if (ok) {
232
+ setCurrentTitle(nextTitle);
233
+ }
234
+ return ok;
235
+ };
236
+
237
+ return (
238
+ <EditableTitle
239
+ title={currentTitle}
240
+ onSave={handleSave}
241
+ size={size}
242
+ weight={weight}
243
+ readOnly={readOnly}
244
+ fullWidth={fullWidth}
245
+ disabled={disabled}
246
+ iconLeft={iconLeft}
247
+ />
248
+ );
249
+ };
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@varialkit/editabletitle",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "main": "src/index.ts",
6
+ "types": "src/index.ts",
7
+ "exports": {
8
+ ".": "./src/index.ts",
9
+ "./examples": "./examples/index.tsx"
10
+ },
11
+ "dependencies": {
12
+ "@varialkit/icons": "0.1.1"
13
+ },
14
+ "files": [
15
+ "src",
16
+ "docs.md",
17
+ "examples",
18
+ "examples.tsx"
19
+ ],
20
+ "peerDependencies": {
21
+ "react": "^19.0.0"
22
+ },
23
+ "devDependencies": {
24
+ "@types/react": "19.0.10",
25
+ "react": "19.0.0"
26
+ }
27
+ }
@@ -0,0 +1,193 @@
1
+ .solara-editable-title {
2
+ --editable-title-font-size: var(--font-size-h5-scaled);
3
+ --editable-title-line-height: var(--line-height-heading-scaled);
4
+
5
+ position: relative;
6
+ display: inline-flex;
7
+ width: auto;
8
+ max-width: 100%;
9
+ color: var(--color-text-primary);
10
+ font-family: var(--font-body);
11
+ }
12
+
13
+ .solara-editable-title--full-width {
14
+ width: 100%;
15
+ }
16
+
17
+ .solara-editable-title__input,
18
+ .solara-editable-title__button {
19
+ font-size: var(--editable-title-font-size);
20
+ line-height: var(--editable-title-line-height);
21
+ }
22
+
23
+ .solara-editable-title__input {
24
+ background: transparent;
25
+ border: none;
26
+ outline: none;
27
+ padding: calc(var(--space-1) * var(--spacing-multiplier))
28
+ calc(var(--space-2) * var(--spacing-multiplier));
29
+ margin: calc(var(--space-1) * -1 * var(--spacing-multiplier))
30
+ calc(var(--space-2) * -1 * var(--spacing-multiplier));
31
+ width: calc(100% + (var(--space-2) * 2 * var(--spacing-multiplier)));
32
+ transition: color 0.2s ease, background-color 0.2s ease;
33
+ color: inherit;
34
+ font-family: inherit;
35
+ display: block;
36
+ border-radius: var(--radius-2);
37
+
38
+ &::placeholder {
39
+ color: var(--color-text-secondary);
40
+ }
41
+ }
42
+
43
+ .solara-editable-title__button {
44
+ background: none;
45
+ border: none;
46
+ cursor: pointer;
47
+ text-align: left;
48
+ padding: calc(var(--space-1) * var(--spacing-multiplier))
49
+ calc(var(--space-2) * var(--spacing-multiplier));
50
+ margin: calc(var(--space-1) * -1 * var(--spacing-multiplier))
51
+ calc(var(--space-2) * -1 * var(--spacing-multiplier));
52
+ border-radius: var(--radius-2);
53
+ transition: background-color 0.2s ease, color 0.2s ease;
54
+ width: 100%;
55
+ color: inherit;
56
+ font-family: inherit;
57
+ display: inline-flex;
58
+ align-items: center;
59
+ gap: calc(var(--space-2) * var(--spacing-multiplier));
60
+ white-space: nowrap;
61
+ overflow: hidden;
62
+ text-overflow: ellipsis;
63
+
64
+ &:hover:not(:disabled) {
65
+ background-color: var(--color-surface-100);
66
+ }
67
+
68
+ &:focus-visible {
69
+ outline: none;
70
+ box-shadow: 0 0 0 3px var(--color-focus-halo);
71
+ }
72
+
73
+ &:disabled {
74
+ cursor: default;
75
+ background-color: transparent;
76
+ }
77
+ }
78
+
79
+ .solara-editable-title__icon {
80
+ display: inline-flex;
81
+ align-items: center;
82
+ justify-content: center;
83
+ color: currentColor;
84
+ flex-shrink: 0;
85
+ }
86
+
87
+ .solara-editable-title__icon .solara-icon [stroke]:not([stroke="none"]) {
88
+ stroke: currentColor;
89
+ }
90
+
91
+ .solara-editable-title__icon .solara-icon [fill]:not([fill="none"]) {
92
+ fill: currentColor;
93
+ }
94
+
95
+ .solara-editable-title__label {
96
+ min-width: 0;
97
+ overflow: hidden;
98
+ text-overflow: ellipsis;
99
+ }
100
+
101
+
102
+
103
+ .solara-editable-title__spinner {
104
+ position: absolute;
105
+ right: calc(var(--space-2) * var(--spacing-multiplier));
106
+ top: 50%;
107
+ width: calc(var(--space-3) * var(--spacing-multiplier));
108
+ height: calc(var(--space-3) * var(--spacing-multiplier));
109
+ border-radius: 999px;
110
+ border: 2px solid var(--color-divider-secondary);
111
+ border-top-color: var(--color-accent-primary);
112
+ transform: translateY(-50%);
113
+ animation: solara-editable-title-spin 1s linear infinite;
114
+ }
115
+
116
+ .solara-editable-title--size-xs {
117
+ --editable-title-font-size: var(--font-size-footnote-scaled);
118
+ --editable-title-line-height: var(--line-height-footnote-scaled);
119
+ }
120
+
121
+ .solara-editable-title--size-sm {
122
+ --editable-title-font-size: var(--font-size-caption-scaled);
123
+ --editable-title-line-height: var(--line-height-caption-scaled);
124
+ }
125
+
126
+ .solara-editable-title--size-base {
127
+ --editable-title-font-size: var(--font-size-body-scaled);
128
+ --editable-title-line-height: var(--line-height-body-scaled);
129
+ }
130
+
131
+ .solara-editable-title--size-lg {
132
+ --editable-title-font-size: var(--font-size-subhead-scaled);
133
+ --editable-title-line-height: var(--line-height-body-scaled);
134
+ }
135
+
136
+ .solara-editable-title--size-xl {
137
+ --editable-title-font-size: var(--font-size-h5-scaled);
138
+ --editable-title-line-height: var(--line-height-heading-scaled);
139
+ }
140
+
141
+ .solara-editable-title--size-2xl {
142
+ --editable-title-font-size: var(--font-size-h4-scaled);
143
+ --editable-title-line-height: var(--line-height-heading-scaled);
144
+ }
145
+
146
+ .solara-editable-title--size-3xl {
147
+ --editable-title-font-size: var(--font-size-h3-scaled);
148
+ --editable-title-line-height: var(--line-height-heading-scaled);
149
+ }
150
+
151
+ .solara-editable-title--size-4xl {
152
+ --editable-title-font-size: var(--font-size-h2-scaled);
153
+ --editable-title-line-height: var(--line-height-heading-scaled);
154
+ }
155
+
156
+ .solara-editable-title--size-5xl {
157
+ --editable-title-font-size: var(--font-size-h1-scaled);
158
+ --editable-title-line-height: var(--line-height-heading-scaled);
159
+ }
160
+
161
+ .solara-editable-title--size-6xl {
162
+ --editable-title-font-size: calc(var(--font-size-h1-scaled) * 1.125);
163
+ --editable-title-line-height: var(--line-height-heading-scaled);
164
+ }
165
+
166
+ .solara-editable-title--weight-normal .solara-editable-title__input,
167
+ .solara-editable-title--weight-normal .solara-editable-title__button {
168
+ font-weight: 400;
169
+ }
170
+
171
+ .solara-editable-title--weight-medium .solara-editable-title__input,
172
+ .solara-editable-title--weight-medium .solara-editable-title__button {
173
+ font-weight: 500;
174
+ }
175
+
176
+ .solara-editable-title--weight-semibold .solara-editable-title__input,
177
+ .solara-editable-title--weight-semibold .solara-editable-title__button {
178
+ font-weight: 600;
179
+ }
180
+
181
+ .solara-editable-title--weight-bold .solara-editable-title__input,
182
+ .solara-editable-title--weight-bold .solara-editable-title__button {
183
+ font-weight: 700;
184
+ }
185
+
186
+ @keyframes solara-editable-title-spin {
187
+ from {
188
+ transform: translateY(-50%) rotate(0deg);
189
+ }
190
+ to {
191
+ transform: translateY(-50%) rotate(360deg);
192
+ }
193
+ }
@@ -0,0 +1,140 @@
1
+ import React, { forwardRef, useEffect, useRef, useState } from "react";
2
+ import { Icon } from "@solara/icons";
3
+ import type { IconProps } from "@solara/icons";
4
+ import type { EditableTitleProps } from "./EditableTitle.types";
5
+ import "./EditableTitle.scss";
6
+
7
+ type EditableTitleIcon = IconProps | IconProps["name"];
8
+
9
+ const normalizeIconProps = (icon: EditableTitleIcon): IconProps =>
10
+ typeof icon === "string" ? { name: icon } : icon;
11
+
12
+ const resolveIconProps = (icon: EditableTitleIcon): IconProps => {
13
+ const iconProps = normalizeIconProps(icon);
14
+
15
+ return {
16
+ ...iconProps,
17
+ style: {
18
+ ...iconProps.style,
19
+ color: "currentColor",
20
+ },
21
+ };
22
+ };
23
+
24
+ export const EditableTitle = forwardRef<HTMLButtonElement, EditableTitleProps>(
25
+ (
26
+ {
27
+ title,
28
+ onSave,
29
+ className,
30
+ readOnly = false,
31
+ size = "lg",
32
+ weight,
33
+ fullWidth = false,
34
+ disabled,
35
+ iconLeft,
36
+ ...buttonProps
37
+ },
38
+ ref
39
+ ) => {
40
+ const [isEditing, setIsEditing] = useState(false);
41
+ const [isSaving, setIsSaving] = useState(false);
42
+ const [value, setValue] = useState(title);
43
+ const inputRef = useRef<HTMLInputElement>(null);
44
+
45
+ const isLocked = readOnly || Boolean(disabled);
46
+
47
+ useEffect(() => {
48
+ setValue(title);
49
+ }, [title]);
50
+
51
+ useEffect(() => {
52
+ if (isEditing && inputRef.current) {
53
+ inputRef.current.select();
54
+ }
55
+ }, [isEditing]);
56
+
57
+ const handleSave = async () => {
58
+ if (isLocked || isSaving) return;
59
+
60
+ const trimmedValue = value.trim();
61
+ if (!trimmedValue || trimmedValue === title) {
62
+ setValue(title);
63
+ setIsEditing(false);
64
+ return;
65
+ }
66
+
67
+ setIsSaving(true);
68
+ const success = await onSave(trimmedValue);
69
+ setIsSaving(false);
70
+
71
+ if (success) {
72
+ setIsEditing(false);
73
+ } else {
74
+ setValue(title);
75
+ }
76
+ };
77
+
78
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
79
+ if (event.key === "Enter") {
80
+ handleSave();
81
+ } else if (event.key === "Escape") {
82
+ setValue(title);
83
+ setIsEditing(false);
84
+ }
85
+ };
86
+
87
+ const rootClasses = [
88
+ "solara-editable-title",
89
+ size ? `solara-editable-title--size-${size}` : null,
90
+ weight ? `solara-editable-title--weight-${weight}` : null,
91
+ fullWidth ? "solara-editable-title--full-width" : null,
92
+ className,
93
+ ]
94
+ .filter(Boolean)
95
+ .join(" ");
96
+
97
+ if (isEditing) {
98
+ return (
99
+ <div className={rootClasses} aria-busy={isSaving}>
100
+ <input
101
+ ref={inputRef}
102
+ type="text"
103
+ value={value}
104
+ onChange={(event) => setValue(event.target.value)}
105
+ onKeyDown={handleKeyDown}
106
+ onBlur={handleSave}
107
+ className="solara-editable-title__input"
108
+ disabled={isSaving}
109
+ autoFocus
110
+ />
111
+ {isSaving ? (
112
+ <span className="solara-editable-title__spinner" aria-hidden="true" />
113
+ ) : null}
114
+ </div>
115
+ );
116
+ }
117
+
118
+ return (
119
+ <div className={rootClasses}>
120
+ <button
121
+ ref={ref}
122
+ type={buttonProps.type ?? "button"}
123
+ onClick={isLocked ? undefined : () => setIsEditing(true)}
124
+ className="solara-editable-title__button"
125
+ title={isLocked ? title : "Click to edit"}
126
+ disabled={isLocked}
127
+ {...buttonProps}>
128
+ {iconLeft ? (
129
+ <span className="solara-editable-title__icon" aria-hidden="true">
130
+ <Icon {...resolveIconProps(iconLeft)} />
131
+ </span>
132
+ ) : null}
133
+ <span className="solara-editable-title__label">{title}</span>
134
+ </button>
135
+ </div>
136
+ );
137
+ }
138
+ );
139
+
140
+ EditableTitle.displayName = "EditableTitle";
@@ -0,0 +1,36 @@
1
+ import type React from "react";
2
+ import type { IconProps } from "@solara/icons";
3
+
4
+ export type EditableTitleSize =
5
+ | "xs"
6
+ | "sm"
7
+ | "base"
8
+ | "lg"
9
+ | "xl"
10
+ | "2xl"
11
+ | "3xl"
12
+ | "4xl"
13
+ | "5xl"
14
+ | "6xl";
15
+
16
+ export type EditableTitleWeight = "normal" | "medium" | "semibold" | "bold";
17
+
18
+ export interface EditableTitleProps
19
+ extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "title" | "onClick"> {
20
+ /** The title text displayed in view mode. */
21
+ title: string;
22
+ /** Called when the title is saved. Return true to accept the change. */
23
+ onSave: (newTitle: string) => Promise<boolean>;
24
+ /** Prevent editing when true. */
25
+ readOnly?: boolean;
26
+ /** Typography size token. */
27
+ size?: EditableTitleSize;
28
+ /** Typography weight token. */
29
+ weight?: EditableTitleWeight;
30
+ /** When true, the component will take up the full width of its container. */
31
+ fullWidth?: boolean;
32
+ /** Optional leading icon displayed before the title. */
33
+ iconLeft?: IconProps | IconProps["name"];
34
+ /** Additional class name. */
35
+ className?: string;
36
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export { EditableTitle } from "./EditableTitle";
2
+ export type {
3
+ EditableTitleProps,
4
+ EditableTitleSize,
5
+ EditableTitleWeight,
6
+ } from "./EditableTitle.types";