@ciridae/ui 0.3.2 → 0.3.5
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/assets/ciridae/brand-background-dark.webp +0 -0
- package/dist/assets/ciridae/brand-background-light.webp +0 -0
- package/dist/assets/ciridae/ciridae-black.svg +17 -0
- package/dist/assets/ciridae/ciridae-brand-assets.d.ts +25 -0
- package/dist/assets/ciridae/ciridae-brand-assets.js +25 -0
- package/dist/assets/ciridae/ciridae-icon-black.png +0 -0
- package/dist/assets/ciridae/ciridae-icon-white.png +0 -0
- package/dist/assets/ciridae/ciridae-ui-wordmark-black.png +0 -0
- package/dist/assets/ciridae/ciridae-ui-wordmark-white.png +0 -0
- package/dist/assets/ciridae/ciridae-white.svg +17 -0
- package/dist/assets/ciridae/ciridae-wordmark-black.png +0 -0
- package/dist/assets/ciridae/ciridae-wordmark-white.png +0 -0
- package/dist/assets/ciridae/favicon.ico +0 -0
- package/dist/assets/ciridae/logo-white.png +0 -0
- package/dist/assets/ciridae/sign-in-screenshot.png +0 -0
- package/dist/components/blocks/admin-users-table.d.ts +22 -0
- package/dist/components/blocks/admin-users-table.js +115 -0
- package/dist/components/blocks/comment-composer.d.ts +16 -0
- package/dist/components/blocks/comment-composer.js +113 -0
- package/dist/components/blocks/comment-item.d.ts +8 -0
- package/dist/components/blocks/comment-item.js +45 -0
- package/dist/components/blocks/comment-thread.d.ts +18 -0
- package/dist/components/blocks/comment-thread.js +30 -0
- package/dist/components/blocks/comment-types.d.ts +18 -0
- package/dist/components/blocks/comment-types.js +11 -0
- package/dist/components/blocks/feedback-center-comments.d.ts +12 -0
- package/dist/components/blocks/feedback-center-comments.js +11 -0
- package/dist/components/blocks/feedback-center-sheet-parts.d.ts +34 -0
- package/dist/components/blocks/feedback-center-sheet-parts.js +44 -0
- package/dist/components/blocks/feedback-center-sheet.d.ts +19 -0
- package/dist/components/blocks/feedback-center-sheet.js +46 -0
- package/dist/components/blocks/feedback-composer-parts.d.ts +16 -0
- package/dist/components/blocks/feedback-composer-parts.js +43 -0
- package/dist/components/blocks/feedback-composer-types.d.ts +11 -0
- package/dist/components/blocks/feedback-composer-types.js +1 -0
- package/dist/components/blocks/feedback-composer.d.ts +17 -0
- package/dist/components/blocks/feedback-composer.js +112 -0
- package/dist/components/blocks/feedback-date-format.d.ts +5 -0
- package/dist/components/blocks/feedback-date-format.js +11 -0
- package/dist/components/blocks/feedback-detail-sheet.d.ts +9 -0
- package/dist/components/blocks/feedback-detail-sheet.js +41 -0
- package/dist/components/blocks/feedback-metrics.d.ts +6 -0
- package/dist/components/blocks/feedback-metrics.js +15 -0
- package/dist/components/blocks/feedback-sheet.d.ts +8 -0
- package/dist/components/blocks/feedback-sheet.js +16 -0
- package/dist/components/blocks/feedback-status-badge.d.ts +6 -0
- package/dist/components/blocks/feedback-status-badge.js +14 -0
- package/dist/components/blocks/feedback-table-row.d.ts +6 -0
- package/dist/components/blocks/feedback-table-row.js +37 -0
- package/dist/components/blocks/feedback-table-sort.d.ts +11 -0
- package/dist/components/blocks/feedback-table-sort.js +81 -0
- package/dist/components/blocks/feedback-table.d.ts +16 -0
- package/dist/components/blocks/feedback-table.js +45 -0
- package/dist/components/blocks/feedback-tracking-types.d.ts +78 -0
- package/dist/components/blocks/feedback-tracking-types.js +36 -0
- package/dist/components/blocks/feedback-type-badge.d.ts +6 -0
- package/dist/components/blocks/feedback-type-badge.js +12 -0
- package/dist/components/blocks/feedback-user-detail.d.ts +10 -0
- package/dist/components/blocks/feedback-user-detail.js +57 -0
- package/dist/components/blocks/feedback-user-list.d.ts +9 -0
- package/dist/components/blocks/feedback-user-list.js +33 -0
- package/dist/components/blocks/invite-user-sheet.d.ts +20 -0
- package/dist/components/blocks/invite-user-sheet.js +72 -0
- package/dist/components/blocks/kanban-board-parts.d.ts +26 -0
- package/dist/components/blocks/kanban-board-parts.js +85 -0
- package/dist/components/blocks/kanban-board-utils.d.ts +42 -0
- package/dist/components/blocks/kanban-board-utils.js +115 -0
- package/dist/components/blocks/kanban-board.d.ts +16 -0
- package/dist/components/blocks/kanban-board.js +36 -0
- package/dist/components/blocks/kanban-card.d.ts +10 -0
- package/dist/components/blocks/kanban-card.js +34 -0
- package/dist/components/blocks/kanban-column.d.ts +17 -0
- package/dist/components/blocks/kanban-column.js +34 -0
- package/dist/components/blocks/kanban-detail-sheet.d.ts +17 -0
- package/dist/components/blocks/kanban-detail-sheet.js +34 -0
- package/dist/components/blocks/kanban-types.d.ts +24 -0
- package/dist/components/blocks/kanban-types.js +1 -0
- package/dist/components/blocks/timestamp-format.d.ts +8 -0
- package/dist/components/blocks/timestamp-format.js +66 -0
- package/dist/components/pages/admin-users-page.d.ts +11 -0
- package/dist/components/pages/admin-users-page.js +30 -0
- package/dist/components/pages/feedback-stakeholder-page-parts.d.ts +18 -0
- package/dist/components/pages/feedback-stakeholder-page-parts.js +36 -0
- package/dist/components/pages/feedback-stakeholder-page.d.ts +9 -0
- package/dist/components/pages/feedback-stakeholder-page.js +66 -0
- package/dist/components/pages/kanban-page.d.ts +13 -0
- package/dist/components/pages/kanban-page.js +68 -0
- package/dist/components/ui/calendar.d.ts +2 -1
- package/dist/components/ui/calendar.js +1 -1
- package/dist/components/ui/dialog.d.ts +2 -1
- package/dist/components/ui/dialog.js +2 -2
- package/dist/components/ui/page-header.d.ts +15 -1
- package/dist/components/ui/page-header.js +20 -2
- package/dist/components/ui/resizable.d.ts +9 -2
- package/dist/components/ui/resizable.js +3 -3
- package/dist/components/ui/sidebar-account-menu.d.ts +3 -1
- package/dist/components/ui/sidebar-account-menu.js +5 -5
- package/dist/components/ui/sonner.d.ts +2 -2
- package/dist/components/ui/sonner.js +2 -2
- package/dist/styled-system/css/css.d.ts +11 -16
- package/dist/styled-system/css/cva.d.ts +2 -4
- package/dist/styled-system/css/cx.d.ts +2 -3
- package/dist/styled-system/css/index.d.ts +1 -1
- package/dist/styled-system/css/sva.d.ts +1 -2
- package/dist/styled-system/patterns/aspect-ratio.d.ts +5 -9
- package/dist/styled-system/patterns/bleed.d.ts +6 -10
- package/dist/styled-system/patterns/box.d.ts +4 -9
- package/dist/styled-system/patterns/center.d.ts +5 -9
- package/dist/styled-system/patterns/circle.d.ts +5 -9
- package/dist/styled-system/patterns/container.d.ts +4 -9
- package/dist/styled-system/patterns/cq.d.ts +6 -10
- package/dist/styled-system/patterns/divider.d.ts +7 -11
- package/dist/styled-system/patterns/flex.d.ts +11 -15
- package/dist/styled-system/patterns/float.d.ts +8 -12
- package/dist/styled-system/patterns/grid-item.d.ts +10 -14
- package/dist/styled-system/patterns/grid.d.ts +9 -13
- package/dist/styled-system/patterns/hstack.d.ts +6 -10
- package/dist/styled-system/patterns/index.d.ts +1 -1
- package/dist/styled-system/patterns/link-overlay.d.ts +4 -9
- package/dist/styled-system/patterns/spacer.d.ts +5 -9
- package/dist/styled-system/patterns/square.d.ts +5 -9
- package/dist/styled-system/patterns/stack.d.ts +8 -12
- package/dist/styled-system/patterns/visually-hidden.d.ts +4 -9
- package/dist/styled-system/patterns/vstack.d.ts +6 -10
- package/dist/styled-system/patterns/wrap.d.ts +9 -13
- package/dist/styled-system/tokens/index.d.ts +4 -6
- package/dist/styled-system/tokens/tokens.d.ts +42 -61
- package/dist/styled-system/types/composition.d.ts +20 -211
- package/dist/styled-system/types/conditions.d.ts +297 -303
- package/dist/styled-system/types/csstype.d.ts +20289 -22518
- package/dist/styled-system/types/global.d.ts +11 -12
- package/dist/styled-system/types/index.d.ts +2 -2
- package/dist/styled-system/types/parts.d.ts +2 -3
- package/dist/styled-system/types/pattern.d.ts +75 -71
- package/dist/styled-system/types/prop-type.d.ts +236 -233
- package/dist/styled-system/types/recipe.d.ts +114 -155
- package/dist/styled-system/types/selectors.d.ts +8 -58
- package/dist/styled-system/types/static-css.d.ts +40 -47
- package/dist/styled-system/types/style-props.d.ts +8078 -8082
- package/dist/styled-system/types/system-types.d.ts +69 -111
- package/dist/styles.css +490 -1
- package/package.json +159 -7
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { SendIcon } from "lucide-react";
|
|
3
|
+
import { FeedbackCenterCommentThread, } from "@ciridae/ui/feedback-center-comments";
|
|
4
|
+
import { FeedbackComposer } from "@ciridae/ui/feedback-composer";
|
|
5
|
+
import { FeedbackUserDetail } from "@ciridae/ui/feedback-user-detail";
|
|
6
|
+
import { FeedbackUserList } from "@ciridae/ui/feedback-user-list";
|
|
7
|
+
import { Button } from "@ciridae/ui/button";
|
|
8
|
+
import { SheetDescription, SheetFooter, SheetHeader, SheetTitle } from "@ciridae/ui/sheet";
|
|
9
|
+
import { TabsContent, TabsList, TabsTrigger } from "@ciridae/ui/tabs";
|
|
10
|
+
function FeedbackCenterSheetHeader() {
|
|
11
|
+
return (_jsxs(SheetHeader, { className: "gap-3", children: [_jsxs("div", { className: "grid gap-1.5 pr-12", children: [_jsx(SheetTitle, { children: "Feedback" }), _jsx(SheetDescription, { children: "Send feedback and track your submissions in one place." })] }), _jsx(FeedbackCenterTabsList, {})] }));
|
|
12
|
+
}
|
|
13
|
+
function FeedbackCenterTabs({ composerProps, commentsByFeedbackId, formId, isCommentSubmitting, isCommentsLoading, isLoading, items, onBack, onCancel, onSelectFeedback, onSubmittedChange, onSubmitComment, selectedFeedback, view, }) {
|
|
14
|
+
return (_jsxs(_Fragment, { children: [_jsx(FeedbackCenterSubmitTab, { composerProps: composerProps, formId: formId, onCancel: onCancel, onSubmittedChange: onSubmittedChange }), _jsx(FeedbackCenterMineContent, { commentsByFeedbackId: commentsByFeedbackId, isCommentSubmitting: isCommentSubmitting, isCommentsLoading: isCommentsLoading, isLoading: isLoading, items: items, onBack: onBack, onSelectFeedback: onSelectFeedback, onSubmitComment: onSubmitComment, selectedFeedback: selectedFeedback, view: view })] }));
|
|
15
|
+
}
|
|
16
|
+
function FeedbackCenterTabsList() {
|
|
17
|
+
return (_jsxs(TabsList, { className: "w-full", children: [_jsx(TabsTrigger, { value: "submit", children: "Send feedback" }), _jsx(TabsTrigger, { value: "mine", children: "My feedback" })] }));
|
|
18
|
+
}
|
|
19
|
+
function FeedbackCenterSubmitTab({ composerProps, formId, onCancel, onSubmittedChange, }) {
|
|
20
|
+
return (_jsx(TabsContent, { className: "mt-0", value: "submit", children: _jsx(FeedbackComposer, { ...composerProps, actions: "external", formId: formId, onCancel: onCancel, onSubmittedChange: onSubmittedChange }) }));
|
|
21
|
+
}
|
|
22
|
+
function FeedbackCenterFooter({ activeTab, formId, isSubmitted, isSubmitting, onCancel, }) {
|
|
23
|
+
if (activeTab !== "submit" || isSubmitted)
|
|
24
|
+
return null;
|
|
25
|
+
return (_jsxs(SheetFooter, { children: [_jsx(Button, { disabled: isSubmitting, onClick: onCancel, type: "button", variant: "outline", children: "Cancel" }), _jsxs(Button, { form: formId, loading: isSubmitting, type: "submit", children: [_jsx(SendIcon, {}), "Submit feedback"] })] }));
|
|
26
|
+
}
|
|
27
|
+
function FeedbackCenterMineContent(props) {
|
|
28
|
+
return (_jsx(TabsContent, { className: "mt-0", value: "mine", children: _jsx(FeedbackCenterMineTab, { ...props }) }));
|
|
29
|
+
}
|
|
30
|
+
function FeedbackCenterMineTab({ isLoading, commentsByFeedbackId, isCommentSubmitting, isCommentsLoading, items, onBack, onSelectFeedback, onSubmitComment, selectedFeedback, view, }) {
|
|
31
|
+
if (view === "detail") {
|
|
32
|
+
return (_jsx(FeedbackCenterDetailPane, { comments: getFeedbackComments(commentsByFeedbackId, selectedFeedback), feedback: selectedFeedback, isCommentSubmitting: isCommentSubmitting, isCommentsLoading: isCommentsLoading, onBack: onBack, onSubmitComment: onSubmitComment }));
|
|
33
|
+
}
|
|
34
|
+
return (_jsx(FeedbackUserList, { isLoading: isLoading, items: items, onSelectFeedback: onSelectFeedback, selectedId: selectedFeedback?.id }));
|
|
35
|
+
}
|
|
36
|
+
function FeedbackCenterDetailPane({ comments, feedback, isCommentSubmitting, isCommentsLoading, onBack, onSubmitComment, }) {
|
|
37
|
+
return (_jsx(FeedbackUserDetail, { activityLabel: "Comments", activitySlot: _jsx(FeedbackCenterCommentThread, { comments: comments, feedback: feedback, isCommentSubmitting: isCommentSubmitting, isCommentsLoading: isCommentsLoading, onSubmitComment: onSubmitComment }), feedback: feedback, onBack: onBack }));
|
|
38
|
+
}
|
|
39
|
+
function getFeedbackComments(commentsByFeedbackId, feedback) {
|
|
40
|
+
if (!feedback)
|
|
41
|
+
return [];
|
|
42
|
+
return commentsByFeedbackId[feedback.id] ?? [];
|
|
43
|
+
}
|
|
44
|
+
export { FeedbackCenterFooter, FeedbackCenterSheetHeader, FeedbackCenterTabs, };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { CommentItemData } from "@ciridae/ui/comment-types";
|
|
3
|
+
import type { FeedbackCommentSubmitHandler } from "@ciridae/ui/feedback-center-comments";
|
|
4
|
+
import { type FeedbackCenterTab } from "@ciridae/ui/feedback-center-sheet-parts";
|
|
5
|
+
import type { FeedbackComposerProps } from "@ciridae/ui/feedback-composer";
|
|
6
|
+
import type { UserFeedbackItem } from "@ciridae/ui/feedback-tracking-types";
|
|
7
|
+
type FeedbackCenterSheetProps = Omit<FeedbackComposerProps, "actions" | "formId" | "onCancel" | "onSubmittedChange"> & {
|
|
8
|
+
commentsByFeedbackId?: Record<string, CommentItemData[]>;
|
|
9
|
+
defaultTab?: FeedbackCenterTab;
|
|
10
|
+
isCommentSubmitting?: boolean;
|
|
11
|
+
isCommentsLoading?: boolean;
|
|
12
|
+
isLoading?: boolean;
|
|
13
|
+
items: UserFeedbackItem[];
|
|
14
|
+
onSubmitComment?: FeedbackCommentSubmitHandler;
|
|
15
|
+
onOpenChange: (open: boolean) => void;
|
|
16
|
+
open: boolean;
|
|
17
|
+
};
|
|
18
|
+
declare function FeedbackCenterSheet({ commentsByFeedbackId, defaultTab, isCommentSubmitting, isCommentsLoading, isLoading, items, onSubmitComment, onOpenChange, open, ...composerProps }: FeedbackCenterSheetProps): React.JSX.Element;
|
|
19
|
+
export { FeedbackCenterSheet, type FeedbackCenterSheetProps };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { FeedbackCenterFooter, FeedbackCenterSheetHeader, FeedbackCenterTabs, } from "@ciridae/ui/feedback-center-sheet-parts";
|
|
4
|
+
import { Sheet, SheetBody, SheetContent } from "@ciridae/ui/sheet";
|
|
5
|
+
import { Tabs } from "@ciridae/ui/tabs";
|
|
6
|
+
function FeedbackCenterSheet({ commentsByFeedbackId = {}, defaultTab = "submit", isCommentSubmitting = false, isCommentsLoading = false, isLoading = false, items, onSubmitComment, onOpenChange, open, ...composerProps }) {
|
|
7
|
+
const formId = React.useId();
|
|
8
|
+
const [activeTab, setActiveTab] = React.useState(defaultTab);
|
|
9
|
+
const [isSubmitted, setIsSubmitted] = React.useState(false);
|
|
10
|
+
const [selectedId, setSelectedId] = React.useState();
|
|
11
|
+
const [view, setView] = React.useState("list");
|
|
12
|
+
const selectedFeedback = getSelectedFeedback(items, selectedId);
|
|
13
|
+
const handleBack = React.useCallback(() => {
|
|
14
|
+
setView("list");
|
|
15
|
+
}, []);
|
|
16
|
+
const handleSelectFeedback = React.useCallback((item) => {
|
|
17
|
+
setSelectedId(item.id);
|
|
18
|
+
setView("detail");
|
|
19
|
+
}, []);
|
|
20
|
+
const handleTabChange = React.useCallback((tab) => {
|
|
21
|
+
setActiveTab(tab);
|
|
22
|
+
setIsSubmitted(false);
|
|
23
|
+
setView("list");
|
|
24
|
+
}, []);
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
if (!open) {
|
|
27
|
+
setActiveTab(defaultTab);
|
|
28
|
+
setIsSubmitted(false);
|
|
29
|
+
setSelectedId(undefined);
|
|
30
|
+
setView("list");
|
|
31
|
+
}
|
|
32
|
+
}, [defaultTab, open]);
|
|
33
|
+
return (_jsx(Sheet, { onOpenChange: onOpenChange, open: open, children: _jsx(SheetContent, { size: "md", children: _jsxs(Tabs, { className: "min-h-0 flex-1", onValueChange: setFeedbackCenterTab(handleTabChange), value: activeTab, children: [_jsx(FeedbackCenterSheetHeader, {}), _jsx(SheetBody, { className: "pb-(--panel-padding)", children: _jsx(FeedbackCenterTabs, { composerProps: composerProps, commentsByFeedbackId: commentsByFeedbackId, formId: formId, isCommentSubmitting: isCommentSubmitting, isCommentsLoading: isCommentsLoading, isLoading: isLoading, items: items, onBack: handleBack, onCancel: () => onOpenChange(false), onSelectFeedback: handleSelectFeedback, onSubmittedChange: setIsSubmitted, onSubmitComment: onSubmitComment, selectedFeedback: selectedFeedback, view: view }) }), _jsx(FeedbackCenterFooter, { activeTab: activeTab, formId: formId, isSubmitted: isSubmitted, isSubmitting: composerProps.isSubmitting ?? false, onCancel: () => onOpenChange(false) })] }) }) }));
|
|
34
|
+
}
|
|
35
|
+
function getSelectedFeedback(items, selectedId) {
|
|
36
|
+
if (!selectedId)
|
|
37
|
+
return undefined;
|
|
38
|
+
return items.find((item) => item.id === selectedId);
|
|
39
|
+
}
|
|
40
|
+
function setFeedbackCenterTab(onTabChange) {
|
|
41
|
+
return (value) => {
|
|
42
|
+
if (value === "submit" || value === "mine")
|
|
43
|
+
onTabChange(value);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export { FeedbackCenterSheet };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { FeedbackType } from "@ciridae/ui/feedback-composer-types";
|
|
2
|
+
declare function FeedbackTypeField({ disabled, onTypeChange, type, }: {
|
|
3
|
+
disabled: boolean;
|
|
4
|
+
onTypeChange: (type: FeedbackType) => void;
|
|
5
|
+
type: FeedbackType;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
7
|
+
declare function FeedbackSubmittedState({ onCancel, onSendAnother, referenceId, }: {
|
|
8
|
+
onCancel?: () => void;
|
|
9
|
+
onSendAnother: () => void;
|
|
10
|
+
referenceId?: string;
|
|
11
|
+
}): import("react").JSX.Element;
|
|
12
|
+
declare function FeedbackComposerActions({ isSubmitting, onCancel, }: {
|
|
13
|
+
isSubmitting: boolean;
|
|
14
|
+
onCancel?: () => void;
|
|
15
|
+
}): import("react").JSX.Element;
|
|
16
|
+
export { FeedbackComposerActions, FeedbackSubmittedState, FeedbackTypeField };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { BugIcon, CheckCircle2Icon, LightbulbIcon, MessageSquareTextIcon, SendIcon, } from "lucide-react";
|
|
3
|
+
import { Alert, AlertDescription, AlertTitle } from "@ciridae/ui/alert";
|
|
4
|
+
import { Button } from "@ciridae/ui/button";
|
|
5
|
+
import { FieldContent, FieldSet, FieldTitle } from "@ciridae/ui/field";
|
|
6
|
+
import { ToggleGroup, ToggleGroupItem } from "@ciridae/ui/toggle-group";
|
|
7
|
+
const feedbackTypeOptions = [
|
|
8
|
+
{
|
|
9
|
+
icon: BugIcon,
|
|
10
|
+
label: "Bug",
|
|
11
|
+
value: "bug",
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
icon: LightbulbIcon,
|
|
15
|
+
label: "Feature request",
|
|
16
|
+
value: "feature",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
icon: MessageSquareTextIcon,
|
|
20
|
+
label: "General",
|
|
21
|
+
value: "general",
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
function FeedbackTypeField({ disabled, onTypeChange, type, }) {
|
|
25
|
+
return (_jsxs(FieldSet, { className: "gap-2", children: [_jsx(FieldContent, { children: _jsx(FieldTitle, { children: "Type" }) }), _jsx(ToggleGroup, { className: "grid w-full grid-cols-3", disabled: disabled, size: "lg", type: "single", variant: "outline", onValueChange: (value) => {
|
|
26
|
+
if (isFeedbackType(value))
|
|
27
|
+
onTypeChange(value);
|
|
28
|
+
}, value: type, children: feedbackTypeOptions.map((option) => (_jsx(FeedbackTypeOption, { option: option }, option.value))) })] }));
|
|
29
|
+
}
|
|
30
|
+
function FeedbackTypeOption({ option }) {
|
|
31
|
+
const Icon = option.icon;
|
|
32
|
+
return (_jsxs(ToggleGroupItem, { className: "w-full px-2", value: option.value, children: [_jsx(Icon, { className: "size-4 shrink-0 text-muted-foreground" }), _jsx("span", { className: "truncate", children: option.label })] }));
|
|
33
|
+
}
|
|
34
|
+
function FeedbackSubmittedState({ onCancel, onSendAnother, referenceId, }) {
|
|
35
|
+
return (_jsxs("div", { className: "grid gap-(--panel-gap)", children: [_jsxs(Alert, { children: [_jsx(CheckCircle2Icon, {}), _jsx(AlertTitle, { children: "Feedback submitted" }), _jsxs(AlertDescription, { children: [_jsx("p", { children: "Your feedback has been sent to the team." }), referenceId ? (_jsxs("p", { children: ["Reference", " ", _jsx("span", { className: "font-mono text-foreground tabular-nums", children: referenceId })] })) : null] })] }), _jsxs("div", { className: "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", children: [_jsx(Button, { onClick: onSendAnother, type: "button", variant: "outline", children: "Send another" }), onCancel ? (_jsx(Button, { onClick: onCancel, type: "button", children: "Done" })) : null] })] }));
|
|
36
|
+
}
|
|
37
|
+
function FeedbackComposerActions({ isSubmitting, onCancel, }) {
|
|
38
|
+
return (_jsxs("div", { className: "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", children: [onCancel ? (_jsx(Button, { disabled: isSubmitting, onClick: onCancel, type: "button", variant: "outline", children: "Cancel" })) : null, _jsxs(Button, { loading: isSubmitting, type: "submit", children: [_jsx(SendIcon, {}), "Submit feedback"] })] }));
|
|
39
|
+
}
|
|
40
|
+
function isFeedbackType(value) {
|
|
41
|
+
return feedbackTypeOptions.some((option) => option.value === value);
|
|
42
|
+
}
|
|
43
|
+
export { FeedbackComposerActions, FeedbackSubmittedState, FeedbackTypeField };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type FeedbackType = "bug" | "feature" | "general";
|
|
2
|
+
type FeedbackSubmitValues = {
|
|
3
|
+
attachments: File[];
|
|
4
|
+
description: string;
|
|
5
|
+
subject: string;
|
|
6
|
+
type: FeedbackType;
|
|
7
|
+
};
|
|
8
|
+
type FeedbackSubmitResult = {
|
|
9
|
+
referenceId?: string;
|
|
10
|
+
};
|
|
11
|
+
export type { FeedbackSubmitResult, FeedbackSubmitValues, FeedbackType };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { FeedbackSubmitResult, FeedbackSubmitValues, FeedbackType } from "@ciridae/ui/feedback-composer-types";
|
|
3
|
+
type FeedbackComposerProps = {
|
|
4
|
+
acceptedFileTypes?: string;
|
|
5
|
+
actions?: "inline" | "external";
|
|
6
|
+
attachmentsEnabled?: boolean;
|
|
7
|
+
defaultType?: FeedbackType;
|
|
8
|
+
formId?: string;
|
|
9
|
+
isSubmitting?: boolean;
|
|
10
|
+
maxAttachmentSizeMb?: number;
|
|
11
|
+
maxAttachments?: number;
|
|
12
|
+
onCancel?: () => void;
|
|
13
|
+
onSubmittedChange?: (submitted: boolean) => void;
|
|
14
|
+
onSubmit: (values: FeedbackSubmitValues) => void | Promise<void | FeedbackSubmitResult>;
|
|
15
|
+
};
|
|
16
|
+
declare function FeedbackComposer(props: FeedbackComposerProps): React.JSX.Element;
|
|
17
|
+
export { FeedbackComposer, type FeedbackComposerProps, type FeedbackSubmitResult, type FeedbackSubmitValues, type FeedbackType, };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { FeedbackComposerActions, FeedbackSubmittedState, FeedbackTypeField, } from "@ciridae/ui/feedback-composer-parts";
|
|
4
|
+
import { FileUploadField } from "@ciridae/ui/file-upload-field";
|
|
5
|
+
import { Field, FieldContent, FieldError, FieldGroup, FieldLabel } from "@ciridae/ui/field";
|
|
6
|
+
import { Input } from "@ciridae/ui/input";
|
|
7
|
+
import { Textarea } from "@ciridae/ui/textarea";
|
|
8
|
+
function FeedbackComposer(props) {
|
|
9
|
+
const state = useFeedbackComposerState(props.defaultType ?? "bug");
|
|
10
|
+
const ids = useFeedbackComposerIds();
|
|
11
|
+
if (state.submitResult) {
|
|
12
|
+
return (_jsx(FeedbackSubmittedState, { onCancel: props.onCancel, onSendAnother: () => {
|
|
13
|
+
state.resetForm();
|
|
14
|
+
props.onSubmittedChange?.(false);
|
|
15
|
+
}, referenceId: state.submitResult.referenceId }));
|
|
16
|
+
}
|
|
17
|
+
return _jsx(FeedbackComposerForm, { ids: ids, props: props, state: state });
|
|
18
|
+
}
|
|
19
|
+
function FeedbackComposerForm({ ids, props, state, }) {
|
|
20
|
+
const isSubmitting = props.isSubmitting ?? false;
|
|
21
|
+
return (_jsxs("form", { className: "grid gap-(--panel-gap)", id: props.formId, onSubmit: (event) => submitFeedback(event, props, state), children: [_jsxs(FieldGroup, { children: [_jsx(FeedbackTypeField, { disabled: isSubmitting, onTypeChange: state.setType, type: state.type }), _jsx(FeedbackSubjectField, { disabled: isSubmitting, error: state.error, fieldId: ids.subjectId, onSubjectChange: state.setSubject, subject: state.subject }), _jsx(FeedbackDescriptionField, { description: state.description, disabled: isSubmitting, error: state.error, fieldId: ids.descriptionId, onDescriptionChange: state.setDescription }), _jsx(FeedbackAttachmentsField, { props: props, state: state }), state.submitError ? _jsx(FieldError, { children: state.submitError }) : null] }), props.actions === "external" ? null : (_jsx(FeedbackComposerActions, { isSubmitting: isSubmitting, onCancel: props.onCancel }))] }));
|
|
22
|
+
}
|
|
23
|
+
function FeedbackSubjectField({ disabled, error, fieldId, onSubjectChange, subject, }) {
|
|
24
|
+
return (_jsxs(Field, { "data-invalid": error === "subject", children: [_jsx(FieldContent, { children: _jsx(FieldLabel, { htmlFor: fieldId, required: true, children: "Subject" }) }), _jsx(Input, { "aria-invalid": error === "subject", "aria-required": "true", disabled: disabled, id: fieldId, onChange: (event) => onSubjectChange(event.target.value), placeholder: "Short subject for the team", value: subject }), error === "subject" ? _jsx(FieldError, { children: "Subject is required." }) : null] }));
|
|
25
|
+
}
|
|
26
|
+
function FeedbackDescriptionField({ description, disabled, error, fieldId, onDescriptionChange, }) {
|
|
27
|
+
return (_jsxs(Field, { "data-invalid": error === "description", children: [_jsx(FieldContent, { children: _jsx(FieldLabel, { htmlFor: fieldId, required: true, children: "Feedback" }) }), _jsx(Textarea, { "aria-invalid": error === "description", "aria-required": "true", className: "min-h-40", disabled: disabled, id: fieldId, onChange: (event) => onDescriptionChange(event.target.value), placeholder: "What happened, what did you expect, or what would you like to see?", rows: 6, value: description }), error === "description" ? _jsx(FieldError, { children: "Feedback is required." }) : null] }));
|
|
28
|
+
}
|
|
29
|
+
function FeedbackAttachmentsField({ props, state, }) {
|
|
30
|
+
if (props.attachmentsEnabled === false)
|
|
31
|
+
return null;
|
|
32
|
+
return (_jsx(FileUploadField, { acceptedFileTypes: props.acceptedFileTypes ?? "image/*,.pdf,.csv,.txt,.doc,.docx,.xls,.xlsx", disabled: props.isSubmitting ?? false, files: state.attachments, maxAttachmentSizeMb: props.maxAttachmentSizeMb, maxAttachments: props.maxAttachments, onFilesChange: state.setAttachments, showConstraints: false, variant: "dropzone" }));
|
|
33
|
+
}
|
|
34
|
+
function useFeedbackComposerIds() {
|
|
35
|
+
const formId = React.useId();
|
|
36
|
+
return {
|
|
37
|
+
descriptionId: `${formId}-description`,
|
|
38
|
+
subjectId: `${formId}-subject`,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function useFeedbackComposerState(defaultType) {
|
|
42
|
+
const [attachments, setAttachments] = React.useState([]);
|
|
43
|
+
const [description, setDescription] = React.useState("");
|
|
44
|
+
const [error, setError] = React.useState();
|
|
45
|
+
const [submitError, setSubmitError] = React.useState();
|
|
46
|
+
const [submitResult, setSubmitResult] = React.useState();
|
|
47
|
+
const [subject, setSubject] = React.useState("");
|
|
48
|
+
const [type, setType] = React.useState(defaultType);
|
|
49
|
+
React.useEffect(() => {
|
|
50
|
+
setType(defaultType);
|
|
51
|
+
}, [defaultType]);
|
|
52
|
+
const resetForm = () => {
|
|
53
|
+
setAttachments([]);
|
|
54
|
+
setDescription("");
|
|
55
|
+
setError(undefined);
|
|
56
|
+
setSubmitError(undefined);
|
|
57
|
+
setSubmitResult(undefined);
|
|
58
|
+
setSubject("");
|
|
59
|
+
setType(defaultType);
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
attachments,
|
|
63
|
+
description,
|
|
64
|
+
error,
|
|
65
|
+
resetForm,
|
|
66
|
+
setAttachments,
|
|
67
|
+
setDescription,
|
|
68
|
+
setError,
|
|
69
|
+
setSubmitError,
|
|
70
|
+
setSubmitResult,
|
|
71
|
+
setSubject,
|
|
72
|
+
setType,
|
|
73
|
+
submitError,
|
|
74
|
+
submitResult,
|
|
75
|
+
subject,
|
|
76
|
+
type,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async function submitFeedback(event, props, state) {
|
|
80
|
+
event.preventDefault();
|
|
81
|
+
const trimmedDescription = state.description.trim();
|
|
82
|
+
const trimmedSubject = state.subject.trim();
|
|
83
|
+
const validationError = getValidationError({
|
|
84
|
+
description: trimmedDescription,
|
|
85
|
+
subject: trimmedSubject,
|
|
86
|
+
});
|
|
87
|
+
state.setError(validationError);
|
|
88
|
+
state.setSubmitError(undefined);
|
|
89
|
+
if (validationError)
|
|
90
|
+
return;
|
|
91
|
+
try {
|
|
92
|
+
const result = await props.onSubmit({
|
|
93
|
+
attachments: state.attachments,
|
|
94
|
+
description: trimmedDescription,
|
|
95
|
+
subject: trimmedSubject,
|
|
96
|
+
type: state.type,
|
|
97
|
+
});
|
|
98
|
+
state.setSubmitResult(result ?? {});
|
|
99
|
+
props.onSubmittedChange?.(true);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
state.setSubmitError("Unable to submit feedback. Please try again.");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function getValidationError({ description, subject, }) {
|
|
106
|
+
if (!subject)
|
|
107
|
+
return "subject";
|
|
108
|
+
if (!description)
|
|
109
|
+
return "description";
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
export { FeedbackComposer, };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type TimestampFormatOptions } from "@ciridae/ui/timestamp-format";
|
|
2
|
+
declare function formatFeedbackTimestamp(value: string | undefined, options?: TimestampFormatOptions): string;
|
|
3
|
+
declare function formatFeedbackTimestampFull(value: string | undefined, options?: TimestampFormatOptions): string;
|
|
4
|
+
declare function getFeedbackTimestampDateTime(value: string | undefined): string | undefined;
|
|
5
|
+
export { formatFeedbackTimestamp, formatFeedbackTimestampFull, getFeedbackTimestampDateTime };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { formatTimestamp, formatTimestampFull, getTimestampDateTime, } from "@ciridae/ui/timestamp-format";
|
|
2
|
+
function formatFeedbackTimestamp(value, options = {}) {
|
|
3
|
+
return formatTimestamp(value, options);
|
|
4
|
+
}
|
|
5
|
+
function formatFeedbackTimestampFull(value, options = {}) {
|
|
6
|
+
return formatTimestampFull(value, options);
|
|
7
|
+
}
|
|
8
|
+
function getFeedbackTimestampDateTime(value) {
|
|
9
|
+
return getTimestampDateTime(value);
|
|
10
|
+
}
|
|
11
|
+
export { formatFeedbackTimestamp, formatFeedbackTimestampFull, getFeedbackTimestampDateTime };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
import { type StakeholderFeedbackItem } from "@ciridae/ui/feedback-tracking-types";
|
|
3
|
+
type FeedbackDetailSheetProps = {
|
|
4
|
+
feedback?: StakeholderFeedbackItem;
|
|
5
|
+
onOpenChange: (open: boolean) => void;
|
|
6
|
+
open: boolean;
|
|
7
|
+
};
|
|
8
|
+
declare function FeedbackDetailSheet({ feedback, onOpenChange, open }: FeedbackDetailSheetProps): React.JSX.Element;
|
|
9
|
+
export { FeedbackDetailSheet, type FeedbackDetailSheetProps };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { PaperclipIcon } from "lucide-react";
|
|
3
|
+
import { formatFeedbackTimestampFull, getFeedbackTimestampDateTime, } from "@ciridae/ui/feedback-date-format";
|
|
4
|
+
import { FeedbackStatusBadge } from "@ciridae/ui/feedback-status-badge";
|
|
5
|
+
import { FeedbackTypeBadge } from "@ciridae/ui/feedback-type-badge";
|
|
6
|
+
import { getFeedbackInitials, } from "@ciridae/ui/feedback-tracking-types";
|
|
7
|
+
import { Avatar, AvatarFallback, AvatarImage } from "@ciridae/ui/avatar";
|
|
8
|
+
import { Button } from "@ciridae/ui/button";
|
|
9
|
+
import { Card } from "@ciridae/ui/card";
|
|
10
|
+
import { Item, ItemContent, ItemGroup, ItemMedia, ItemTitle } from "@ciridae/ui/item";
|
|
11
|
+
import { PropertyList, PropertyListItem, PropertyListLabel, PropertyListValue, } from "@ciridae/ui/property-list";
|
|
12
|
+
import { Sheet, SheetBody, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, } from "@ciridae/ui/sheet";
|
|
13
|
+
function FeedbackDetailSheet({ feedback, onOpenChange, open }) {
|
|
14
|
+
return (_jsx(Sheet, { onOpenChange: onOpenChange, open: open, children: _jsxs(SheetContent, { size: "md", children: [_jsxs(SheetHeader, { children: [_jsx(SheetTitle, { children: feedback?.subject ?? "Feedback" }), _jsx(SheetDescription, { children: "Review the original submission and current progress." })] }), _jsx(SheetBody, { className: "grid content-start gap-(--panel-gap) pb-(--panel-padding)", children: feedback ? _jsx(FeedbackDetailContent, { feedback: feedback }) : null }), _jsx(SheetFooter, { children: _jsx(Button, { onClick: () => onOpenChange(false), type: "button", children: "Done" }) })] }) }));
|
|
15
|
+
}
|
|
16
|
+
function FeedbackDetailContent({ feedback }) {
|
|
17
|
+
return (_jsxs(_Fragment, { children: [_jsx(FeedbackDetailSummary, { feedback: feedback }), _jsx(FeedbackDetailBody, { feedback: feedback }), _jsx(FeedbackDetailAttachments, { feedback: feedback })] }));
|
|
18
|
+
}
|
|
19
|
+
function FeedbackDetailSummary({ feedback }) {
|
|
20
|
+
const submitter = feedback.submittedBy;
|
|
21
|
+
return (_jsxs("div", { className: "flex min-w-0 items-start justify-between gap-3 rounded-lg border bg-card p-(--panel-padding)", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [_jsxs(Avatar, { children: [_jsx(AvatarImage, { alt: "", src: submitter.imageUrl }), _jsx(AvatarFallback, { children: getFeedbackInitials(submitter.name, submitter.email) })] }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate text-sm font-medium text-foreground", children: submitter.name }), _jsx("div", { className: "truncate text-xs text-muted-foreground", children: submitter.organization ?? submitter.email ?? "Stakeholder" })] })] }), _jsx(FeedbackStatusBadge, { status: feedback.status })] }));
|
|
22
|
+
}
|
|
23
|
+
function FeedbackDetailBody({ feedback }) {
|
|
24
|
+
return (_jsxs("div", { className: "grid gap-(--panel-gap)", children: [_jsxs(PropertyList, { children: [_jsx(FeedbackProperty, { label: "Type", children: _jsx(FeedbackTypeBadge, { type: feedback.type }) }), _jsx(FeedbackProperty, { label: "Submitted", children: _jsx(FeedbackDetailDateValue, { value: feedback.submittedAt }) }), _jsx(FeedbackProperty, { label: "Updated", children: _jsx(FeedbackDetailDateValue, { value: feedback.updatedAt }) }), feedback.referenceId ? (_jsx(FeedbackProperty, { label: "Reference", children: _jsx("span", { className: "font-mono text-xs tabular-nums", children: feedback.referenceId }) })) : null] }), _jsxs(Card, { className: "grid gap-2 p-(--panel-padding)", children: [_jsx("div", { className: "text-meta text-muted-foreground", children: "Feedback" }), _jsx("p", { className: "text-sm whitespace-pre-wrap text-foreground", children: feedback.description })] })] }));
|
|
25
|
+
}
|
|
26
|
+
function FeedbackProperty({ children, label }) {
|
|
27
|
+
return (_jsxs(PropertyListItem, { children: [_jsx(PropertyListLabel, { children: label }), _jsx(PropertyListValue, { children: children })] }));
|
|
28
|
+
}
|
|
29
|
+
function FeedbackDetailDateValue({ value }) {
|
|
30
|
+
return (_jsx("time", { className: "font-mono text-xs tabular-nums", dateTime: getFeedbackTimestampDateTime(value), children: formatFeedbackTimestampFull(value) }));
|
|
31
|
+
}
|
|
32
|
+
function FeedbackDetailAttachments({ feedback }) {
|
|
33
|
+
const attachments = feedback.attachments ?? [];
|
|
34
|
+
if (attachments.length === 0)
|
|
35
|
+
return null;
|
|
36
|
+
return (_jsxs("section", { className: "grid gap-2", children: [_jsx("div", { className: "text-meta text-muted-foreground", children: "Attachments" }), _jsx(ItemGroup, { children: attachments.map((attachment, index) => (_jsxs(Item, { size: "sm", variant: "outline", children: [_jsx(ItemMedia, { variant: "icon", children: _jsx(PaperclipIcon, {}) }), _jsx(ItemContent, { children: _jsx(ItemTitle, { children: attachment.url ? (_jsx("a", { className: "underline-offset-4 hover:underline", href: attachment.url, children: attachment.name })) : (attachment.name) }) })] }, getAttachmentKey(attachment, index)))) })] }));
|
|
37
|
+
}
|
|
38
|
+
function getAttachmentKey(attachment, index) {
|
|
39
|
+
return `${attachment.url ?? attachment.name}-${index}`;
|
|
40
|
+
}
|
|
41
|
+
export { FeedbackDetailSheet };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FeedbackMetrics } from "./feedback-tracking-types";
|
|
2
|
+
type FeedbackMetricsProps = {
|
|
3
|
+
metrics: FeedbackMetrics;
|
|
4
|
+
};
|
|
5
|
+
declare function FeedbackMetricsStrip({ metrics }: FeedbackMetricsProps): import("react").JSX.Element;
|
|
6
|
+
export { FeedbackMetricsStrip, type FeedbackMetricsProps };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { MetricsStrip, MetricsStripDetail, MetricsStripItem, MetricsStripLabel, MetricsStripValue, } from "@ciridae/ui/metrics-strip";
|
|
3
|
+
function FeedbackMetricsStrip({ metrics }) {
|
|
4
|
+
return (_jsxs(MetricsStrip, { className: "md:grid-cols-5", children: [_jsx(FeedbackMetricItem, { detail: "New this week", label: "Received", tone: "neutral", value: metrics.receivedThisWeek }), _jsx(FeedbackMetricItem, { detail: "Needs review", label: "Awaiting review", tone: "warning", value: metrics.awaitingReview }), _jsx(FeedbackMetricItem, { detail: "Reviewed this week", label: "Triaged", tone: "info", value: metrics.triagedThisWeek }), _jsx(FeedbackMetricItem, { detail: "Active work", label: "In progress", tone: "info", value: metrics.inProgress }), _jsx(FeedbackMetricItem, { detail: "This week", label: "Resolved", tone: "success", value: metrics.resolvedThisWeek })] }));
|
|
5
|
+
}
|
|
6
|
+
function FeedbackMetricItem({ detail, label, tone, value, }) {
|
|
7
|
+
return (_jsxs(MetricsStripItem, { children: [_jsx(MetricsStripLabel, { children: label }), _jsx(MetricsStripValue, { children: value }), _jsx(MetricsStripDetail, { className: feedbackMetricToneClasses[tone], children: detail })] }));
|
|
8
|
+
}
|
|
9
|
+
const feedbackMetricToneClasses = {
|
|
10
|
+
info: "text-info",
|
|
11
|
+
neutral: "text-muted-foreground",
|
|
12
|
+
success: "text-success",
|
|
13
|
+
warning: "text-warning",
|
|
14
|
+
};
|
|
15
|
+
export { FeedbackMetricsStrip };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type FeedbackComposerProps } from "@ciridae/ui/feedback-composer";
|
|
3
|
+
type FeedbackSheetProps = Omit<FeedbackComposerProps, "actions" | "formId" | "onCancel" | "onSubmittedChange"> & {
|
|
4
|
+
onOpenChange: (open: boolean) => void;
|
|
5
|
+
open: boolean;
|
|
6
|
+
};
|
|
7
|
+
declare function FeedbackSheet({ onOpenChange, open, ...composerProps }: FeedbackSheetProps): React.JSX.Element;
|
|
8
|
+
export { FeedbackSheet, type FeedbackSheetProps };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { SendIcon } from "lucide-react";
|
|
4
|
+
import { FeedbackComposer } from "@ciridae/ui/feedback-composer";
|
|
5
|
+
import { Button } from "@ciridae/ui/button";
|
|
6
|
+
import { Sheet, SheetBody, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, } from "@ciridae/ui/sheet";
|
|
7
|
+
function FeedbackSheet({ onOpenChange, open, ...composerProps }) {
|
|
8
|
+
const formId = React.useId();
|
|
9
|
+
const [isSubmitted, setIsSubmitted] = React.useState(false);
|
|
10
|
+
React.useEffect(() => {
|
|
11
|
+
if (!open)
|
|
12
|
+
setIsSubmitted(false);
|
|
13
|
+
}, [open]);
|
|
14
|
+
return (_jsx(Sheet, { onOpenChange: onOpenChange, open: open, children: _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: () => onOpenChange(false), onSubmittedChange: setIsSubmitted }) }), isSubmitted ? null : (_jsxs(SheetFooter, { children: [_jsx(Button, { disabled: composerProps.isSubmitting, onClick: () => onOpenChange(false), type: "button", variant: "outline", children: "Cancel" }), _jsxs(Button, { form: formId, loading: composerProps.isSubmitting, type: "submit", children: [_jsx(SendIcon, {}), "Submit feedback"] })] }))] }) }));
|
|
15
|
+
}
|
|
16
|
+
export { FeedbackSheet };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type FeedbackStatus } from "./feedback-tracking-types";
|
|
2
|
+
type FeedbackStatusBadgeProps = {
|
|
3
|
+
status: FeedbackStatus;
|
|
4
|
+
};
|
|
5
|
+
declare function FeedbackStatusBadge({ status }: FeedbackStatusBadgeProps): import("react").JSX.Element;
|
|
6
|
+
export { FeedbackStatusBadge, type FeedbackStatusBadgeProps };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Badge } from "@ciridae/ui/badge";
|
|
3
|
+
import { feedbackStatusLabels } from "./feedback-tracking-types";
|
|
4
|
+
function FeedbackStatusBadge({ status }) {
|
|
5
|
+
return _jsx(Badge, { variant: feedbackStatusVariants[status], children: feedbackStatusLabels[status] });
|
|
6
|
+
}
|
|
7
|
+
const feedbackStatusVariants = {
|
|
8
|
+
in_progress: "info",
|
|
9
|
+
in_review: "warning",
|
|
10
|
+
planned: "info",
|
|
11
|
+
received: "outline",
|
|
12
|
+
resolved: "success",
|
|
13
|
+
};
|
|
14
|
+
export { FeedbackStatusBadge };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type StakeholderFeedbackItem } from "@ciridae/ui/feedback-tracking-types";
|
|
2
|
+
declare function FeedbackTableRow({ item, onSelectFeedback, }: {
|
|
3
|
+
item: StakeholderFeedbackItem;
|
|
4
|
+
onSelectFeedback?: (item: StakeholderFeedbackItem) => void;
|
|
5
|
+
}): import("react").JSX.Element;
|
|
6
|
+
export { FeedbackTableRow };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PaperclipIcon } from "lucide-react";
|
|
3
|
+
import { formatFeedbackTimestamp, getFeedbackTimestampDateTime, } from "@ciridae/ui/feedback-date-format";
|
|
4
|
+
import { FeedbackStatusBadge } from "@ciridae/ui/feedback-status-badge";
|
|
5
|
+
import { FeedbackTypeBadge } from "@ciridae/ui/feedback-type-badge";
|
|
6
|
+
import { getFeedbackInitials, } from "@ciridae/ui/feedback-tracking-types";
|
|
7
|
+
import { Avatar, AvatarFallback, AvatarImage } from "@ciridae/ui/avatar";
|
|
8
|
+
import { TableCell, TableRow } from "@ciridae/ui/table";
|
|
9
|
+
function FeedbackTableRow({ item, onSelectFeedback, }) {
|
|
10
|
+
return (_jsxs(TableRow, { className: onSelectFeedback ? "cursor-pointer" : undefined, onClick: () => onSelectFeedback?.(item), onKeyDown: (event) => {
|
|
11
|
+
if (!onSelectFeedback)
|
|
12
|
+
return;
|
|
13
|
+
if (event.currentTarget !== event.target)
|
|
14
|
+
return;
|
|
15
|
+
if (event.key !== "Enter" && event.key !== " ")
|
|
16
|
+
return;
|
|
17
|
+
event.preventDefault();
|
|
18
|
+
onSelectFeedback(item);
|
|
19
|
+
}, role: onSelectFeedback ? "button" : undefined, tabIndex: onSelectFeedback ? 0 : undefined, children: [_jsx(TableCell, { className: "max-w-[22rem]", children: _jsx(FeedbackSummaryCell, { item: item }) }), _jsx(TableCell, { children: _jsx(FeedbackTypeBadge, { type: item.type }) }), _jsx(TableCell, { children: _jsx(FeedbackSubmitterCell, { item: item }) }), _jsx(TableCell, { children: _jsx(FeedbackStatusBadge, { status: item.status }) }), _jsx(TableCell, { children: _jsx(FeedbackDateValue, { value: item.submittedAt }) }), _jsx(TableCell, { children: _jsx(FeedbackDateValue, { value: item.updatedAt }) }), _jsx(TableCell, { className: "text-right", children: _jsx(FeedbackAttachmentCount, { item: item }) })] }));
|
|
20
|
+
}
|
|
21
|
+
function FeedbackSummaryCell({ item }) {
|
|
22
|
+
return (_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate text-sm font-medium text-foreground", children: item.subject }), _jsx("div", { className: "truncate text-xs text-muted-foreground", children: item.description })] }));
|
|
23
|
+
}
|
|
24
|
+
function FeedbackSubmitterCell({ item }) {
|
|
25
|
+
const submitter = item.submittedBy;
|
|
26
|
+
return (_jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [_jsxs(Avatar, { size: "sm", children: [_jsx(AvatarImage, { alt: "", src: submitter.imageUrl }), _jsx(AvatarFallback, { children: getFeedbackInitials(submitter.name, submitter.email) })] }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate text-sm font-medium text-foreground", children: submitter.name }), _jsx("div", { className: "truncate text-xs text-muted-foreground", children: submitter.organization ?? submitter.email ?? "Stakeholder" })] })] }));
|
|
27
|
+
}
|
|
28
|
+
function FeedbackDateValue({ value }) {
|
|
29
|
+
return (_jsx("time", { className: "font-mono text-xs text-muted-foreground tabular-nums", dateTime: getFeedbackTimestampDateTime(value), children: formatFeedbackTimestamp(value) }));
|
|
30
|
+
}
|
|
31
|
+
function FeedbackAttachmentCount({ item }) {
|
|
32
|
+
const count = item.attachments?.length ?? 0;
|
|
33
|
+
if (count === 0)
|
|
34
|
+
return _jsx("span", { className: "text-xs text-muted-foreground", children: "None" });
|
|
35
|
+
return (_jsxs("span", { className: "inline-flex items-center justify-end gap-1 font-mono text-xs text-muted-foreground tabular-nums", children: [_jsx(PaperclipIcon, { className: "size-3.5" }), count] }));
|
|
36
|
+
}
|
|
37
|
+
export { FeedbackTableRow };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SortDirection } from "@ciridae/ui/data-table";
|
|
2
|
+
import type { StakeholderFeedbackItem } from "./feedback-tracking-types";
|
|
3
|
+
type FeedbackTableSortKey = "status" | "submittedAt" | "submittedBy" | "type" | "updatedAt";
|
|
4
|
+
type FeedbackTableSort = {
|
|
5
|
+
direction: SortDirection;
|
|
6
|
+
key: FeedbackTableSortKey;
|
|
7
|
+
};
|
|
8
|
+
declare function sortFeedbackItems(items: StakeholderFeedbackItem[], sort: FeedbackTableSort): StakeholderFeedbackItem[];
|
|
9
|
+
declare function getNextFeedbackTableSort(currentSort: FeedbackTableSort, key: FeedbackTableSortKey): FeedbackTableSort;
|
|
10
|
+
declare const defaultFeedbackTableSort: FeedbackTableSort;
|
|
11
|
+
export { defaultFeedbackTableSort, getNextFeedbackTableSort, sortFeedbackItems, type FeedbackTableSort, type FeedbackTableSortKey, };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
function sortFeedbackItems(items, sort) {
|
|
2
|
+
return [...items].sort((first, second) => {
|
|
3
|
+
const result = compareFeedbackItems(first, second, sort);
|
|
4
|
+
if (result !== 0)
|
|
5
|
+
return result;
|
|
6
|
+
return compareStrings(first.subject, second.subject, "asc");
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
function getNextFeedbackTableSort(currentSort, key) {
|
|
10
|
+
if (currentSort.key !== key)
|
|
11
|
+
return { direction: getDefaultSortDirection(key), key };
|
|
12
|
+
return {
|
|
13
|
+
direction: currentSort.direction === "asc" ? "desc" : "asc",
|
|
14
|
+
key,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function compareFeedbackItems(first, second, sort) {
|
|
18
|
+
if (sort.key === "type")
|
|
19
|
+
return compareFeedbackTypes(first.type, second.type, sort.direction);
|
|
20
|
+
if (sort.key === "submittedBy") {
|
|
21
|
+
return compareStrings(first.submittedBy.name, second.submittedBy.name, sort.direction);
|
|
22
|
+
}
|
|
23
|
+
if (sort.key === "status")
|
|
24
|
+
return compareFeedbackStatuses(first.status, second.status, sort.direction);
|
|
25
|
+
if (sort.key === "submittedAt") {
|
|
26
|
+
return compareTimestamps(first.submittedAt, second.submittedAt, sort.direction);
|
|
27
|
+
}
|
|
28
|
+
return compareTimestamps(first.updatedAt, second.updatedAt, sort.direction);
|
|
29
|
+
}
|
|
30
|
+
function getDefaultSortDirection(key) {
|
|
31
|
+
if (key === "submittedAt" || key === "updatedAt")
|
|
32
|
+
return "desc";
|
|
33
|
+
return "asc";
|
|
34
|
+
}
|
|
35
|
+
function compareFeedbackStatuses(first, second, direction) {
|
|
36
|
+
const result = statusSortOrder[first] - statusSortOrder[second];
|
|
37
|
+
return direction === "asc" ? result : -result;
|
|
38
|
+
}
|
|
39
|
+
function compareFeedbackTypes(first, second, direction) {
|
|
40
|
+
const result = typeSortOrder[first] - typeSortOrder[second];
|
|
41
|
+
return direction === "asc" ? result : -result;
|
|
42
|
+
}
|
|
43
|
+
function compareStrings(first, second, direction) {
|
|
44
|
+
const result = first.localeCompare(second, undefined, { sensitivity: "base" });
|
|
45
|
+
return direction === "asc" ? result : -result;
|
|
46
|
+
}
|
|
47
|
+
function compareTimestamps(first, second, direction) {
|
|
48
|
+
const firstTimestamp = getTimestamp(first);
|
|
49
|
+
const secondTimestamp = getTimestamp(second);
|
|
50
|
+
if (firstTimestamp === undefined && secondTimestamp === undefined)
|
|
51
|
+
return 0;
|
|
52
|
+
if (firstTimestamp === undefined)
|
|
53
|
+
return 1;
|
|
54
|
+
if (secondTimestamp === undefined)
|
|
55
|
+
return -1;
|
|
56
|
+
const result = firstTimestamp - secondTimestamp;
|
|
57
|
+
return direction === "asc" ? result : -result;
|
|
58
|
+
}
|
|
59
|
+
function getTimestamp(value) {
|
|
60
|
+
if (!value)
|
|
61
|
+
return undefined;
|
|
62
|
+
const timestamp = Date.parse(value);
|
|
63
|
+
return Number.isNaN(timestamp) ? undefined : timestamp;
|
|
64
|
+
}
|
|
65
|
+
const defaultFeedbackTableSort = {
|
|
66
|
+
direction: "desc",
|
|
67
|
+
key: "submittedAt",
|
|
68
|
+
};
|
|
69
|
+
const statusSortOrder = {
|
|
70
|
+
in_progress: 3,
|
|
71
|
+
in_review: 1,
|
|
72
|
+
planned: 2,
|
|
73
|
+
received: 0,
|
|
74
|
+
resolved: 4,
|
|
75
|
+
};
|
|
76
|
+
const typeSortOrder = {
|
|
77
|
+
bug: 0,
|
|
78
|
+
feature: 1,
|
|
79
|
+
general: 2,
|
|
80
|
+
};
|
|
81
|
+
export { defaultFeedbackTableSort, getNextFeedbackTableSort, sortFeedbackItems, };
|