@stll/ui 0.5.2 → 0.7.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.
Files changed (39) hide show
  1. package/README.md +71 -2
  2. package/dist/components/application-shell.d.ts +27 -0
  3. package/dist/components/application-shell.js +25 -0
  4. package/dist/components/button-variants.d.ts +2 -2
  5. package/dist/components/input-group.d.ts +1 -1
  6. package/dist/components/loader.d.ts +36 -0
  7. package/dist/components/loader.js +54 -0
  8. package/dist/index.d.ts +12 -1
  9. package/dist/index.js +12 -1
  10. package/dist/inspector/chrome.d.ts +6 -1
  11. package/dist/inspector/chrome.js +8 -3
  12. package/dist/inspector/layout-tokens.d.ts +1 -2
  13. package/dist/inspector/layout-tokens.js +1 -2
  14. package/dist/kanban/index.d.ts +3 -1
  15. package/dist/kanban/index.js +3 -1
  16. package/dist/kanban/sortable-edge.d.ts +39 -0
  17. package/dist/kanban/sortable-edge.js +27 -0
  18. package/dist/kanban/sortable-interactions.d.ts +75 -0
  19. package/dist/kanban/sortable-interactions.js +210 -0
  20. package/dist/kanban/touch-identity.d.ts +8 -0
  21. package/dist/kanban/touch-identity.js +4 -0
  22. package/dist/lib/initials.d.ts +16 -0
  23. package/dist/lib/initials.js +27 -0
  24. package/dist/review/review-author-avatar.d.ts +23 -0
  25. package/dist/review/review-author-avatar.js +30 -0
  26. package/dist/review/review-comment-card.d.ts +33 -0
  27. package/dist/review/review-comment-card.js +76 -0
  28. package/dist/review/review-decision-actions.d.ts +32 -0
  29. package/dist/review/review-decision-actions.js +74 -0
  30. package/dist/review/review-diff-text.d.ts +33 -0
  31. package/dist/review/review-diff-text.js +67 -0
  32. package/dist/review/review-out-of-date-notice.d.ts +28 -0
  33. package/dist/review/review-out-of-date-notice.js +32 -0
  34. package/dist/review/review-severity-dot.d.ts +27 -0
  35. package/dist/review/review-severity-dot.js +35 -0
  36. package/dist/review/review-status-badge.d.ts +30 -0
  37. package/dist/review/review-status-badge.js +45 -0
  38. package/dist/styles/theme.css +26 -0
  39. package/package.json +58 -5
@@ -0,0 +1,75 @@
1
+ import { Button } from "../components/button.js";
2
+ import * as React$1 from "react";
3
+ import { AutoScrollOptions, CollisionDetection, DndContextProps, DragCancelEvent, DragEndEvent, DragOverlayProps, DragStartEvent, KeyboardCoordinateGetter, UniqueIdentifier } from "@dnd-kit/core";
4
+ import { SortableContextProps, useSortable } from "@dnd-kit/sortable";
5
+ //#region src/kanban/sortable-interactions.d.ts
6
+ declare const KANBAN_MOUSE_ACTIVATION_DISTANCE = 8;
7
+ declare const KANBAN_TOUCH_ACTIVATION_CONSTRAINT: {
8
+ readonly delay: 150;
9
+ readonly tolerance: 8;
10
+ };
11
+ type KanbanSortableBoardProps = {
12
+ children: React$1.ReactNode;
13
+ onDragEnd: (event: DragEndEvent) => void;
14
+ collisionDetection?: CollisionDetection | undefined;
15
+ keyboardCoordinates?: KeyboardCoordinateGetter | undefined;
16
+ autoScroll?: boolean | AutoScrollOptions | undefined;
17
+ /** Replaces the default mouse, touch, and keyboard sensors when supplied. */
18
+ sensors?: DndContextProps["sensors"] | undefined;
19
+ /** Overrides dnd-kit's screen-reader announcements when supplied. */
20
+ accessibility?: DndContextProps["accessibility"] | undefined;
21
+ onDragStart?: ((event: DragStartEvent) => void) | undefined;
22
+ onDragCancel?: ((event: DragCancelEvent) => void) | undefined;
23
+ /** Rendered in document.body while an item is active. */
24
+ overlay?: ((activeId: UniqueIdentifier | null) => React$1.ReactNode) | undefined;
25
+ overlayProps?: Omit<DragOverlayProps, "children"> | undefined;
26
+ };
27
+ /**
28
+ * Input-complete drag context for sortable boards.
29
+ *
30
+ * The consumer owns identifiers and the result of a drop; this component owns
31
+ * the sensor activation rules, keyboard navigation, auto-scroll configuration,
32
+ * and overlay lifecycle shared by sortable board UIs.
33
+ */
34
+ declare const KanbanSortableBoard: ({ children, onDragEnd, collisionDetection, keyboardCoordinates, autoScroll, sensors, accessibility, onDragStart, onDragCancel, overlay, overlayProps }: KanbanSortableBoardProps) => React$1.JSX.Element;
35
+ declare const useKanbanSortableSensors: (keyboardCoordinates?: KeyboardCoordinateGetter) => import("@dnd-kit/core").SensorDescriptor<import("@dnd-kit/core").SensorOptions>[];
36
+ type KanbanSortableListProps = SortableContextProps & React$1.ComponentProps<"div">;
37
+ /** A vertical card list that preserves native vertical touch scrolling. */
38
+ declare const KanbanSortableList: ({ className, children, id, items, strategy, disabled, ...props }: KanbanSortableListProps) => React$1.JSX.Element;
39
+ type KanbanSortableColumnsProps = SortableContextProps & React$1.ComponentProps<"div">;
40
+ /** A horizontal column list that keeps the board pan gesture available. */
41
+ declare const KanbanSortableColumns: ({ className, children, id, items, strategy, disabled, ...props }: KanbanSortableColumnsProps) => React$1.JSX.Element;
42
+ type KanbanSortableBindings = Pick<ReturnType<typeof useSortable>, "attributes" | "listeners" | "setActivatorNodeRef">;
43
+ type UseKanbanSortableOptions = {
44
+ id: UniqueIdentifier;
45
+ disabled?: boolean | undefined;
46
+ };
47
+ /**
48
+ * Connect a sortable item and its separate drag handle without making the
49
+ * item's content a touch-none activation surface.
50
+ */
51
+ declare const useKanbanSortable: ({ id, disabled }: UseKanbanSortableOptions) => {
52
+ isDragging: boolean;
53
+ setNodeRef: (node: HTMLElement | null) => void;
54
+ style: {
55
+ transform: string | undefined;
56
+ transition: string | undefined;
57
+ };
58
+ dragHandle: {
59
+ attributes: import("@dnd-kit/core").DraggableAttributes;
60
+ listeners: import("@dnd-kit/core/dist/hooks/utilities").SyntheticListenerMap | undefined;
61
+ setActivatorNodeRef: (element: HTMLElement | null) => void;
62
+ };
63
+ };
64
+ type KanbanDragHandleProps = {
65
+ bindings: KanbanSortableBindings;
66
+ label: string;
67
+ } & Omit<React$1.ComponentProps<typeof Button>, "aria-label" | "children" | "ref" | "tooltip" | "type">;
68
+ /**
69
+ * A 44px keyboard-accessible drag activator. The handle is the only board
70
+ * surface that disables touch panning, leaving cards and scroll regions free
71
+ * for ordinary scrolling.
72
+ */
73
+ declare const KanbanDragHandle: ({ bindings, label, className, ...props }: KanbanDragHandleProps) => React$1.JSX.Element;
74
+ //#endregion
75
+ export { KANBAN_MOUSE_ACTIVATION_DISTANCE, KANBAN_TOUCH_ACTIVATION_CONSTRAINT, KanbanDragHandle, KanbanDragHandleProps, KanbanSortableBindings, KanbanSortableBoard, KanbanSortableBoardProps, KanbanSortableColumns, KanbanSortableColumnsProps, KanbanSortableList, KanbanSortableListProps, UseKanbanSortableOptions, useKanbanSortable, useKanbanSortableSensors };
@@ -0,0 +1,210 @@
1
+ "use client";
2
+ import { cn } from "../lib/utils.js";
3
+ import { Button } from "../components/button.js";
4
+ import { isActiveTouchChange } from "./touch-identity.js";
5
+ import { GripVerticalIcon } from "lucide-react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ import * as React$1 from "react";
8
+ import { createPortal } from "react-dom";
9
+ import { DndContext, DragOverlay, KeyboardSensor, MouseSensor, TouchSensor, useSensor, useSensors } from "@dnd-kit/core";
10
+ import { SortableContext, sortableKeyboardCoordinates, useSortable } from "@dnd-kit/sortable";
11
+ //#region src/kanban/sortable-interactions.tsx
12
+ const KANBAN_MOUSE_ACTIVATION_DISTANCE = 8;
13
+ const KANBAN_TOUCH_ACTIVATION_CONSTRAINT = {
14
+ delay: 150,
15
+ tolerance: 8
16
+ };
17
+ const hasTouchLists = (event) => "changedTouches" in event && "touches" in event;
18
+ const getTouchIdentifier = (event) => {
19
+ if (!hasTouchLists(event)) return null;
20
+ return event.changedTouches.item(0)?.identifier ?? event.touches.item(0)?.identifier ?? null;
21
+ };
22
+ const getTouchIdentifiers = (touches) => {
23
+ const identifiers = [];
24
+ for (let index = 0; index < touches.length; index += 1) {
25
+ const identifier = touches.item(index)?.identifier;
26
+ if (identifier !== void 0) identifiers.push(identifier);
27
+ }
28
+ return identifiers;
29
+ };
30
+ /**
31
+ * Keeps a delayed touch drag bound to the finger that activated it.
32
+ *
33
+ * dnd-kit's stock touch sensor attaches document-level lifecycle listeners,
34
+ * so a second finger can otherwise move, end, or cancel the active drag. The
35
+ * capture listeners below suppress only secondary touch changes before those
36
+ * listeners receive them; browser scrolling remains native because no default
37
+ * action is prevented here.
38
+ */
39
+ var KanbanTouchSensor = class extends TouchSensor {
40
+ identityListeners;
41
+ ownerDocument;
42
+ touchIdentifier;
43
+ constructor(props) {
44
+ const identityListeners = new AbortController();
45
+ super({
46
+ ...props,
47
+ onAbort: (active) => {
48
+ identityListeners.abort();
49
+ props.onAbort(active);
50
+ },
51
+ onCancel: () => {
52
+ identityListeners.abort();
53
+ props.onCancel();
54
+ },
55
+ onEnd: () => {
56
+ identityListeners.abort();
57
+ props.onEnd();
58
+ }
59
+ });
60
+ this.identityListeners = identityListeners;
61
+ this.ownerDocument = getTouchDocument(props.event);
62
+ this.touchIdentifier = getTouchIdentifier(props.event);
63
+ this.ownerDocument.addEventListener("touchmove", this.handleTouchMove, {
64
+ capture: true,
65
+ passive: false,
66
+ signal: identityListeners.signal
67
+ });
68
+ this.ownerDocument.addEventListener("touchend", this.handleTouchEnd, {
69
+ capture: true,
70
+ signal: identityListeners.signal
71
+ });
72
+ this.ownerDocument.addEventListener("touchcancel", this.handleTouchCancel, {
73
+ capture: true,
74
+ signal: identityListeners.signal
75
+ });
76
+ }
77
+ handleTouchMove = (event) => {
78
+ if (this.isPrimaryTouchChange(event)) return;
79
+ event.stopImmediatePropagation();
80
+ };
81
+ handleTouchEnd = (event) => {
82
+ if (!this.isPrimaryTouchChange(event)) {
83
+ event.stopImmediatePropagation();
84
+ return;
85
+ }
86
+ this.detachIdentityListeners();
87
+ };
88
+ handleTouchCancel = (event) => {
89
+ if (!this.isPrimaryTouchChange(event)) {
90
+ event.stopImmediatePropagation();
91
+ return;
92
+ }
93
+ this.detachIdentityListeners();
94
+ };
95
+ isPrimaryTouchChange = (event) => isActiveTouchChange({
96
+ activeTouchIdentifier: this.touchIdentifier,
97
+ changedTouchIdentifiers: getTouchIdentifiers(event.changedTouches)
98
+ });
99
+ detachIdentityListeners = () => {
100
+ this.identityListeners.abort();
101
+ };
102
+ };
103
+ const getTouchDocument = (event) => {
104
+ if (event.target instanceof Node && event.target.ownerDocument) return event.target.ownerDocument;
105
+ return document;
106
+ };
107
+ /**
108
+ * Input-complete drag context for sortable boards.
109
+ *
110
+ * The consumer owns identifiers and the result of a drop; this component owns
111
+ * the sensor activation rules, keyboard navigation, auto-scroll configuration,
112
+ * and overlay lifecycle shared by sortable board UIs.
113
+ */
114
+ const KanbanSortableBoard = ({ children, onDragEnd, collisionDetection, keyboardCoordinates, autoScroll, sensors, accessibility, onDragStart, onDragCancel, overlay, overlayProps }) => {
115
+ const [activeId, setActiveId] = React$1.useState(null);
116
+ const defaultSensors = useKanbanSortableSensors(keyboardCoordinates);
117
+ const handleDragStart = (event) => {
118
+ setActiveId(event.active.id);
119
+ onDragStart?.(event);
120
+ };
121
+ const handleDragEnd = (event) => {
122
+ setActiveId(null);
123
+ onDragEnd(event);
124
+ };
125
+ const handleDragCancel = (event) => {
126
+ setActiveId(null);
127
+ onDragCancel?.(event);
128
+ };
129
+ return /* @__PURE__ */ jsxs(DndContext, {
130
+ ...autoScroll === void 0 ? {} : { autoScroll },
131
+ ...accessibility === void 0 ? {} : { accessibility },
132
+ ...collisionDetection === void 0 ? {} : { collisionDetection },
133
+ onDragCancel: handleDragCancel,
134
+ onDragEnd: handleDragEnd,
135
+ onDragStart: handleDragStart,
136
+ sensors: sensors ?? defaultSensors,
137
+ children: [children, overlay && typeof document !== "undefined" ? createPortal(/* @__PURE__ */ jsx(DragOverlay, {
138
+ ...overlayProps,
139
+ children: overlay(activeId)
140
+ }), document.body) : null]
141
+ });
142
+ };
143
+ const useKanbanSortableSensors = (keyboardCoordinates = sortableKeyboardCoordinates) => useSensors(useSensor(MouseSensor, { activationConstraint: { distance: 8 } }), useSensor(KanbanTouchSensor, { activationConstraint: KANBAN_TOUCH_ACTIVATION_CONSTRAINT }), useSensor(KeyboardSensor, { coordinateGetter: keyboardCoordinates }));
144
+ /** A vertical card list that preserves native vertical touch scrolling. */
145
+ const KanbanSortableList = ({ className, children, id, items, strategy, disabled, ...props }) => /* @__PURE__ */ jsx(SortableContext, {
146
+ ...disabled === void 0 ? {} : { disabled },
147
+ ...id === void 0 ? {} : { id },
148
+ items,
149
+ ...strategy === void 0 ? {} : { strategy },
150
+ children: /* @__PURE__ */ jsx("div", {
151
+ className: cn("min-h-0 touch-auto overflow-y-auto overscroll-y-contain", className),
152
+ ...props,
153
+ children
154
+ })
155
+ });
156
+ /** A horizontal column list that keeps the board pan gesture available. */
157
+ const KanbanSortableColumns = ({ className, children, id, items, strategy, disabled, ...props }) => /* @__PURE__ */ jsx(SortableContext, {
158
+ ...disabled === void 0 ? {} : { disabled },
159
+ ...id === void 0 ? {} : { id },
160
+ items,
161
+ ...strategy === void 0 ? {} : { strategy },
162
+ children: /* @__PURE__ */ jsx("div", {
163
+ className: cn("flex min-h-0 touch-auto overflow-x-auto overscroll-x-contain", className),
164
+ ...props,
165
+ children
166
+ })
167
+ });
168
+ /**
169
+ * Connect a sortable item and its separate drag handle without making the
170
+ * item's content a touch-none activation surface.
171
+ */
172
+ const useKanbanSortable = ({ id, disabled }) => {
173
+ const sortable = useSortable({
174
+ id,
175
+ ...disabled === void 0 ? {} : { disabled }
176
+ });
177
+ return {
178
+ isDragging: sortable.isDragging,
179
+ setNodeRef: sortable.setNodeRef,
180
+ style: {
181
+ transform: sortable.transform ? `translate3d(${sortable.transform.x}px, ${sortable.transform.y}px, 0)` : void 0,
182
+ transition: sortable.transition
183
+ },
184
+ dragHandle: {
185
+ attributes: sortable.attributes,
186
+ listeners: sortable.listeners,
187
+ setActivatorNodeRef: sortable.setActivatorNodeRef
188
+ }
189
+ };
190
+ };
191
+ /**
192
+ * A 44px keyboard-accessible drag activator. The handle is the only board
193
+ * surface that disables touch panning, leaving cards and scroll regions free
194
+ * for ordinary scrolling.
195
+ */
196
+ const KanbanDragHandle = ({ bindings, label, className, ...props }) => /* @__PURE__ */ jsx(Button, {
197
+ ...props,
198
+ ...bindings.attributes,
199
+ ...bindings.listeners,
200
+ "aria-label": label,
201
+ className: cn("size-11 touch-none sm:size-11", className),
202
+ ref: (element) => bindings.setActivatorNodeRef(element),
203
+ size: "icon-xl",
204
+ tooltip: false,
205
+ type: "button",
206
+ variant: "ghost",
207
+ children: /* @__PURE__ */ jsx(GripVerticalIcon, { "aria-hidden": "true" })
208
+ });
209
+ //#endregion
210
+ export { KANBAN_MOUSE_ACTIVATION_DISTANCE, KANBAN_TOUCH_ACTIVATION_CONSTRAINT, KanbanDragHandle, KanbanSortableBoard, KanbanSortableColumns, KanbanSortableList, useKanbanSortable, useKanbanSortableSensors };
@@ -0,0 +1,8 @@
1
+ //#region src/kanban/touch-identity.d.ts
2
+ type ActiveTouchChangeOptions = {
3
+ activeTouchIdentifier: number | null;
4
+ changedTouchIdentifiers: readonly number[];
5
+ };
6
+ declare const isActiveTouchChange: ({ activeTouchIdentifier, changedTouchIdentifiers }: ActiveTouchChangeOptions) => boolean;
7
+ //#endregion
8
+ export { isActiveTouchChange };
@@ -0,0 +1,4 @@
1
+ //#region src/kanban/touch-identity.ts
2
+ const isActiveTouchChange = ({ activeTouchIdentifier, changedTouchIdentifiers }) => activeTouchIdentifier === null || changedTouchIdentifiers.includes(activeTouchIdentifier);
3
+ //#endregion
4
+ export { isActiveTouchChange };
@@ -0,0 +1,16 @@
1
+ //#region src/lib/initials.d.ts
2
+ /**
3
+ * Extract up to two uppercase initials from a display name.
4
+ *
5
+ * Behaviour:
6
+ * - Multi-word: first letter of each of the first two words.
7
+ * "Eva Schmidt" → "ES", "Jan van Houten" → "JV"
8
+ * - Single-word (Latin): first two characters.
9
+ * "John" → "JO"
10
+ * - CJK / no-space scripts: first two characters.
11
+ * "王小明" → "王小"
12
+ * - Null / empty: "?"
13
+ */
14
+ declare const getInitials: (name: string | null) => string;
15
+ //#endregion
16
+ export { getInitials };
@@ -0,0 +1,27 @@
1
+ //#region src/lib/initials.ts
2
+ /**
3
+ * Extract up to two uppercase initials from a display name.
4
+ *
5
+ * Behaviour:
6
+ * - Multi-word: first letter of each of the first two words.
7
+ * "Eva Schmidt" → "ES", "Jan van Houten" → "JV"
8
+ * - Single-word (Latin): first two characters.
9
+ * "John" → "JO"
10
+ * - CJK / no-space scripts: first two characters.
11
+ * "王小明" → "王小"
12
+ * - Null / empty: "?"
13
+ */
14
+ const getInitials = (name) => {
15
+ if (!name) return "?";
16
+ const trimmed = name.trim();
17
+ if (trimmed.length === 0) return "?";
18
+ const parts = trimmed.split(/\s+/u);
19
+ if (parts.length >= 2) {
20
+ const a = parts.at(0) ?? "";
21
+ const b = parts.at(1) ?? "";
22
+ return `${a.at(0) ?? ""}${b.at(0) ?? ""}`.toUpperCase();
23
+ }
24
+ return trimmed.slice(0, 2).toUpperCase();
25
+ };
26
+ //#endregion
27
+ export { getInitials };
@@ -0,0 +1,23 @@
1
+ import { Avatar } from "../components/avatar.js";
2
+ import { ComponentProps } from "react";
3
+ //#region src/review/review-author-avatar.d.ts
4
+ /**
5
+ * Shown when an author has neither a name nor anything to fall back on. This
6
+ * package carries no catalogs, so a localized host passes `fallbackLabel`;
7
+ * the constant is exported so every surface that needs the same placeholder
8
+ * outside an avatar reads it from here rather than minting its own.
9
+ */
10
+ declare const UNKNOWN_AUTHOR_LABEL = "Unknown user";
11
+ type ReviewAuthorAvatarProps = Omit<ComponentProps<typeof Avatar>, "children" | "className"> & {
12
+ image?: string | null | undefined;
13
+ name?: string | null;
14
+ deleted?: boolean | undefined;
15
+ fallbackLabel?: string;
16
+ className?: string | undefined;
17
+ fallbackClassName?: string | undefined;
18
+ };
19
+ /** The author's face on any review surface: image when there is one, initials
20
+ * otherwise, dimmed once the account is gone. */
21
+ declare const ReviewAuthorAvatar: ({ deleted, image, name, fallbackLabel, className, fallbackClassName, ...avatarProps }: ReviewAuthorAvatarProps) => import("react").JSX.Element;
22
+ //#endregion
23
+ export { ReviewAuthorAvatar, UNKNOWN_AUTHOR_LABEL };
@@ -0,0 +1,30 @@
1
+ import { cn } from "../lib/utils.js";
2
+ import { Avatar, AvatarFallback, AvatarImage } from "../components/avatar.js";
3
+ import { getInitials } from "../lib/initials.js";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/review/review-author-avatar.tsx
6
+ /**
7
+ * Shown when an author has neither a name nor anything to fall back on. This
8
+ * package carries no catalogs, so a localized host passes `fallbackLabel`;
9
+ * the constant is exported so every surface that needs the same placeholder
10
+ * outside an avatar reads it from here rather than minting its own.
11
+ */
12
+ const UNKNOWN_AUTHOR_LABEL = "Unknown user";
13
+ /** The author's face on any review surface: image when there is one, initials
14
+ * otherwise, dimmed once the account is gone. */
15
+ const ReviewAuthorAvatar = ({ deleted = false, image, name, fallbackLabel = UNKNOWN_AUTHOR_LABEL, className, fallbackClassName, ...avatarProps }) => {
16
+ const displayName = name?.trim() || fallbackLabel;
17
+ return /* @__PURE__ */ jsxs(Avatar, {
18
+ ...avatarProps,
19
+ className: cn(className, deleted && "opacity-60 grayscale"),
20
+ children: [image ? /* @__PURE__ */ jsx(AvatarImage, {
21
+ alt: displayName,
22
+ src: image
23
+ }) : null, /* @__PURE__ */ jsx(AvatarFallback, {
24
+ className: cn(fallbackClassName, deleted && "bg-muted text-muted-foreground"),
25
+ children: getInitials(name ?? null)
26
+ })]
27
+ });
28
+ };
29
+ //#endregion
30
+ export { ReviewAuthorAvatar, UNKNOWN_AUTHOR_LABEL };
@@ -0,0 +1,33 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/review/review-comment-card.d.ts
3
+ type ReviewCommentAuthor = {
4
+ name: string | null;
5
+ image?: string | null | undefined;
6
+ deleted?: boolean | undefined;
7
+ };
8
+ type ReviewCommentCardProps = {
9
+ author: ReviewCommentAuthor;
10
+ /** Machine-readable instant for `<time>`; ISO string or Date. */
11
+ timestamp: string | Date;
12
+ /** The same instant, already formatted by the host's locale formatter. */
13
+ formattedTime: string;
14
+ body: ReactNode;
15
+ /** The text the comment was written against, when it is worth echoing. */
16
+ anchorText?: string | undefined;
17
+ /** The comment is anchored to text the document has since moved past. */
18
+ isStale?: boolean | undefined;
19
+ staleLabel?: string | undefined;
20
+ resolved?: boolean | undefined;
21
+ onToggleResolved?: (() => void) | undefined;
22
+ resolveLabel?: string | undefined;
23
+ reopenLabel?: string | undefined;
24
+ canDelete?: boolean | undefined;
25
+ onDelete?: (() => void) | undefined;
26
+ deleteLabel?: string | undefined;
27
+ className?: string;
28
+ };
29
+ /** One comment on a reviewed surface: who wrote it, when, what it says, what
30
+ * it points at, and the two things a reader can do to it. */
31
+ declare const ReviewCommentCard: ({ author, timestamp, formattedTime, body, anchorText, isStale, staleLabel, resolved, onToggleResolved, resolveLabel, reopenLabel, canDelete, onDelete, deleteLabel, className }: ReviewCommentCardProps) => import("react").JSX.Element;
32
+ //#endregion
33
+ export { ReviewCommentAuthor, ReviewCommentCard };
@@ -0,0 +1,76 @@
1
+ import { cn } from "../lib/utils.js";
2
+ import { BidiText } from "../components/bidi-text.js";
3
+ import { Button } from "../components/button.js";
4
+ import { ReviewAuthorAvatar } from "./review-author-avatar.js";
5
+ import { CheckIcon, RotateCcwIcon, Trash2Icon } from "lucide-react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/review/review-comment-card.tsx
8
+ /** One comment on a reviewed surface: who wrote it, when, what it says, what
9
+ * it points at, and the two things a reader can do to it. */
10
+ const ReviewCommentCard = ({ author, timestamp, formattedTime, body, anchorText, isStale = false, staleLabel, resolved = false, onToggleResolved, resolveLabel, reopenLabel, canDelete = false, onDelete, deleteLabel, className }) => /* @__PURE__ */ jsxs("article", {
11
+ className: cn("flex items-start gap-2 px-3 py-2", resolved && "opacity-60", className),
12
+ "data-slot": "review-comment-card",
13
+ children: [
14
+ /* @__PURE__ */ jsx(ReviewAuthorAvatar, {
15
+ className: "mt-0.5 size-5 shrink-0 text-[9px]",
16
+ deleted: author.deleted,
17
+ image: author.image,
18
+ name: author.name
19
+ }),
20
+ /* @__PURE__ */ jsxs("div", {
21
+ className: "min-w-0 flex-1",
22
+ children: [
23
+ /* @__PURE__ */ jsxs("p", {
24
+ className: "text-muted-foreground flex min-w-0 items-baseline gap-1.5 text-[11px]",
25
+ children: [/* @__PURE__ */ jsx(BidiText, {
26
+ as: "span",
27
+ className: "text-foreground-strong-muted truncate font-medium",
28
+ children: author.name
29
+ }), /* @__PURE__ */ jsx("time", {
30
+ className: "shrink-0 tabular-nums",
31
+ dateTime: toIsoInstant(timestamp),
32
+ children: formattedTime
33
+ })]
34
+ }),
35
+ /* @__PURE__ */ jsx(BidiText, {
36
+ as: "div",
37
+ className: "text-foreground text-xs wrap-anywhere",
38
+ children: body
39
+ }),
40
+ anchorText === void 0 || anchorText === "" ? null : /* @__PURE__ */ jsx(BidiText, {
41
+ as: "p",
42
+ className: "text-muted-foreground mt-0.5 truncate text-[11px] italic",
43
+ children: anchorText
44
+ }),
45
+ isStale && staleLabel !== void 0 ? /* @__PURE__ */ jsx("p", {
46
+ className: "text-muted-foreground mt-0.5 text-[11px]",
47
+ children: staleLabel
48
+ }) : null
49
+ ]
50
+ }),
51
+ /* @__PURE__ */ jsxs("div", {
52
+ className: "flex shrink-0 items-center gap-0.5",
53
+ children: [onToggleResolved === void 0 ? null : /* @__PURE__ */ jsx(Button, {
54
+ "aria-label": resolved ? reopenLabel : resolveLabel,
55
+ onClick: onToggleResolved,
56
+ size: "icon-xs",
57
+ variant: "ghost",
58
+ children: resolved ? /* @__PURE__ */ jsx(RotateCcwIcon, {}) : /* @__PURE__ */ jsx(CheckIcon, {})
59
+ }), canDelete && onDelete !== void 0 ? /* @__PURE__ */ jsx(Button, {
60
+ "aria-label": deleteLabel,
61
+ onClick: onDelete,
62
+ size: "icon-xs",
63
+ variant: "ghost",
64
+ children: /* @__PURE__ */ jsx(Trash2Icon, {})
65
+ }) : null]
66
+ })
67
+ ]
68
+ });
69
+ /** `<time dateTime>` wants a machine-readable instant. An unparseable date
70
+ * yields no attribute rather than throwing on `toISOString`. */
71
+ const toIsoInstant = (timestamp) => {
72
+ if (typeof timestamp === "string") return timestamp;
73
+ return Number.isNaN(timestamp.getTime()) ? void 0 : timestamp.toISOString();
74
+ };
75
+ //#endregion
76
+ export { ReviewCommentCard };
@@ -0,0 +1,32 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/review/review-decision-actions.d.ts
3
+ /**
4
+ * Where a reviewable item stands. Undecided items offer accept/reject;
5
+ * decided ones offer the way back (revert the applied change, or reopen the
6
+ * decision). `applying` is undecided but in flight: the same pair, inert,
7
+ * so the row does not resize while the write lands.
8
+ */
9
+ type ReviewDecisionState = "pending" | "accepted" | "rejected" | "dismissed" | "applying";
10
+ type ReviewDecisionSize = "xs" | "sm";
11
+ type ReviewDecisionActionsProps = {
12
+ state: ReviewDecisionState;
13
+ onAccept?: (() => void) | undefined;
14
+ onReject?: (() => void) | undefined;
15
+ onRevert?: (() => void) | undefined;
16
+ onReopen?: (() => void) | undefined;
17
+ size?: ReviewDecisionSize;
18
+ /** Labels are supplied by the host: this package carries no catalogs. */
19
+ acceptLabel?: ReactNode;
20
+ rejectLabel?: ReactNode;
21
+ revertLabel?: ReactNode;
22
+ /** Accessible name for the icon-only reopen control. */
23
+ reopenLabel?: string | undefined;
24
+ acceptTooltip?: ReactNode;
25
+ rejectTooltip?: ReactNode;
26
+ disabled?: boolean;
27
+ className?: string;
28
+ };
29
+ /** Accept / reject / revert / reopen, in one shape, for every review surface. */
30
+ declare const ReviewDecisionActions: ({ state, onAccept, onReject, onRevert, onReopen, size, acceptLabel, rejectLabel, revertLabel, reopenLabel, acceptTooltip, rejectTooltip, disabled, className }: ReviewDecisionActionsProps) => import("react").JSX.Element | null;
31
+ //#endregion
32
+ export { ReviewDecisionActions, ReviewDecisionSize, ReviewDecisionState };
@@ -0,0 +1,74 @@
1
+ import { cn } from "../lib/utils.js";
2
+ import { Button } from "../components/button.js";
3
+ import { CheckIcon, RotateCcwIcon, XIcon } from "lucide-react";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/review/review-decision-actions.tsx
6
+ /** Accept / reject / revert / reopen, in one shape, for every review surface. */
7
+ const ReviewDecisionActions = ({ state, onAccept, onReject, onRevert, onReopen, size = "sm", acceptLabel, rejectLabel, revertLabel, reopenLabel, acceptTooltip, rejectTooltip, disabled = false, className }) => {
8
+ const controls = renderControls({
9
+ acceptLabel,
10
+ acceptTooltip,
11
+ disabled,
12
+ onAccept,
13
+ onReject,
14
+ onReopen,
15
+ onRevert,
16
+ rejectLabel,
17
+ rejectTooltip,
18
+ reopenLabel,
19
+ revertLabel,
20
+ size,
21
+ state
22
+ });
23
+ if (controls === null) return null;
24
+ return /* @__PURE__ */ jsx("div", {
25
+ className: cn("flex items-center gap-1.5", className),
26
+ "data-slot": "review-decision-actions",
27
+ children: controls
28
+ });
29
+ };
30
+ const renderControls = ({ acceptLabel, acceptTooltip, disabled, onAccept, onReject, onReopen, onRevert, rejectLabel, rejectTooltip, reopenLabel, revertLabel, size, state }) => {
31
+ switch (state) {
32
+ case "pending":
33
+ case "applying": {
34
+ const inert = disabled || state === "applying";
35
+ return /* @__PURE__ */ jsxs(Fragment, { children: [onAccept === void 0 ? null : /* @__PURE__ */ jsxs(Button, {
36
+ disabled: inert,
37
+ onClick: onAccept,
38
+ size,
39
+ tooltip: acceptTooltip,
40
+ variant: "default",
41
+ children: [/* @__PURE__ */ jsx(CheckIcon, {}), acceptLabel]
42
+ }), onReject === void 0 ? null : /* @__PURE__ */ jsxs(Button, {
43
+ disabled: inert,
44
+ onClick: onReject,
45
+ size,
46
+ tooltip: rejectTooltip,
47
+ variant: "outline",
48
+ children: [/* @__PURE__ */ jsx(XIcon, {}), rejectLabel]
49
+ })] });
50
+ }
51
+ case "accepted":
52
+ case "rejected":
53
+ case "dismissed":
54
+ if (onRevert === void 0 && onReopen === void 0) return null;
55
+ return /* @__PURE__ */ jsxs(Fragment, { children: [onRevert === void 0 ? null : /* @__PURE__ */ jsx(Button, {
56
+ className: "text-muted-foreground hover:text-foreground",
57
+ disabled,
58
+ onClick: onRevert,
59
+ size,
60
+ variant: "ghost",
61
+ children: revertLabel
62
+ }), onReopen === void 0 ? null : /* @__PURE__ */ jsx(Button, {
63
+ "aria-label": reopenLabel,
64
+ disabled,
65
+ onClick: onReopen,
66
+ size: size === "xs" ? "icon-xs" : "icon-sm",
67
+ variant: "ghost",
68
+ children: /* @__PURE__ */ jsx(RotateCcwIcon, {})
69
+ })] });
70
+ default: return null;
71
+ }
72
+ };
73
+ //#endregion
74
+ export { ReviewDecisionActions };
@@ -0,0 +1,33 @@
1
+ import { CSSProperties, ReactNode } from "react";
2
+ //#region src/review/review-diff-text.d.ts
3
+ declare const TRACKED_DELETION_STYLE: CSSProperties;
4
+ declare const TRACKED_INSERTION_STYLE: CSSProperties;
5
+ type ReviewDiffSegmentType = "equal" | "insert" | "delete";
6
+ type ReviewDiffSegment = {
7
+ type: ReviewDiffSegmentType;
8
+ text: string;
9
+ };
10
+ type ReviewDiffProps = {
11
+ className?: string;
12
+ children: ReactNode;
13
+ };
14
+ /** Inserted text, wherever it is shown. */
15
+ declare const ReviewDiffInsertion: ({ className, children }: ReviewDiffProps) => import("react").JSX.Element;
16
+ /** Deleted text, wherever it is shown. */
17
+ declare const ReviewDiffDeletion: ({ className, children }: ReviewDiffProps) => import("react").JSX.Element;
18
+ type ReviewDiffTextProps = {
19
+ segments: readonly ReviewDiffSegment[];
20
+ className?: string;
21
+ };
22
+ /** A word-level diff rendered inline, in the product's one track-changes
23
+ * language. */
24
+ declare const ReviewDiffText: ({ segments, className }: ReviewDiffTextProps) => import("react").JSX.Element;
25
+ /**
26
+ * A stable render key per segment. Type and text alone repeat inside a single
27
+ * diff (the same word deleted twice), so each repetition carries its
28
+ * occurrence count; the result is stable across renders of the same diff and
29
+ * never falls back to the array index.
30
+ */
31
+ declare const reviewDiffSegmentKeys: (segments: readonly ReviewDiffSegment[]) => string[];
32
+ //#endregion
33
+ export { ReviewDiffDeletion, ReviewDiffInsertion, ReviewDiffSegment, ReviewDiffSegmentType, ReviewDiffText, TRACKED_DELETION_STYLE, TRACKED_INSERTION_STYLE, reviewDiffSegmentKeys };