@ciridae/ui 0.8.0 → 0.8.2
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/dist/components/blocks/feedback-center-comments.js +2 -1
- package/dist/components/blocks/feedback-center-sheet.js +2 -2
- package/dist/components/blocks/feedback-sheet.js +2 -2
- package/dist/components/blocks/kanban-board-parts.d.ts +1 -1
- package/dist/components/blocks/kanban-board-parts.js +0 -2
- package/dist/components/ui/color-theme.js +0 -2
- package/dist/components/ui/form-sheet.d.ts +7 -0
- package/dist/components/ui/form-sheet.js +35 -67
- package/dist/lib/browser-storage.d.ts +4 -5
- package/dist/lib/browser-storage.js +5 -6
- package/package.json +1 -1
|
@@ -4,7 +4,8 @@ import { CommentThread } from "./comment-thread";
|
|
|
4
4
|
import { Alert, AlertDescription, AlertTitle } from "../ui/alert";
|
|
5
5
|
import { Button } from "../ui/button";
|
|
6
6
|
function FeedbackCenterCommentThread({ comments, error, feedback, isCommentSubmitting, isCommentsLoading, onRetry, onSubmitComment, }) {
|
|
7
|
-
|
|
7
|
+
// A retry in flight shows the thread's loading state, not the stale error.
|
|
8
|
+
if (error && !isCommentsLoading) {
|
|
8
9
|
return (_jsxs(Alert, { variant: "destructive", children: [_jsx(AlertCircleIcon, {}), _jsx(AlertTitle, { children: "Conversation could not be loaded" }), _jsxs(AlertDescription, { className: "grid gap-3", children: [_jsx("span", { children: error.message }), onRetry ? (_jsx(Button, { className: "justify-self-start", onClick: onRetry, size: "sm", type: "button", variant: "outline", children: "Try again" })) : null] })] }));
|
|
9
10
|
}
|
|
10
11
|
return (_jsx(CommentThread, { comments: comments, composerPlaceholder: "Write a reply...", composerSubmitLabel: "Reply", isLoading: isCommentsLoading, isSubmitting: isCommentSubmitting, onSubmit: getCommentSubmitHandler(feedback, onSubmitComment), readOnly: !onSubmitComment, showHeader: false }));
|
|
@@ -10,8 +10,8 @@ function FeedbackCenterSheet(props) {
|
|
|
10
10
|
return (_jsx(FormSheet, { closeDisabled: props.isSubmitting ?? false, isDirty: state.isDirty, onOpenChange: props.onOpenChange, open: props.open, children: _jsx(FeedbackCenterSheetContent, { props: props, state: state }) }));
|
|
11
11
|
}
|
|
12
12
|
function FeedbackCenterSheetContent({ props, state, }) {
|
|
13
|
-
const { requestClose } = useFormSheetActions();
|
|
14
|
-
return (_jsx(SheetContent, { size: "md", children: _jsxs(Tabs, { className: "min-h-0 flex-1", onValueChange: setFeedbackCenterTab(state.handleTabChange), value: state.activeTab, children: [_jsx(FeedbackCenterSheetHeader, { unreadReplyCount: state.unreadReplyCount }), _jsx(SheetBody, { className: "pb-(--panel-padding)", children: _jsx(FeedbackCenterTabs, { composerProps: getFeedbackComposerProps(props), commentsByFeedbackId: props.commentsByFeedbackId ?? {}, commentsError: props.commentsError, formId: state.formId, isCommentSubmitting: props.isCommentSubmitting ?? false, isCommentsLoading: props.isCommentsLoading ?? false, isLoading: props.isLoading ?? false, items: props.items, itemsError: props.itemsError, onBack: state.handleBack, onCancel: requestClose, onDirtyChange: state.setIsDirty, onRetryComments: props.onRetryComments, onRetryItems: props.onRetryItems, onSelectFeedback: state.handleSelectFeedback, onSubmitComment: props.onSubmitComment, onSubmittedChange: state.setIsSubmitted, onUrgencyChange: state.setUrgency, renderEvidence: props.renderEvidence, selectedFeedback: state.selectedFeedback, view: state.view }) }), _jsx(FeedbackCenterFooter, { activeTab: state.activeTab, formId: state.formId, isSubmitted: state.isSubmitted, isSubmitting: props.isSubmitting ?? false, onCancel: requestClose, urgency: state.urgency })] }) }));
|
|
13
|
+
const { contentProps, requestClose } = useFormSheetActions();
|
|
14
|
+
return (_jsx(SheetContent, { size: "md", ...contentProps, children: _jsxs(Tabs, { className: "min-h-0 flex-1", onValueChange: setFeedbackCenterTab(state.handleTabChange), value: state.activeTab, children: [_jsx(FeedbackCenterSheetHeader, { unreadReplyCount: state.unreadReplyCount }), _jsx(SheetBody, { className: "pb-(--panel-padding)", children: _jsx(FeedbackCenterTabs, { composerProps: getFeedbackComposerProps(props), commentsByFeedbackId: props.commentsByFeedbackId ?? {}, commentsError: props.commentsError, formId: state.formId, isCommentSubmitting: props.isCommentSubmitting ?? false, isCommentsLoading: props.isCommentsLoading ?? false, isLoading: props.isLoading ?? false, items: props.items, itemsError: props.itemsError, onBack: state.handleBack, onCancel: requestClose, onDirtyChange: state.setIsDirty, onRetryComments: props.onRetryComments, onRetryItems: props.onRetryItems, onSelectFeedback: state.handleSelectFeedback, onSubmitComment: props.onSubmitComment, onSubmittedChange: state.setIsSubmitted, onUrgencyChange: state.setUrgency, renderEvidence: props.renderEvidence, selectedFeedback: state.selectedFeedback, view: state.view }) }), _jsx(FeedbackCenterFooter, { activeTab: state.activeTab, formId: state.formId, isSubmitted: state.isSubmitted, isSubmitting: props.isSubmitting ?? false, onCancel: requestClose, urgency: state.urgency })] }) }));
|
|
15
15
|
}
|
|
16
16
|
function useFeedbackCenterSheetState(props) {
|
|
17
17
|
const defaultTab = props.defaultTab ?? "submit";
|
|
@@ -14,7 +14,7 @@ function FeedbackSheet({ onOpenChange, open, ...composerProps }) {
|
|
|
14
14
|
return (_jsx(FormSheet, { closeDisabled: composerProps.isSubmitting ?? false, isDirty: isDirty, onOpenChange: onOpenChange, open: open, children: _jsx(FeedbackSheetContent, { composerProps: composerProps, onDirtyChange: setIsDirty, open: open }) }));
|
|
15
15
|
}
|
|
16
16
|
function FeedbackSheetContent({ composerProps, onDirtyChange, open, }) {
|
|
17
|
-
const { requestClose } = useFormSheetActions();
|
|
17
|
+
const { contentProps, requestClose } = useFormSheetActions();
|
|
18
18
|
const formId = React.useId();
|
|
19
19
|
const [isSubmitted, setIsSubmitted] = React.useState(false);
|
|
20
20
|
const [urgency, setUrgency] = React.useState("normal");
|
|
@@ -24,6 +24,6 @@ function FeedbackSheetContent({ composerProps, onDirtyChange, open, }) {
|
|
|
24
24
|
setUrgency("normal");
|
|
25
25
|
}
|
|
26
26
|
}, [open]);
|
|
27
|
-
return (_jsxs(SheetContent, { size: "md", children: [_jsxs(SheetHeader, { children: [_jsx(SheetTitle, { children: "Send feedback" }), _jsx(SheetDescription, { children: "Share a bug, feature request, or general note." })] }), _jsx(SheetBody, { children: _jsx(FeedbackComposer, { ...composerProps, actions: "external", formId: formId, onCancel: requestClose, onDirtyChange: onDirtyChange, onSubmittedChange: setIsSubmitted, onUrgencyChange: setUrgency }) }), isSubmitted ? null : (_jsxs(SheetFooter, { children: [_jsx(Button, { disabled: composerProps.isSubmitting, onClick: requestClose, type: "button", variant: "outline", children: "Cancel" }), _jsxs(Button, { form: formId, loading: composerProps.isSubmitting, type: "submit", variant: urgency === "emergency" ? "destructive" : "default", children: [urgency === "emergency" ? _jsx(AlertTriangleIcon, {}) : _jsx(SendIcon, {}), urgency === "emergency" ? "Send emergency" : "Submit feedback"] })] }))] }));
|
|
27
|
+
return (_jsxs(SheetContent, { size: "md", ...contentProps, children: [_jsxs(SheetHeader, { children: [_jsx(SheetTitle, { children: "Send feedback" }), _jsx(SheetDescription, { children: "Share a bug, feature request, or general note." })] }), _jsx(SheetBody, { children: _jsx(FeedbackComposer, { ...composerProps, actions: "external", formId: formId, onCancel: requestClose, onDirtyChange: onDirtyChange, onSubmittedChange: setIsSubmitted, onUrgencyChange: setUrgency }) }), isSubmitted ? null : (_jsxs(SheetFooter, { children: [_jsx(Button, { disabled: composerProps.isSubmitting, onClick: requestClose, type: "button", variant: "outline", children: "Cancel" }), _jsxs(Button, { form: formId, loading: composerProps.isSubmitting, type: "submit", variant: urgency === "emergency" ? "destructive" : "default", children: [urgency === "emergency" ? _jsx(AlertTriangleIcon, {}) : _jsx(SendIcon, {}), urgency === "emergency" ? "Send emergency" : "Submit feedback"] })] }))] }));
|
|
28
28
|
}
|
|
29
29
|
export { FeedbackSheet };
|
|
@@ -22,5 +22,5 @@ declare function KanbanBoardColumn({ activeCardId, activeCardRect, activeMove, b
|
|
|
22
22
|
declare function KanbanDragOverlay({ activeCard, activeCardRect, }: {
|
|
23
23
|
activeCard: KanbanCardData | undefined;
|
|
24
24
|
activeCardRect: KanbanOverlayRect | undefined;
|
|
25
|
-
}): React.
|
|
25
|
+
}): React.ReactPortal;
|
|
26
26
|
export { KanbanBoardColumn, KanbanDragOverlay };
|
|
@@ -45,8 +45,6 @@ function KanbanDropPlaceholder({ height }) {
|
|
|
45
45
|
}
|
|
46
46
|
function KanbanDragOverlay({ activeCard, activeCardRect, }) {
|
|
47
47
|
const overlay = (_jsx(DragOverlay, { adjustScale: false, children: activeCard ? (_jsx("div", { style: { width: activeCardRect?.width }, children: _jsx(KanbanCard, { card: activeCard, isDragging: true }) })) : null }));
|
|
48
|
-
if (typeof document === "undefined")
|
|
49
|
-
return overlay;
|
|
50
48
|
return createPortal(overlay, document.body);
|
|
51
49
|
}
|
|
52
50
|
function getSortableCardClickHandler({ card, onCardSelect, suppressSelectRef, }) {
|
|
@@ -139,8 +139,6 @@ function parseStoredPalette(value) {
|
|
|
139
139
|
return null;
|
|
140
140
|
}
|
|
141
141
|
function getSystemTheme() {
|
|
142
|
-
if (typeof window === "undefined")
|
|
143
|
-
return "light";
|
|
144
142
|
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
145
143
|
}
|
|
146
144
|
export { ThemeProvider };
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
type FormSheetActions = Readonly<{
|
|
3
|
+
/**
|
|
4
|
+
* Spread onto the `SheetContent` this form sheet wraps. Carries the
|
|
5
|
+
* close-focus handler; without it, closing drops focus on `<body>`.
|
|
6
|
+
*/
|
|
7
|
+
contentProps: Readonly<{
|
|
8
|
+
onCloseAutoFocus: (event: Event) => void;
|
|
9
|
+
}>;
|
|
3
10
|
requestClose: () => void;
|
|
4
11
|
}>;
|
|
5
12
|
type FormSheetProps = {
|
|
@@ -11,13 +11,14 @@ function useFormSheetActions() {
|
|
|
11
11
|
}
|
|
12
12
|
function FormSheet({ children, closeDisabled = false, isDirty, onOpenChange, open, trigger, }) {
|
|
13
13
|
const [discardPromptOpen, setDiscardPromptOpen] = React.useState(false);
|
|
14
|
+
const sheetReturnFocusRef = React.useRef(null);
|
|
14
15
|
const discardReturnFocusRef = React.useRef(null);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}, [
|
|
16
|
+
// Layout effect: parent layout effects run before the sheet's focus scope
|
|
17
|
+
// captures on mount, so this still sees the element focused before opening.
|
|
18
|
+
React.useLayoutEffect(() => {
|
|
19
|
+
if (open)
|
|
20
|
+
sheetReturnFocusRef.current = getActiveElement();
|
|
21
|
+
}, [open]);
|
|
21
22
|
const requestClose = React.useCallback(() => {
|
|
22
23
|
if (closeDisabled)
|
|
23
24
|
return;
|
|
@@ -26,81 +27,48 @@ function FormSheet({ children, closeDisabled = false, isDirty, onOpenChange, ope
|
|
|
26
27
|
setDiscardPromptOpen(true);
|
|
27
28
|
return;
|
|
28
29
|
}
|
|
29
|
-
|
|
30
|
-
}, [closeDisabled,
|
|
31
|
-
const
|
|
30
|
+
onOpenChange(false);
|
|
31
|
+
}, [closeDisabled, isDirty, onOpenChange]);
|
|
32
|
+
const handleSheetCloseAutoFocus = React.useCallback((event) => {
|
|
33
|
+
returnFocusTo(event, sheetReturnFocusRef.current);
|
|
34
|
+
}, []);
|
|
35
|
+
const handleDiscardCloseAutoFocus = React.useCallback((event) => {
|
|
36
|
+
returnFocusTo(event, discardReturnFocusRef.current);
|
|
37
|
+
}, []);
|
|
38
|
+
const actions = React.useMemo(() => ({
|
|
39
|
+
contentProps: { onCloseAutoFocus: handleSheetCloseAutoFocus },
|
|
40
|
+
requestClose,
|
|
41
|
+
}), [handleSheetCloseAutoFocus, requestClose]);
|
|
32
42
|
function handleOpenChange(nextOpen) {
|
|
33
43
|
if (nextOpen) {
|
|
34
|
-
returnFocus.capture();
|
|
35
44
|
onOpenChange(true);
|
|
36
45
|
return;
|
|
37
46
|
}
|
|
38
47
|
requestClose();
|
|
39
48
|
}
|
|
40
49
|
function discardChanges() {
|
|
41
|
-
discardingRef.current = true;
|
|
42
50
|
setDiscardPromptOpen(false);
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
function handleDiscardPromptOpenChange(nextOpen) {
|
|
46
|
-
setDiscardPromptOpen(nextOpen);
|
|
47
|
-
if (!nextOpen && !discardingRef.current) {
|
|
48
|
-
requestAnimationFrame(() => discardReturnFocusRef.current?.focus());
|
|
49
|
-
}
|
|
50
|
-
discardingRef.current = false;
|
|
51
|
+
onOpenChange(false);
|
|
51
52
|
}
|
|
52
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Sheet, { onOpenChange: handleOpenChange, open: open, children: [trigger ? _jsx(SheetTrigger, { asChild: true, children: trigger }) : null, _jsx(FormSheetContext.Provider, { value: actions, children: children })] }), _jsx(DiscardChangesPrompt, { onDiscard: discardChanges, onOpenChange:
|
|
53
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Sheet, { onOpenChange: handleOpenChange, open: open, children: [trigger ? _jsx(SheetTrigger, { asChild: true, children: trigger }) : null, _jsx(FormSheetContext.Provider, { value: actions, children: children })] }), _jsx(DiscardChangesPrompt, { onCloseAutoFocus: handleDiscardCloseAutoFocus, onDiscard: discardChanges, onOpenChange: setDiscardPromptOpen, open: discardPromptOpen })] }));
|
|
53
54
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
focusFrameRef.current = null;
|
|
67
|
-
if (returnFocus.isConnected)
|
|
68
|
-
returnFocus.focus();
|
|
69
|
-
});
|
|
70
|
-
}, []);
|
|
71
|
-
React.useEffect(() => {
|
|
72
|
-
if (open) {
|
|
73
|
-
if (!wasOpenRef.current)
|
|
74
|
-
returnFocusRef.current = activeElementDuringRender;
|
|
75
|
-
wasOpenRef.current = true;
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
if (!wasOpenRef.current)
|
|
79
|
-
return;
|
|
80
|
-
wasOpenRef.current = false;
|
|
81
|
-
schedule();
|
|
82
|
-
}, [activeElementDuringRender, open, schedule]);
|
|
83
|
-
React.useEffect(() => () => {
|
|
84
|
-
if (wasOpenRef.current)
|
|
85
|
-
schedule();
|
|
86
|
-
}, [schedule]);
|
|
87
|
-
const capture = React.useCallback(() => {
|
|
88
|
-
returnFocusRef.current = getActiveElement();
|
|
89
|
-
wasOpenRef.current = true;
|
|
90
|
-
}, []);
|
|
91
|
-
const prepareForClose = React.useCallback(() => {
|
|
92
|
-
schedule();
|
|
93
|
-
wasOpenRef.current = false;
|
|
94
|
-
}, [schedule]);
|
|
95
|
-
return React.useMemo(() => ({ capture, prepareForClose }), [capture, prepareForClose]);
|
|
55
|
+
/**
|
|
56
|
+
* Radix aims its close-focus at the overlay's trigger. A controlled sheet has
|
|
57
|
+
* no trigger, so Radix suppresses the focus scope's own fallback and then
|
|
58
|
+
* focuses nothing, leaving `<body>` active. Restoring here rather than on a
|
|
59
|
+
* timer is what keeps the timing right: Radix fires this after the content
|
|
60
|
+
* unmounts, which is the moment the element is safe to focus again.
|
|
61
|
+
*/
|
|
62
|
+
function returnFocusTo(event, returnFocus) {
|
|
63
|
+
if (!returnFocus?.isConnected)
|
|
64
|
+
return; // discarded: the sheet owns focus from here
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
returnFocus.focus();
|
|
96
67
|
}
|
|
97
|
-
function DiscardChangesPrompt({ onDiscard, onOpenChange, open, }) {
|
|
98
|
-
return (_jsx(AlertDialog, { onOpenChange: onOpenChange, open: open, children: _jsxs(AlertDialogContent, { size: "sm", children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Discard unsaved changes?" }), _jsx(AlertDialogDescription, { children: "Your changes have not been saved. Keep editing or discard them and close this panel." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Keep editing" }), _jsx(AlertDialogAction, { onClick: onDiscard, variant: "destructive", children: "Discard changes" })] })] }) }));
|
|
68
|
+
function DiscardChangesPrompt({ onCloseAutoFocus, onDiscard, onOpenChange, open, }) {
|
|
69
|
+
return (_jsx(AlertDialog, { onOpenChange: onOpenChange, open: open, children: _jsxs(AlertDialogContent, { onCloseAutoFocus: onCloseAutoFocus, size: "sm", children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Discard unsaved changes?" }), _jsx(AlertDialogDescription, { children: "Your changes have not been saved. Keep editing or discard them and close this panel." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Keep editing" }), _jsx(AlertDialogAction, { onClick: onDiscard, variant: "destructive", children: "Discard changes" })] })] }) }));
|
|
99
70
|
}
|
|
100
71
|
function getActiveElement() {
|
|
101
|
-
// Called during render, so it must be safe on the server too.
|
|
102
|
-
if (typeof document === "undefined")
|
|
103
|
-
return null;
|
|
104
72
|
return document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
105
73
|
}
|
|
106
74
|
export { FormSheet, useFormSheetActions };
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* localStorage that cannot throw at its callers.
|
|
3
3
|
*
|
|
4
|
-
* Every access can raise: the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* in-memory state.
|
|
4
|
+
* Every access can raise: reading the property throws when storage is disabled
|
|
5
|
+
* by policy or by Safari's private mode, and `setItem` throws on quota. A
|
|
6
|
+
* preference is worth remembering and never worth failing over, so each call
|
|
7
|
+
* degrades to "no stored value" and the caller keeps its in-memory state.
|
|
9
8
|
*/
|
|
10
9
|
declare function readStoredValue(key: string): string | null;
|
|
11
10
|
declare function writeStoredValue(key: string, value: string): void;
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* localStorage that cannot throw at its callers.
|
|
3
3
|
*
|
|
4
|
-
* Every access can raise: the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* in-memory state.
|
|
4
|
+
* Every access can raise: reading the property throws when storage is disabled
|
|
5
|
+
* by policy or by Safari's private mode, and `setItem` throws on quota. A
|
|
6
|
+
* preference is worth remembering and never worth failing over, so each call
|
|
7
|
+
* degrades to "no stored value" and the caller keeps its in-memory state.
|
|
9
8
|
*/
|
|
10
9
|
function storage() {
|
|
11
10
|
try {
|
|
12
|
-
return
|
|
11
|
+
return window.localStorage;
|
|
13
12
|
}
|
|
14
13
|
catch {
|
|
15
14
|
// Accessing the property itself throws when storage is blocked by policy.
|