@wallarm-org/design-system 0.71.5 → 0.72.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/README.md +1 -3
- package/dist/components/Attribute/AttributeValue.js +1 -1
- package/dist/components/Calendar/Calendar.js +14 -4
- package/dist/components/Calendar/CalendarContext.d.ts +19 -2
- package/dist/components/Calendar/CalendarHeader.js +2 -1
- package/dist/components/Calendar/CalendarInputHeader.js +27 -27
- package/dist/components/Calendar/dateValue.d.ts +18 -0
- package/dist/components/Calendar/dateValue.js +7 -0
- package/dist/components/Calendar/useCalendarTime.d.ts +25 -0
- package/dist/components/Calendar/useCalendarTime.js +38 -0
- package/dist/components/DropdownMenu/DropdownMenuInput.d.ts +1 -1
- package/dist/components/InlineEdit/InlineEdit.d.ts +52 -0
- package/dist/components/InlineEdit/InlineEdit.js +223 -0
- package/dist/components/InlineEdit/InlineEditContext.d.ts +43 -0
- package/dist/components/InlineEdit/InlineEditContext.js +16 -0
- package/dist/components/InlineEdit/InlineEditControl.d.ts +14 -0
- package/dist/components/InlineEdit/InlineEditControl.js +72 -0
- package/dist/components/InlineEdit/InlineEditDate.d.ts +19 -0
- package/dist/components/InlineEdit/InlineEditDate.js +22 -0
- package/dist/components/InlineEdit/InlineEditDateTime.d.ts +21 -0
- package/dist/components/InlineEdit/InlineEditDateTime.js +23 -0
- package/dist/components/InlineEdit/InlineEditError.d.ts +6 -0
- package/dist/components/InlineEdit/InlineEditError.js +32 -0
- package/dist/components/InlineEdit/InlineEditInput.d.ts +4 -0
- package/dist/components/InlineEdit/InlineEditInput.js +18 -0
- package/dist/components/InlineEdit/InlineEditNumber.d.ts +11 -0
- package/dist/components/InlineEdit/InlineEditNumber.js +18 -0
- package/dist/components/InlineEdit/InlineEditPreview.d.ts +23 -0
- package/dist/components/InlineEdit/InlineEditPreview.js +105 -0
- package/dist/components/InlineEdit/InlineEditPreviewIcon.d.ts +12 -0
- package/dist/components/InlineEdit/InlineEditPreviewIcon.js +43 -0
- package/dist/components/InlineEdit/InlineEditPreviewValue.d.ts +11 -0
- package/dist/components/InlineEdit/InlineEditPreviewValue.js +25 -0
- package/dist/components/InlineEdit/InlineEditSelect.d.ts +39 -0
- package/dist/components/InlineEdit/InlineEditSelect.js +42 -0
- package/dist/components/InlineEdit/InlineEditTextarea.d.ts +4 -0
- package/dist/components/InlineEdit/InlineEditTextarea.js +18 -0
- package/dist/components/InlineEdit/InlineEditTime.d.ts +12 -0
- package/dist/components/InlineEdit/InlineEditTime.js +21 -0
- package/dist/components/InlineEdit/classes.d.ts +5 -0
- package/dist/components/InlineEdit/classes.js +35 -0
- package/dist/components/InlineEdit/dateValueCast.d.ts +25 -0
- package/dist/components/InlineEdit/dateValueCast.js +9 -0
- package/dist/components/InlineEdit/index.d.ts +15 -0
- package/dist/components/InlineEdit/index.js +16 -0
- package/dist/components/Input/Input.d.ts +1 -1
- package/dist/components/Input/Input.js +4 -3
- package/dist/components/Input/classes.d.ts +1 -0
- package/dist/components/Input/classes.js +8 -0
- package/dist/components/NumberInput/NumberInput.d.ts +4 -1
- package/dist/components/NumberInput/NumberInput.js +17 -7
- package/dist/components/NumberInput/classes.d.ts +12 -0
- package/dist/components/NumberInput/classes.js +51 -0
- package/dist/components/Select/SelectButton.d.ts +2 -1
- package/dist/components/Select/SelectButton.js +7 -2
- package/dist/components/Select/SelectInput/SelectInput.d.ts +1 -0
- package/dist/components/Select/SelectInput/SelectInput.js +21 -7
- package/dist/components/Select/SelectInput/SelectInputItemRenderer.d.ts +4 -0
- package/dist/components/Select/SelectInput/SelectInputItemRenderer.js +8 -3
- package/dist/components/Select/SelectInput/SelectInputOverflowRenderer.d.ts +1 -1
- package/dist/components/Select/SelectInput/SelectInputOverflowRenderer.js +28 -27
- package/dist/components/Slider/SliderInput.d.ts +1 -1
- package/dist/components/Textarea/classes.js +2 -2
- package/dist/components/Tree/TreeContext.d.ts +1 -9
- package/dist/components/Tree/TreeContext.js +2 -18
- package/dist/components/Tree/TreeItemContent.js +5 -8
- package/dist/components/TreeView/TreeView.d.ts +19 -0
- package/dist/components/TreeView/TreeView.js +82 -0
- package/dist/components/TreeView/TreeViewContext.d.ts +12 -0
- package/dist/components/TreeView/TreeViewContext.js +9 -0
- package/dist/components/TreeView/TreeViewItem.d.ts +24 -0
- package/dist/components/TreeView/TreeViewItem.js +168 -0
- package/dist/components/TreeView/classes.d.ts +5 -0
- package/dist/components/TreeView/classes.js +30 -0
- package/dist/components/TreeView/index.d.ts +9 -0
- package/dist/components/TreeView/index.js +5 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.js +4 -2
- package/dist/metadata/components.json +6122 -2828
- package/dist/theme/fonts/Geist-Variable.woff2 +0 -0
- package/dist/theme/fonts/GeistMono-Variable.woff2 +0 -0
- package/dist/theme/fonts/geist.css +21 -0
- package/dist/theme/index.css +1 -0
- package/dist/theme/typography.css +5 -3
- package/dist/utils/Collapse.d.ts +18 -0
- package/dist/utils/Collapse.js +15 -0
- package/dist/utils/treeDepth.d.ts +11 -0
- package/dist/utils/treeDepth.js +18 -0
- package/package.json +1 -3
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FC, HTMLAttributes, ReactNode, Ref } from 'react';
|
|
2
|
+
import { type InlineEditContextValue, type InlineEditSubmitMode } from './InlineEditContext';
|
|
3
|
+
export interface InlineEditControlProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
4
|
+
ref?: Ref<HTMLDivElement>;
|
|
5
|
+
/**
|
|
6
|
+
* Explicit render-time submit mode for this editor session. Highest
|
|
7
|
+
* precedence (beats editor registrations and the root prop) — the escape
|
|
8
|
+
* hatch for custom editors that cannot call useInlineEditSubmitMode.
|
|
9
|
+
*/
|
|
10
|
+
submitMode?: InlineEditSubmitMode;
|
|
11
|
+
/** Function children receive the inline-edit context (render-prop). */
|
|
12
|
+
children?: ReactNode | ((ctx: InlineEditContextValue) => ReactNode);
|
|
13
|
+
}
|
|
14
|
+
export declare const InlineEditControl: FC<InlineEditControlProps>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
3
|
+
import { cn } from "../../utils/cn.js";
|
|
4
|
+
import { useTestId } from "../../utils/testId.js";
|
|
5
|
+
import { useInlineEdit } from "./InlineEditContext.js";
|
|
6
|
+
const FOCUSABLE_SELECTOR = 'input, textarea, select, button, [tabindex]:not([tabindex="-1"])';
|
|
7
|
+
const InlineEditControl = ({ ref, children, className, onKeyDown, onBlur, submitMode: submitModeProp, ...props })=>{
|
|
8
|
+
const testId = useTestId('control');
|
|
9
|
+
const ctx = useInlineEdit();
|
|
10
|
+
const { editing, selectOnFocus, submit, cancel, isCommitPending } = ctx;
|
|
11
|
+
const submitMode = submitModeProp ?? ctx.submitMode;
|
|
12
|
+
const divRef = useRef(null);
|
|
13
|
+
const selectOnFocusRef = useRef(selectOnFocus);
|
|
14
|
+
selectOnFocusRef.current = selectOnFocus;
|
|
15
|
+
useEffect(()=>{
|
|
16
|
+
if (!editing) return;
|
|
17
|
+
const node = divRef.current;
|
|
18
|
+
if (!node) return;
|
|
19
|
+
const focusable = node.querySelector(FOCUSABLE_SELECTOR);
|
|
20
|
+
if (!focusable) return;
|
|
21
|
+
focusable.focus();
|
|
22
|
+
if (selectOnFocusRef.current && (focusable instanceof HTMLInputElement || focusable instanceof HTMLTextAreaElement)) focusable.select();
|
|
23
|
+
}, [
|
|
24
|
+
editing
|
|
25
|
+
]);
|
|
26
|
+
const combinedRef = useCallback((node)=>{
|
|
27
|
+
divRef.current = node;
|
|
28
|
+
if ('function' == typeof ref) ref(node);
|
|
29
|
+
else if (ref) ref.current = node;
|
|
30
|
+
}, [
|
|
31
|
+
ref
|
|
32
|
+
]);
|
|
33
|
+
if (!editing) return null;
|
|
34
|
+
const submitsOnEnter = 'enter' === submitMode || 'both' === submitMode;
|
|
35
|
+
const submitsOnBlur = 'blur' === submitMode || 'both' === submitMode;
|
|
36
|
+
const handleKeyDown = (event)=>{
|
|
37
|
+
onKeyDown?.(event);
|
|
38
|
+
if (event.defaultPrevented) return;
|
|
39
|
+
if ('Escape' === event.key) {
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
cancel();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if ('Enter' === event.key && submitsOnEnter) {
|
|
45
|
+
const target = event.target;
|
|
46
|
+
const isTextarea = 'TEXTAREA' === target.tagName;
|
|
47
|
+
if (isTextarea && !event.metaKey && !event.ctrlKey) return;
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
submit();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const handleBlur = (event)=>{
|
|
53
|
+
onBlur?.(event);
|
|
54
|
+
if (event.defaultPrevented) return;
|
|
55
|
+
if (event.currentTarget.contains(event.relatedTarget)) return;
|
|
56
|
+
if (isCommitPending()) return;
|
|
57
|
+
if (submitsOnBlur) submit();
|
|
58
|
+
else if ('none' !== submitMode) cancel();
|
|
59
|
+
};
|
|
60
|
+
return /*#__PURE__*/ jsx("div", {
|
|
61
|
+
...props,
|
|
62
|
+
ref: combinedRef,
|
|
63
|
+
"data-testid": testId,
|
|
64
|
+
"data-slot": "inline-edit-control",
|
|
65
|
+
onKeyDown: handleKeyDown,
|
|
66
|
+
onBlur: handleBlur,
|
|
67
|
+
className: cn('w-full min-w-0', '-ml-7', '[&_[data-slot=input]]:px-6', '[&_textarea]:px-6', '[&_[data-scope=number-input][data-part=input]]:px-6', '[&_[data-scope=select][data-part=trigger]]:px-6', '[&_div[data-scope=select][data-part=trigger]:has([data-slot=overflow-list])]:pl-0', '[&_[data-slot=input-group-addon]]:px-6', '[&_[data-slot=input-group]>div:not([data-slot])]:pl-0', className),
|
|
68
|
+
children: 'function' == typeof children ? children(ctx) : children
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
InlineEditControl.displayName = 'InlineEditControl';
|
|
72
|
+
export { InlineEditControl };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FC, ReactNode } from 'react';
|
|
2
|
+
export interface InlineEditDateProps {
|
|
3
|
+
/**
|
|
4
|
+
* `InlineEditDate` IS the prewired `Calendar` root (day-only:
|
|
5
|
+
* `closeOnSelect`) — the `DateValue[]` adapter and commit-on-close live
|
|
6
|
+
* here. `children` are ordinary `Calendar` compound parts
|
|
7
|
+
* (`CalendarTrigger > DateInput`, `CalendarContent > CalendarBody >
|
|
8
|
+
* CalendarGrids`) — exactly as you'd compose a standalone `Calendar`. Use
|
|
9
|
+
* `InlineEditDateTime` for date+time. Wire your own `DateInput`'s
|
|
10
|
+
* `value`/`onChange` via `useInlineEdit()` plus the `toReactAriaDateValue`/
|
|
11
|
+
* `toCalendarDateValue` casts from `./dateValueCast` (the two
|
|
12
|
+
* `@internationalized/date` package copies aren't `instanceof`-compatible).
|
|
13
|
+
* `Calendar` does not re-provide its own testid cascade, so a `DateInput`
|
|
14
|
+
* you place inside already resolves `useTestId(...)` from the ambient
|
|
15
|
+
* `InlineEdit` root — no extra wiring needed.
|
|
16
|
+
*/
|
|
17
|
+
children: ReactNode;
|
|
18
|
+
}
|
|
19
|
+
export declare const InlineEditDate: FC<InlineEditDateProps>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Calendar } from "../Calendar/index.js";
|
|
3
|
+
import { useInlineEdit, useInlineEditSubmitMode } from "./InlineEditContext.js";
|
|
4
|
+
const InlineEditDate = ({ children })=>{
|
|
5
|
+
const { value, setValue, submit } = useInlineEdit();
|
|
6
|
+
useInlineEditSubmitMode('none');
|
|
7
|
+
return /*#__PURE__*/ jsx(Calendar, {
|
|
8
|
+
type: "single",
|
|
9
|
+
defaultOpen: true,
|
|
10
|
+
closeOnSelect: true,
|
|
11
|
+
value: value ? [
|
|
12
|
+
value
|
|
13
|
+
] : [],
|
|
14
|
+
onChange: (next)=>setValue(next[0] ?? null),
|
|
15
|
+
onOpenChange: (open)=>{
|
|
16
|
+
if (!open) submit();
|
|
17
|
+
},
|
|
18
|
+
children: children
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
InlineEditDate.displayName = 'InlineEditDate';
|
|
22
|
+
export { InlineEditDate };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FC, ReactNode } from 'react';
|
|
2
|
+
export interface InlineEditDateTimeProps {
|
|
3
|
+
/**
|
|
4
|
+
* `InlineEditDateTime` IS the prewired `Calendar` root (date+time:
|
|
5
|
+
* `showTime`, `closeOnSelect={false}`) — the `DateValue[]` adapter and
|
|
6
|
+
* commit-on-close live here. `children` are ordinary `Calendar` compound
|
|
7
|
+
* parts (`CalendarTrigger > DateInput`, `CalendarContent > CalendarBody >
|
|
8
|
+
* CalendarInputHeader > CalendarGrids`) — exactly as you'd compose a
|
|
9
|
+
* standalone `Calendar`. Use `InlineEditDate` for day-only. Wire your own
|
|
10
|
+
* `DateInput`'s `value`/`onChange` via `useInlineEdit()` plus the
|
|
11
|
+
* `toReactAriaDateValue`/`toCalendarDateValue`/`withMinuteGranularity`
|
|
12
|
+
* helpers from `./dateValueCast` (minute-granularity `DateInput` needs a
|
|
13
|
+
* promoted day-only value, and the two `@internationalized/date` package
|
|
14
|
+
* copies aren't `instanceof`-compatible). `Calendar` does not re-provide
|
|
15
|
+
* its own testid cascade, so a `DateInput` you place inside already
|
|
16
|
+
* resolves `useTestId(...)` from the ambient `InlineEdit` root — no extra
|
|
17
|
+
* wiring needed.
|
|
18
|
+
*/
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
export declare const InlineEditDateTime: FC<InlineEditDateTimeProps>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Calendar } from "../Calendar/index.js";
|
|
3
|
+
import { useInlineEdit, useInlineEditSubmitMode } from "./InlineEditContext.js";
|
|
4
|
+
const InlineEditDateTime = ({ children })=>{
|
|
5
|
+
const { value, setValue, submit } = useInlineEdit();
|
|
6
|
+
useInlineEditSubmitMode('none');
|
|
7
|
+
return /*#__PURE__*/ jsx(Calendar, {
|
|
8
|
+
type: "single",
|
|
9
|
+
defaultOpen: true,
|
|
10
|
+
showTime: true,
|
|
11
|
+
closeOnSelect: false,
|
|
12
|
+
value: value ? [
|
|
13
|
+
value
|
|
14
|
+
] : [],
|
|
15
|
+
onChange: (next)=>setValue(next[0] ?? null),
|
|
16
|
+
onOpenChange: (open)=>{
|
|
17
|
+
if (!open) submit();
|
|
18
|
+
},
|
|
19
|
+
children: children
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
InlineEditDateTime.displayName = 'InlineEditDateTime';
|
|
23
|
+
export { InlineEditDateTime };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FC, HTMLAttributes, ReactNode, Ref } from 'react';
|
|
2
|
+
export interface InlineEditErrorProps extends Omit<HTMLAttributes<HTMLDivElement>, 'color'> {
|
|
3
|
+
ref?: Ref<HTMLDivElement>;
|
|
4
|
+
children?: ReactNode;
|
|
5
|
+
}
|
|
6
|
+
export declare const InlineEditError: FC<InlineEditErrorProps>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { OctagonAlert } from "../../icons/index.js";
|
|
3
|
+
import { cn } from "../../utils/cn.js";
|
|
4
|
+
import { useTestId } from "../../utils/testId.js";
|
|
5
|
+
import { Text } from "../Text/index.js";
|
|
6
|
+
import { useInlineEdit } from "./InlineEditContext.js";
|
|
7
|
+
const InlineEditError = ({ ref, children, className, ...props })=>{
|
|
8
|
+
const testId = useTestId('error');
|
|
9
|
+
const { invalid, error } = useInlineEdit();
|
|
10
|
+
const message = children ?? error;
|
|
11
|
+
if (!invalid || !message) return null;
|
|
12
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
13
|
+
...props,
|
|
14
|
+
ref: ref,
|
|
15
|
+
"data-testid": testId,
|
|
16
|
+
"data-slot": "inline-edit-error",
|
|
17
|
+
className: cn('flex items-center gap-4 pt-4', className),
|
|
18
|
+
children: [
|
|
19
|
+
/*#__PURE__*/ jsx(OctagonAlert, {
|
|
20
|
+
size: "md",
|
|
21
|
+
className: "shrink-0 text-icon-danger"
|
|
22
|
+
}),
|
|
23
|
+
/*#__PURE__*/ jsx(Text, {
|
|
24
|
+
size: "sm",
|
|
25
|
+
color: "danger",
|
|
26
|
+
children: message
|
|
27
|
+
})
|
|
28
|
+
]
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
InlineEditError.displayName = 'InlineEditError';
|
|
32
|
+
export { InlineEditError };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useTestId } from "../../utils/testId.js";
|
|
3
|
+
import { Input } from "../Input/index.js";
|
|
4
|
+
import { useInlineEdit } from "./InlineEditContext.js";
|
|
5
|
+
const InlineEditInput = ({ size = 'small', 'data-testid': testIdProp, ...props })=>{
|
|
6
|
+
const testId = useTestId('input', testIdProp);
|
|
7
|
+
const { value, setValue, invalid } = useInlineEdit();
|
|
8
|
+
return /*#__PURE__*/ jsx(Input, {
|
|
9
|
+
...props,
|
|
10
|
+
"data-testid": testId,
|
|
11
|
+
value: value ?? '',
|
|
12
|
+
onChange: (event)=>setValue(event.target.value),
|
|
13
|
+
error: invalid,
|
|
14
|
+
size: size
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
InlineEditInput.displayName = 'InlineEditInput';
|
|
18
|
+
export { InlineEditInput };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import { type NumberInputProps } from '../NumberInput';
|
|
3
|
+
export type InlineEditNumberProps = Omit<NumberInputProps, 'value' | 'onValueChange' | 'error'>;
|
|
4
|
+
/**
|
|
5
|
+
* Analytics closed-target gap: `NumberInput` spreads consumer props onto its
|
|
6
|
+
* Ark `Root` wrapper rather than the inner `<input>`, so `data-analytics-*`
|
|
7
|
+
* land on the wrapper (click analytics still resolve via `closest()`, but the
|
|
8
|
+
* attribute is not on the real focusable node). The durable fix is to forward
|
|
9
|
+
* those attributes to the input inside `NumberInput`. See ./ANALYTICS_GAPS.md.
|
|
10
|
+
*/
|
|
11
|
+
export declare const InlineEditNumber: FC<InlineEditNumberProps>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useTestId } from "../../utils/testId.js";
|
|
3
|
+
import { NumberInput } from "../NumberInput/index.js";
|
|
4
|
+
import { useInlineEdit } from "./InlineEditContext.js";
|
|
5
|
+
const InlineEditNumber = ({ size = 'small', 'data-testid': testIdProp, ...props })=>{
|
|
6
|
+
const testId = useTestId('input', testIdProp);
|
|
7
|
+
const { value, setValue, invalid } = useInlineEdit();
|
|
8
|
+
return /*#__PURE__*/ jsx(NumberInput, {
|
|
9
|
+
...props,
|
|
10
|
+
"data-testid": testId,
|
|
11
|
+
value: value ?? '',
|
|
12
|
+
onValueChange: (details)=>setValue(details.value),
|
|
13
|
+
error: invalid,
|
|
14
|
+
size: size
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
InlineEditNumber.displayName = 'InlineEditNumber';
|
|
18
|
+
export { InlineEditNumber };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { FC, HTMLAttributes, ReactNode, Ref } from 'react';
|
|
2
|
+
export interface InlineEditPreviewProps extends HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
ref?: Ref<HTMLDivElement>;
|
|
4
|
+
/**
|
|
5
|
+
* Tooltip shown on hover/focus while the value is editable. Defaults to
|
|
6
|
+
* `'Edit'`. Pass `null` to disable the tooltip.
|
|
7
|
+
*/
|
|
8
|
+
tooltip?: ReactNode;
|
|
9
|
+
/**
|
|
10
|
+
* Clamp the read-mode value to this many lines (with an ellipsis) instead of
|
|
11
|
+
* the default single-line truncation. Use for multi-line values (textarea).
|
|
12
|
+
* Applies to the default (no-parts) composition; when composing
|
|
13
|
+
* `InlineEditPreviewValue` explicitly, set `lineClamp` on it directly.
|
|
14
|
+
*/
|
|
15
|
+
lineClamp?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
16
|
+
/**
|
|
17
|
+
* Plain content auto-wraps into `InlineEditPreviewValue` + a default
|
|
18
|
+
* `InlineEditPreviewIcon` (pencil). Compose `InlineEditPreviewValue`/
|
|
19
|
+
* `InlineEditPreviewIcon` explicitly for a custom trailing icon.
|
|
20
|
+
*/
|
|
21
|
+
children?: ReactNode;
|
|
22
|
+
}
|
|
23
|
+
export declare const InlineEditPreview: FC<InlineEditPreviewProps>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Children, isValidElement, useRef } from "react";
|
|
3
|
+
import { cn } from "../../utils/cn.js";
|
|
4
|
+
import { useTestId } from "../../utils/testId.js";
|
|
5
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "../Tooltip/index.js";
|
|
6
|
+
import { inlineEditPreviewVariants } from "./classes.js";
|
|
7
|
+
import { useInlineEdit } from "./InlineEditContext.js";
|
|
8
|
+
import { InlineEditPreviewIcon } from "./InlineEditPreviewIcon.js";
|
|
9
|
+
import { InlineEditPreviewValue } from "./InlineEditPreviewValue.js";
|
|
10
|
+
const isInlineEditPreviewValue = (child)=>/*#__PURE__*/ isValidElement(child) && child.type?.displayName === InlineEditPreviewValue.displayName;
|
|
11
|
+
const isInlineEditPreviewIcon = (child)=>/*#__PURE__*/ isValidElement(child) && child.type?.displayName === InlineEditPreviewIcon.displayName;
|
|
12
|
+
const InlineEditPreview = ({ ref, tooltip = 'Edit', lineClamp, children, className, onClick, onFocus, onKeyDown, onPointerMove, ...props })=>{
|
|
13
|
+
const testId = useTestId('preview');
|
|
14
|
+
const { editing, status, invalid, disabled, readOnly, activationMode, edit } = useInlineEdit();
|
|
15
|
+
const pointerRef = useRef({
|
|
16
|
+
x: 0,
|
|
17
|
+
y: 0
|
|
18
|
+
});
|
|
19
|
+
if (editing) return null;
|
|
20
|
+
const isLoading = 'loading' === status;
|
|
21
|
+
const activatable = !disabled && !readOnly && !isLoading && 'none' !== activationMode;
|
|
22
|
+
const childArray = Children.toArray(children);
|
|
23
|
+
const valueChild = childArray.find(isInlineEditPreviewValue);
|
|
24
|
+
const hasExplicitParts = childArray.some((child)=>isInlineEditPreviewValue(child) || isInlineEditPreviewIcon(child));
|
|
25
|
+
const effectiveLineClamp = hasExplicitParts ? valueChild?.props.lineClamp : lineClamp;
|
|
26
|
+
const handleClick = (event)=>{
|
|
27
|
+
onClick?.(event);
|
|
28
|
+
if (event.defaultPrevented) return;
|
|
29
|
+
if (activatable && 'click' === activationMode) edit();
|
|
30
|
+
};
|
|
31
|
+
const handleFocus = (event)=>{
|
|
32
|
+
onFocus?.(event);
|
|
33
|
+
if (event.defaultPrevented) return;
|
|
34
|
+
if (activatable && 'focus' === activationMode) edit();
|
|
35
|
+
};
|
|
36
|
+
const handleKeyDown = (event)=>{
|
|
37
|
+
onKeyDown?.(event);
|
|
38
|
+
if (event.defaultPrevented) return;
|
|
39
|
+
if (activatable && ('Enter' === event.key || ' ' === event.key)) {
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
edit();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const handlePointerMove = (event)=>{
|
|
45
|
+
onPointerMove?.(event);
|
|
46
|
+
pointerRef.current = {
|
|
47
|
+
x: event.clientX,
|
|
48
|
+
y: event.clientY
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
const tooltipPositioning = {
|
|
52
|
+
placement: 'bottom-start',
|
|
53
|
+
offset: {
|
|
54
|
+
mainAxis: 24,
|
|
55
|
+
crossAxis: 0
|
|
56
|
+
},
|
|
57
|
+
getAnchorRect: ()=>({
|
|
58
|
+
x: pointerRef.current.x,
|
|
59
|
+
y: pointerRef.current.y,
|
|
60
|
+
width: 0,
|
|
61
|
+
height: 0
|
|
62
|
+
})
|
|
63
|
+
};
|
|
64
|
+
const target = /*#__PURE__*/ jsx("div", {
|
|
65
|
+
...props,
|
|
66
|
+
ref: ref,
|
|
67
|
+
"data-testid": testId,
|
|
68
|
+
"data-slot": "inline-edit-preview",
|
|
69
|
+
role: activatable ? 'button' : void 0,
|
|
70
|
+
tabIndex: activatable ? 0 : void 0,
|
|
71
|
+
onClick: handleClick,
|
|
72
|
+
onFocus: handleFocus,
|
|
73
|
+
onKeyDown: handleKeyDown,
|
|
74
|
+
onPointerMove: handlePointerMove,
|
|
75
|
+
className: cn(inlineEditPreviewVariants({
|
|
76
|
+
multiline: Boolean(effectiveLineClamp),
|
|
77
|
+
activatable,
|
|
78
|
+
invalid: activatable && invalid
|
|
79
|
+
}), className),
|
|
80
|
+
children: hasExplicitParts ? children : /*#__PURE__*/ jsxs(Fragment, {
|
|
81
|
+
children: [
|
|
82
|
+
/*#__PURE__*/ jsx(InlineEditPreviewValue, {
|
|
83
|
+
lineClamp: lineClamp,
|
|
84
|
+
children: children
|
|
85
|
+
}),
|
|
86
|
+
/*#__PURE__*/ jsx(InlineEditPreviewIcon, {})
|
|
87
|
+
]
|
|
88
|
+
})
|
|
89
|
+
});
|
|
90
|
+
if (activatable && null != tooltip) return /*#__PURE__*/ jsxs(Tooltip, {
|
|
91
|
+
positioning: tooltipPositioning,
|
|
92
|
+
children: [
|
|
93
|
+
/*#__PURE__*/ jsx(TooltipTrigger, {
|
|
94
|
+
asChild: true,
|
|
95
|
+
children: target
|
|
96
|
+
}),
|
|
97
|
+
/*#__PURE__*/ jsx(TooltipContent, {
|
|
98
|
+
children: tooltip
|
|
99
|
+
})
|
|
100
|
+
]
|
|
101
|
+
});
|
|
102
|
+
return target;
|
|
103
|
+
};
|
|
104
|
+
InlineEditPreview.displayName = 'InlineEditPreview';
|
|
105
|
+
export { InlineEditPreview };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FC, ReactNode, Ref } from 'react';
|
|
2
|
+
export interface InlineEditPreviewIconProps {
|
|
3
|
+
ref?: Ref<HTMLSpanElement>;
|
|
4
|
+
/** Idle-state icon shown on hover/focus while the value is editable. Defaults to a pencil. */
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Trailing icon area of `InlineEditPreview`: while a commit is in flight it
|
|
9
|
+
* shows a spinner, right after a successful commit a success check, and
|
|
10
|
+
* otherwise the idle icon (only while the field is actually editable).
|
|
11
|
+
*/
|
|
12
|
+
export declare const InlineEditPreviewIcon: FC<InlineEditPreviewIconProps>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Check, Pencil } from "../../icons/index.js";
|
|
3
|
+
import { useTestId } from "../../utils/testId.js";
|
|
4
|
+
import { Loader } from "../Loader/index.js";
|
|
5
|
+
import { useInlineEdit } from "./InlineEditContext.js";
|
|
6
|
+
const InlineEditPreviewIcon = ({ ref, children = /*#__PURE__*/ jsx(Pencil, {
|
|
7
|
+
size: "md"
|
|
8
|
+
}) })=>{
|
|
9
|
+
const testId = useTestId('preview-icon');
|
|
10
|
+
const { status, disabled, readOnly, activationMode } = useInlineEdit();
|
|
11
|
+
const isLoading = 'loading' === status;
|
|
12
|
+
const activatable = !disabled && !readOnly && !isLoading && 'none' !== activationMode;
|
|
13
|
+
if (isLoading) return /*#__PURE__*/ jsx("span", {
|
|
14
|
+
ref: ref,
|
|
15
|
+
"data-testid": testId,
|
|
16
|
+
"data-slot": "inline-edit-preview-icon",
|
|
17
|
+
className: "inline-flex items-center",
|
|
18
|
+
children: /*#__PURE__*/ jsx(Loader, {
|
|
19
|
+
type: "circle",
|
|
20
|
+
size: "md"
|
|
21
|
+
})
|
|
22
|
+
});
|
|
23
|
+
if ('saved' === status) return /*#__PURE__*/ jsx("span", {
|
|
24
|
+
ref: ref,
|
|
25
|
+
"data-testid": testId,
|
|
26
|
+
"data-slot": "inline-edit-preview-icon",
|
|
27
|
+
className: "inline-flex items-center",
|
|
28
|
+
children: /*#__PURE__*/ jsx(Check, {
|
|
29
|
+
size: "md",
|
|
30
|
+
className: "text-icon-success"
|
|
31
|
+
})
|
|
32
|
+
});
|
|
33
|
+
if (!activatable) return null;
|
|
34
|
+
return /*#__PURE__*/ jsx("span", {
|
|
35
|
+
ref: ref,
|
|
36
|
+
"data-testid": testId,
|
|
37
|
+
"data-slot": "inline-edit-preview-icon",
|
|
38
|
+
className: "inline-flex items-center text-icon-secondary opacity-0 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100",
|
|
39
|
+
children: children
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
InlineEditPreviewIcon.displayName = 'InlineEditPreviewIcon';
|
|
43
|
+
export { InlineEditPreviewIcon };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FC, ReactNode, Ref } from 'react';
|
|
2
|
+
export interface InlineEditPreviewValueProps {
|
|
3
|
+
ref?: Ref<HTMLSpanElement>;
|
|
4
|
+
/**
|
|
5
|
+
* Clamp the read-mode value to this many lines (with an ellipsis) instead of
|
|
6
|
+
* the default single-line truncation. Use for multi-line values (textarea).
|
|
7
|
+
*/
|
|
8
|
+
lineClamp?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
export declare const InlineEditPreviewValue: FC<InlineEditPreviewValueProps>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../../utils/cn.js";
|
|
3
|
+
import { useTestId } from "../../utils/testId.js";
|
|
4
|
+
import { useInlineEdit } from "./InlineEditContext.js";
|
|
5
|
+
const LINE_CLAMP_CLASS = {
|
|
6
|
+
1: 'line-clamp-1',
|
|
7
|
+
2: 'line-clamp-2',
|
|
8
|
+
3: 'line-clamp-3',
|
|
9
|
+
4: 'line-clamp-4',
|
|
10
|
+
5: 'line-clamp-5',
|
|
11
|
+
6: 'line-clamp-6'
|
|
12
|
+
};
|
|
13
|
+
const InlineEditPreviewValue = ({ ref, lineClamp, children })=>{
|
|
14
|
+
const testId = useTestId('preview-value');
|
|
15
|
+
const { status } = useInlineEdit();
|
|
16
|
+
return /*#__PURE__*/ jsx("span", {
|
|
17
|
+
ref: ref,
|
|
18
|
+
"data-testid": testId,
|
|
19
|
+
"data-slot": "inline-edit-preview-value",
|
|
20
|
+
className: cn('min-w-0 flex-1', lineClamp ? LINE_CLAMP_CLASS[lineClamp] : 'truncate', 'loading' === status && 'opacity-50'),
|
|
21
|
+
children: children
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
InlineEditPreviewValue.displayName = 'InlineEditPreviewValue';
|
|
25
|
+
export { InlineEditPreviewValue };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { ListCollection } from '@ark-ui/react/collection';
|
|
3
|
+
import { type SelectDataItem } from '../Select';
|
|
4
|
+
export interface InlineEditSelectProps {
|
|
5
|
+
/**
|
|
6
|
+
* Options; the collection is built internally (PaginationPageSize
|
|
7
|
+
* precedent). Provide exactly one of `items` / `collection`.
|
|
8
|
+
*/
|
|
9
|
+
items?: SelectDataItem[];
|
|
10
|
+
/**
|
|
11
|
+
* A prebuilt collection (e.g. built once upstream, or with custom
|
|
12
|
+
* filtering/sorting). Provide exactly one of `items` / `collection` — a
|
|
13
|
+
* dev-only warning fires otherwise.
|
|
14
|
+
*/
|
|
15
|
+
collection?: ListCollection<SelectDataItem>;
|
|
16
|
+
multiple?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* `InlineEditSelect` IS the prewired `Select` root — the draft binding,
|
|
19
|
+
* `defaultOpen`, and commit-on-close all live here. `children` are
|
|
20
|
+
* ordinary `Select` compound parts (`SelectButton`/`SelectInput`,
|
|
21
|
+
* `SelectPositioner > SelectContent > SelectOption…`) — exactly as you'd
|
|
22
|
+
* compose a standalone `Select`. `SelectButton` already cascades its own
|
|
23
|
+
* testid slot from this root with zero extra wiring (`{base}--button`).
|
|
24
|
+
* `SelectOption` cascades too, but nested one level further: `SelectContent`
|
|
25
|
+
* re-provides the cascade scoped to its own `content` slot (a `ScrollArea`
|
|
26
|
+
* implementation detail — see its own testid comment), so bare options
|
|
27
|
+
* resolve under `{base}--content--option` rather than `{base}--option`.
|
|
28
|
+
* That cascaded id also has no per-item disambiguation (every option in a
|
|
29
|
+
* collection resolves to the same testid) — pass a per-item `data-testid`
|
|
30
|
+
* override if a test needs to address one option by id rather than by text.
|
|
31
|
+
* `SelectInput` (the multiple-select trigger) does not cascade on its own —
|
|
32
|
+
* pass it `data-testid` explicitly if you need one.
|
|
33
|
+
*/
|
|
34
|
+
children: ReactNode;
|
|
35
|
+
}
|
|
36
|
+
export declare const InlineEditSelect: {
|
|
37
|
+
({ items, collection, multiple, children, }: InlineEditSelectProps): import("react/jsx-runtime").JSX.Element;
|
|
38
|
+
displayName: string;
|
|
39
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo } from "react";
|
|
3
|
+
import { useTestId } from "../../utils/testId.js";
|
|
4
|
+
import { Select, createListCollection } from "../Select/index.js";
|
|
5
|
+
import { useInlineEdit, useInlineEditSubmitMode } from "./InlineEditContext.js";
|
|
6
|
+
const InlineEditSelect = ({ items, collection, multiple = false, children })=>{
|
|
7
|
+
const testId = useTestId();
|
|
8
|
+
const { value, setValue, submit } = useInlineEdit();
|
|
9
|
+
useInlineEditSubmitMode('none');
|
|
10
|
+
const collectionFromItems = useMemo(()=>createListCollection({
|
|
11
|
+
items: items ?? []
|
|
12
|
+
}), [
|
|
13
|
+
items
|
|
14
|
+
]);
|
|
15
|
+
useEffect(()=>{
|
|
16
|
+
if ('production' === process.env.NODE_ENV) return;
|
|
17
|
+
const hasItems = void 0 !== items;
|
|
18
|
+
const hasCollection = void 0 !== collection;
|
|
19
|
+
if (hasItems === hasCollection) console.warn(`InlineEditSelect: provide exactly one of \`items\` or \`collection\` (got ${hasItems && hasCollection ? 'both' : 'neither'}).`);
|
|
20
|
+
}, [
|
|
21
|
+
items,
|
|
22
|
+
collection
|
|
23
|
+
]);
|
|
24
|
+
const resolvedCollection = collection ?? collectionFromItems;
|
|
25
|
+
const selected = Array.isArray(value) ? value : 'string' == typeof value && '' !== value ? [
|
|
26
|
+
value
|
|
27
|
+
] : [];
|
|
28
|
+
return /*#__PURE__*/ jsx(Select, {
|
|
29
|
+
"data-testid": testId,
|
|
30
|
+
defaultOpen: true,
|
|
31
|
+
collection: resolvedCollection,
|
|
32
|
+
multiple: multiple,
|
|
33
|
+
value: selected,
|
|
34
|
+
onValueChange: (details)=>setValue(details.value),
|
|
35
|
+
onOpenChange: (details)=>{
|
|
36
|
+
if (!details.open) submit();
|
|
37
|
+
},
|
|
38
|
+
children: children
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
InlineEditSelect.displayName = 'InlineEditSelect';
|
|
42
|
+
export { InlineEditSelect };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useTestId } from "../../utils/testId.js";
|
|
3
|
+
import { Textarea } from "../Textarea/index.js";
|
|
4
|
+
import { useInlineEdit } from "./InlineEditContext.js";
|
|
5
|
+
const InlineEditTextarea = ({ size = 'small', 'data-testid': testIdProp, ...props })=>{
|
|
6
|
+
const testId = useTestId('input', testIdProp);
|
|
7
|
+
const { value, setValue, invalid } = useInlineEdit();
|
|
8
|
+
return /*#__PURE__*/ jsx(Textarea, {
|
|
9
|
+
...props,
|
|
10
|
+
"data-testid": testId,
|
|
11
|
+
value: value ?? '',
|
|
12
|
+
onChange: (event)=>setValue(event.target.value),
|
|
13
|
+
error: invalid,
|
|
14
|
+
size: size
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
InlineEditTextarea.displayName = 'InlineEditTextarea';
|
|
18
|
+
export { InlineEditTextarea };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import { type TimeInputProps } from '../TimeInput';
|
|
3
|
+
export type InlineEditTimeProps = Omit<TimeInputProps, 'value' | 'onChange'>;
|
|
4
|
+
/**
|
|
5
|
+
* Commits on blur — time pickers have no discrete "selection complete" event.
|
|
6
|
+
* Safe because the time dropdown is rendered inline (absolute, non-portaled)
|
|
7
|
+
* and its rows preventDefault() on mousedown, so focus never leaves the
|
|
8
|
+
* control subtree; a future portaled TimeDropdown would need re-evaluation.
|
|
9
|
+
* Clipping ancestors need `overflow-visible` (automatic inside
|
|
10
|
+
* AttributeValue; standalone hosts own it).
|
|
11
|
+
*/
|
|
12
|
+
export declare const InlineEditTime: FC<InlineEditTimeProps>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useTestId } from "../../utils/testId.js";
|
|
3
|
+
import { TimeInput } from "../TimeInput/index.js";
|
|
4
|
+
import { useInlineEdit, useInlineEditSubmitMode } from "./InlineEditContext.js";
|
|
5
|
+
const InlineEditTime = ({ 'data-testid': testIdProp, granularity = 'minute', showTimeDropdown = true, showIcon = false, size = 'small', ...props })=>{
|
|
6
|
+
const testId = useTestId('input', testIdProp);
|
|
7
|
+
const { value, setValue } = useInlineEdit();
|
|
8
|
+
useInlineEditSubmitMode('blur');
|
|
9
|
+
return /*#__PURE__*/ jsx(TimeInput, {
|
|
10
|
+
...props,
|
|
11
|
+
"data-testid": testId,
|
|
12
|
+
granularity: granularity,
|
|
13
|
+
showTimeDropdown: showTimeDropdown,
|
|
14
|
+
showIcon: showIcon,
|
|
15
|
+
size: size,
|
|
16
|
+
value: value ?? null,
|
|
17
|
+
onChange: (v)=>setValue(v)
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
InlineEditTime.displayName = 'InlineEditTime';
|
|
21
|
+
export { InlineEditTime };
|