@ciridae/ui 1.0.2 → 1.0.4
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/app/auth.d.ts +10 -0
- package/dist/app/auth.js +60 -0
- package/dist/app/error-boundary.d.ts +18 -0
- package/dist/app/error-boundary.js +45 -0
- package/dist/app/feedback.d.ts +12 -0
- package/dist/app/feedback.js +128 -0
- package/dist/app/index.d.ts +8 -0
- package/dist/app/index.js +8 -0
- package/dist/app/request-errors.d.ts +63 -0
- package/dist/app/request-errors.js +172 -0
- package/dist/app/sentry.d.ts +18 -0
- package/dist/app/sentry.js +52 -0
- package/dist/app/telemetry.d.ts +7 -0
- package/dist/app/telemetry.js +28 -0
- package/dist/app/trail.d.ts +9 -0
- package/dist/app/trail.js +18 -0
- package/dist/chat/chat-query-keys.d.ts +3 -3
- package/dist/chat/contract.d.ts +15 -15
- package/dist/chat/conversation-items-query-options.d.ts +114 -114
- package/dist/chat/conversation-source.d.ts +89 -0
- package/dist/chat/conversation-source.js +1 -0
- package/dist/chat/describe-tool-call.d.ts +3 -3
- package/dist/chat/describe-tool-call.js +3 -3
- package/dist/chat/message-text.d.ts +1 -1
- package/dist/chat/message-text.js +116 -48
- package/dist/chat/use-conversation-list.d.ts +6 -6
- package/dist/ciridae.css +16 -4
- package/dist/components/activity-feed.d.ts +3 -2
- package/dist/components/activity-feed.js +1 -0
- package/dist/components/agent-work.d.ts +3 -3
- package/dist/components/app-frame.d.ts +91 -0
- package/dist/components/app-frame.js +134 -0
- package/dist/components/app-navigation.d.ts +30 -3
- package/dist/components/app-navigation.js +105 -14
- package/dist/components/badge.d.ts +30 -5
- package/dist/components/badge.js +134 -15
- package/dist/components/brand-icons.d.ts +3 -3
- package/dist/components/bulk-action-bar.d.ts +1 -1
- package/dist/components/bulk-action-bar.js +3 -3
- package/dist/components/button.d.ts +12 -5
- package/dist/components/button.js +77 -44
- package/dist/components/calendar.d.ts +1 -1
- package/dist/components/calendar.js +44 -17
- package/dist/components/centered-page.d.ts +1 -1
- package/dist/components/centered-page.js +2 -2
- package/dist/components/chart.d.ts +22 -2
- package/dist/components/chart.js +251 -51
- package/dist/components/chat-client-source.d.ts +16 -0
- package/dist/components/chat-client-source.js +297 -0
- package/dist/components/chat-page.d.ts +9 -8
- package/dist/components/chat-page.js +7 -11
- package/dist/components/chat-sheet-controller.d.ts +11 -14
- package/dist/components/chat-sheet-controller.js +3 -3
- package/dist/components/chat-sheet.d.ts +18 -11
- package/dist/components/chat-sheet.js +188 -289
- package/dist/components/chat.d.ts +109 -19
- package/dist/components/chat.js +520 -124
- package/dist/components/checkbox-item.d.ts +6 -3
- package/dist/components/checkbox-item.js +9 -5
- package/dist/components/checkbox-list.d.ts +1 -1
- package/dist/components/chip.d.ts +6 -1
- package/dist/components/chip.js +27 -1
- package/dist/components/collapsible.d.ts +1 -1
- package/dist/components/color-theme.d.ts +1 -1
- package/dist/components/column-settings.d.ts +1 -1
- package/dist/components/column-settings.js +1 -1
- package/dist/components/command-menu.d.ts +5 -1
- package/dist/components/command-menu.js +98 -32
- package/dist/components/command-palette.d.ts +7 -3
- package/dist/components/command-palette.js +20 -11
- package/dist/components/copyable-id.d.ts +22 -5
- package/dist/components/copyable-id.js +46 -6
- package/dist/components/data-display.d.ts +39 -8
- package/dist/components/data-display.js +138 -19
- package/dist/components/data-grid.d.ts +23 -11
- package/dist/components/data-grid.js +61 -26
- package/dist/components/date-range-picker.d.ts +1 -1
- package/dist/components/date-range-picker.js +9 -20
- package/dist/components/date-time.d.ts +1 -1
- package/dist/components/dialog-height-context.d.ts +1 -1
- package/dist/components/document-viewer.d.ts +2 -2
- package/dist/components/drawer.d.ts +11 -6
- package/dist/components/drawer.js +91 -54
- package/dist/components/dropzone.d.ts +4 -4
- package/dist/components/editable-field.d.ts +12 -3
- package/dist/components/editable-field.js +57 -27
- package/dist/components/email-input.d.ts +1 -1
- package/dist/components/email-input.js +6 -5
- package/dist/components/environment-frame.d.ts +2 -2
- package/dist/components/environment-frame.js +37 -6
- package/dist/components/expandable-text.d.ts +1 -1
- package/dist/components/feedback-screenshot.d.ts +1 -1
- package/dist/components/feedback-sheet.d.ts +18 -3
- package/dist/components/feedback-sheet.js +216 -55
- package/dist/components/field-context.d.ts +1 -1
- package/dist/components/filter-bar.d.ts +3 -3
- package/dist/components/filter-bar.js +43 -29
- package/dist/components/form-controls.d.ts +12 -3
- package/dist/components/form-controls.js +46 -22
- package/dist/components/form-layout.d.ts +14 -10
- package/dist/components/form-layout.js +68 -23
- package/dist/components/glass-menu.d.ts +3 -0
- package/dist/components/glass-menu.js +63 -0
- package/dist/components/gradient-edge.d.ts +9 -0
- package/dist/components/gradient-edge.js +31 -0
- package/dist/components/hotkeys.d.ts +2 -2
- package/dist/components/icon.d.ts +1 -1
- package/dist/components/input.d.ts +7 -5
- package/dist/components/input.js +29 -17
- package/dist/components/keyboard-shortcuts.d.ts +2 -2
- package/dist/components/keycap.d.ts +10 -6
- package/dist/components/keycap.js +57 -19
- package/dist/components/layout.d.ts +13 -13
- package/dist/components/lightbox.d.ts +9 -3
- package/dist/components/lightbox.js +13 -9
- package/dist/components/link.d.ts +1 -1
- package/dist/components/locale.d.ts +42 -2
- package/dist/components/locale.js +43 -3
- package/dist/components/log-entry.d.ts +40 -0
- package/dist/components/log-entry.js +146 -0
- package/dist/components/log-list.d.ts +3 -3
- package/dist/components/mention-input.d.ts +2 -2
- package/dist/components/mention-input.js +4 -3
- package/dist/components/menu.d.ts +7 -3
- package/dist/components/menu.js +34 -37
- package/dist/components/meter.d.ts +10 -2
- package/dist/components/meter.js +64 -6
- package/dist/components/navigation.d.ts +21 -4
- package/dist/components/navigation.js +84 -19
- package/dist/components/notification-tray.d.ts +1 -1
- package/dist/components/notification-tray.js +5 -4
- package/dist/components/overlays.d.ts +6 -4
- package/dist/components/overlays.js +69 -40
- package/dist/components/page-frame.d.ts +4 -4
- package/dist/components/page-frame.js +1 -1
- package/dist/components/panel.d.ts +4 -4
- package/dist/components/panel.js +18 -12
- package/dist/components/profile-header.d.ts +1 -1
- package/dist/components/progress.d.ts +1 -1
- package/dist/components/project-avatar.d.ts +1 -1
- package/dist/components/radio-card.d.ts +2 -2
- package/dist/components/radio-card.js +10 -9
- package/dist/components/record.d.ts +29 -5
- package/dist/components/record.js +147 -18
- package/dist/components/resolved-file-url.d.ts +19 -0
- package/dist/components/resolved-file-url.js +49 -0
- package/dist/components/scroll-area.d.ts +1 -1
- package/dist/components/secret-dialog.d.ts +1 -1
- package/dist/components/segmented-bar.d.ts +4 -2
- package/dist/components/segmented-bar.js +16 -6
- package/dist/components/select.d.ts +9 -6
- package/dist/components/select.js +103 -127
- package/dist/components/settings.d.ts +4 -4
- package/dist/components/shell-skeleton.d.ts +1 -1
- package/dist/components/shell-skeleton.js +1 -1
- package/dist/components/shell.d.ts +21 -21
- package/dist/components/shell.js +43 -43
- package/dist/components/side-panel.d.ts +3 -3
- package/dist/components/sidebar.d.ts +13 -8
- package/dist/components/sidebar.js +55 -27
- package/dist/components/single-line-text.d.ts +2 -0
- package/dist/components/single-line-text.js +16 -0
- package/dist/components/site-banner.d.ts +5 -2
- package/dist/components/site-banner.js +17 -6
- package/dist/components/skeleton.d.ts +8 -7
- package/dist/components/skeleton.js +28 -16
- package/dist/components/skeletons.d.ts +17 -5
- package/dist/components/skeletons.js +71 -8
- package/dist/components/stages.d.ts +83 -11
- package/dist/components/stages.js +512 -156
- package/dist/components/stat-card.d.ts +12 -4
- package/dist/components/stat-card.js +92 -37
- package/dist/components/status-banner.d.ts +1 -1
- package/dist/components/status-banner.js +1 -1
- package/dist/components/status.d.ts +2 -2
- package/dist/components/steps.d.ts +1 -1
- package/dist/components/switch.d.ts +1 -1
- package/dist/components/tabs.d.ts +8 -8
- package/dist/components/tabs.js +26 -13
- package/dist/components/theme-customizer.d.ts +2 -2
- package/dist/components/theme-toggle.d.ts +4 -0
- package/dist/components/theme-toggle.js +67 -0
- package/dist/components/toast.d.ts +10 -3
- package/dist/components/toast.js +78 -29
- package/dist/components/use-new-version-available.d.ts +6 -0
- package/dist/components/use-new-version-available.js +29 -0
- package/dist/components/user-chip.d.ts +3 -3
- package/dist/components/wizard.d.ts +2 -2
- package/dist/feedback/contract.d.ts +136 -136
- package/dist/feedback/feedback-controller.d.ts +6 -6
- package/dist/feedback/generated/zod.gen.d.ts +28 -28
- package/dist/feedback/operator-contract.d.ts +11 -11
- package/dist/feedback/operator-controller.d.ts +32 -13
- package/dist/feedback/operator-controller.js +44 -11
- package/dist/preset.js +289 -33
- package/dist/styled-system/styles.css +3294 -1079
- package/dist/styled-system/tokens/index.mjs +598 -22
- package/dist/styled-system/tokens/tokens.d.ts +11 -11
- package/dist/styled-system/types/prop-type.d.ts +2 -2
- package/dist/styles.css +16 -4
- package/dist/theme/color.d.ts +3 -0
- package/dist/theme/color.js +5 -0
- package/dist/theme/derive.d.ts +9 -2
- package/dist/theme/derive.js +72 -12
- package/dist/theme/palettes.d.ts +49 -6
- package/dist/theme/palettes.js +84 -15
- package/dist/theme/tokens.d.ts +3 -3
- package/dist/theme/tokens.js +107 -32
- package/package.json +40 -3
|
@@ -3,23 +3,28 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
* The feedback sheet. New: what it is about, where it was sent from, what happened, whether to
|
|
4
4
|
* attach a screenshot, with numbered pointers and notes on it, and files dropped on the form,
|
|
5
5
|
* picked or pasted; the form owns its validation and the sending, sent and failed states.
|
|
6
|
-
*
|
|
7
|
-
* thread they can reply in, with the item's actions, such as withdrawing it, behind its menu.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* Tickets: everything the person filed, and each item's conversation with the Ciridae team as a
|
|
7
|
+
* thread they can reply in, with the item's actions, such as withdrawing it, behind its menu. For
|
|
8
|
+
* an operator it lists everyone's instead, filtered (their own among the filters) and searched,
|
|
9
|
+
* each item the same thread with a status to set. The sheet is presentational: the caller owns the requests behind
|
|
10
|
+
* `onSubmit`, `sent` and `operator`; `useFeedbackOperatorSheet` builds `operator` from the API.
|
|
10
11
|
*/
|
|
11
12
|
import { ArrowClockwiseIcon, ArrowLeftIcon, ChatCircleTextIcon, DotsThreeIcon, SquaresFourIcon, } from "@phosphor-icons/react";
|
|
12
13
|
import { createContext, useContext, useId, useState } from "react";
|
|
13
14
|
import { css, cx } from "#primitives/css";
|
|
14
|
-
import { feedbackAttachmentLimits, } from "../feedback/contract.js";
|
|
15
|
+
import { feedbackAttachmentLimits, feedbackStatusSchema, feedbackTypeSchema, } from "../feedback/contract.js";
|
|
16
|
+
import { feedbackTriageStatusSchema, } from "../feedback/operator-contract.js";
|
|
15
17
|
import { Button, IconButton } from "./button.js";
|
|
16
18
|
import { ChatComposer, ChatThread } from "./chat.js";
|
|
19
|
+
import { useCopy } from "./copyable-id.js";
|
|
17
20
|
import { DateTime } from "./date-time.js";
|
|
18
21
|
import { Drawer, DrawerBody, DrawerField, DrawerFooter, DrawerForm } from "./drawer.js";
|
|
19
22
|
import { Dropzone, DropzoneFileList, DropzonePad } from "./dropzone.js";
|
|
20
23
|
import { FeedbackScreenshot, } from "./feedback-screenshot.js";
|
|
24
|
+
import { FilterBar, matchesFilter } from "./filter-bar.js";
|
|
21
25
|
import { Checkbox, Textarea } from "./form-controls.js";
|
|
22
26
|
import { directional, icon, Spinner } from "./icon.js";
|
|
27
|
+
import { SearchInput } from "./input.js";
|
|
23
28
|
import { Eyebrow } from "./layout.js";
|
|
24
29
|
import { useCiridaeLocale } from "./locale.js";
|
|
25
30
|
import { Menu } from "./menu.js";
|
|
@@ -205,6 +210,11 @@ const sentEmpty = css({
|
|
|
205
210
|
textAlign: "center",
|
|
206
211
|
});
|
|
207
212
|
const padded = css({ padding: "panel.comfortable" });
|
|
213
|
+
const listToolbar = css({
|
|
214
|
+
paddingBlock: "panel.compact",
|
|
215
|
+
paddingInline: "panel.comfortable",
|
|
216
|
+
borderBottom: "1px solid token(colors.border.subtle)",
|
|
217
|
+
});
|
|
208
218
|
/* An item: its subject and status pinned over the thread, which scrolls; the reply below. */
|
|
209
219
|
const itemHeader = css({
|
|
210
220
|
position: "sticky",
|
|
@@ -347,25 +357,39 @@ function FailedNotice({ title, children, action, }) {
|
|
|
347
357
|
export function FeedbackSheet({ labels, ...props }) {
|
|
348
358
|
return (_jsx(FeedbackLabelsContext, { value: labels, children: _jsx(FeedbackSheetContent, { ...props }) }));
|
|
349
359
|
}
|
|
350
|
-
function FeedbackSheetContent({ open, onOpenChange, actions, sent, errorDetail, ...formProps }) {
|
|
360
|
+
function FeedbackSheetContent({ open, onOpenChange, actions, sent, operator, errorDetail, ...formProps }) {
|
|
351
361
|
const labels = useFeedbackLabels();
|
|
362
|
+
const operatorLabels = useCiridaeLocale().labels.feedbackOperator;
|
|
352
363
|
const [tab, setTab] = useState("new");
|
|
353
364
|
const [openItemId, setOpenItemId] = useState(null);
|
|
354
|
-
const
|
|
365
|
+
const showAll = tab === "tickets" && operator !== undefined;
|
|
366
|
+
const showSent = tab === "tickets" && !showAll && sent !== undefined;
|
|
355
367
|
const close = () => {
|
|
356
368
|
onOpenChange(false);
|
|
357
369
|
setOpenItemId(null);
|
|
358
370
|
};
|
|
359
|
-
|
|
371
|
+
const back = () => setOpenItemId(null);
|
|
372
|
+
return (_jsx(Drawer, { open: open, onOpenChange: (next) => (next ? onOpenChange(true) : close()), title: showAll ? operatorLabels.allTitle : showSent ? labels.sentTitle : labels.sendTitle, icon: _jsx(ChatCircleTextIcon, { className: icon({ size: "small" }), weight: "fill" }), actions: _jsxs(_Fragment, { children: [(sent || operator) && (_jsx(TabsRoot, { value: tab, onValueChange: (value) => {
|
|
360
373
|
setTab(value);
|
|
361
374
|
setOpenItemId(null);
|
|
362
|
-
}, children: _jsxs(TabsList, { variant: "contained", children: [_jsx(TabsTab, { value: "new", children: labels.newTab }), _jsx(TabsTab, { value: "
|
|
375
|
+
}, children: _jsxs(TabsList, { variant: "contained", children: [_jsx(TabsTab, { value: "new", children: labels.newTab }), _jsx(TabsTab, { value: "tickets", children: labels.ticketsTab })] }) })), actions] }), children: showAll ? (openItemId === null ? (_jsx(OperatorList, { operator: operator, ownIds: sent?.items.status === "loaded"
|
|
376
|
+
? new Set(sent.items.data.map((item) => item.id))
|
|
377
|
+
: undefined, errorDetail: errorDetail, onOpen: (feedbackId) => {
|
|
378
|
+
operator.onOpenItem(feedbackId);
|
|
379
|
+
setOpenItemId(feedbackId);
|
|
380
|
+
} })) : (_jsx(OperatorItem, { feedbackId: openItemId, operator: operator, errorDetail: errorDetail, onBack: back }, openItemId))) : !showSent ? (_jsx(FeedbackForm, { ...formProps, errorDetail: errorDetail, onClose: close })) : openItemId === null ? (_jsx(FeedbackList, { items: sent.items, rows: (items) => items.map((item) => ({
|
|
381
|
+
id: item.id,
|
|
382
|
+
subject: item.subject,
|
|
383
|
+
status: item.status,
|
|
384
|
+
meta: (_jsxs(_Fragment, { children: [typeLabel(item.type, labels), " \u00B7", " ", _jsx(DateTime, { value: item.updatedAt_epoch_milliseconds })] })),
|
|
385
|
+
})), empty: labels.noneSent, errorDetail: errorDetail, onOpen: (feedbackId) => {
|
|
363
386
|
sent.onOpenItem(feedbackId);
|
|
364
387
|
setOpenItemId(feedbackId);
|
|
365
|
-
} })) : (_jsx(SentItem, { feedbackId: openItemId, item: sent.item, errorDetail: errorDetail, onReply: sent.onReply, onWithdraw: sent.onWithdraw, onBack:
|
|
388
|
+
} })) : (_jsx(SentItem, { feedbackId: openItemId, item: sent.item, errorDetail: errorDetail, onReply: sent.onReply, onWithdraw: sent.onWithdraw, onBack: back }, openItemId)) }));
|
|
366
389
|
}
|
|
367
390
|
const placeholderWidths = ["58%", "42%", "50%"];
|
|
368
|
-
|
|
391
|
+
/** A list of feedback rows: loading, failed, empty, or each row's subject, meta and status. */
|
|
392
|
+
function FeedbackList({ items, rows, empty, toolbar, errorDetail, onOpen, }) {
|
|
369
393
|
const labels = useFeedbackLabels();
|
|
370
394
|
const { common } = useCiridaeLocale().labels;
|
|
371
395
|
if (items.status === "error") {
|
|
@@ -374,13 +398,68 @@ function SentList({ items, errorDetail, onOpen, }) {
|
|
|
374
398
|
if (items.status === "loading") {
|
|
375
399
|
return (_jsx(DrawerBody, { flush: true, children: _jsx("ul", { className: sentList, "aria-busy": "true", children: placeholderWidths.map((width) => (_jsxs("li", { className: sentRow, children: [_jsxs("span", { className: sentRowCopy, children: [_jsx(Skeleton, { shape: "text", style: { width } }), _jsx(Skeleton, { shape: "text", style: { width: "30%" } })] }), _jsx(Skeleton, { shape: "text", style: { width: 64 } })] }, width))) }) }));
|
|
376
400
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
401
|
+
const shown = rows(items.data);
|
|
402
|
+
return (_jsxs(DrawerBody, { flush: true, children: [toolbar, shown.length === 0 ? (_jsx("p", { className: sentEmpty, children: empty })) : (_jsx("ul", { className: sentList, children: shown.map((row) => {
|
|
403
|
+
const needsReply = row.status === "discussion_required";
|
|
404
|
+
const display = statusDisplay(row.status, labels);
|
|
405
|
+
return (_jsx("li", { children: _jsxs("button", { type: "button", className: sentRow, "data-needs-reply": needsReply ? "" : undefined, onClick: () => onOpen(row.id), children: [_jsxs("span", { className: sentRowCopy, children: [_jsxs("span", { className: sentRowSubject, children: [needsReply && _jsx("span", { className: needsReplyDot, "aria-hidden": "true" }), _jsx("span", { children: row.subject })] }), _jsx("span", { className: sentRowMeta, children: row.meta })] }), _jsx(Status, { variant: display.variant, children: display.label })] }) }, row.id));
|
|
406
|
+
}) }))] }));
|
|
407
|
+
}
|
|
408
|
+
/** Everyone's feedback, latest change first, narrowed by status, type and a search. */
|
|
409
|
+
function OperatorList({ operator, ownIds, errorDetail, onOpen, }) {
|
|
410
|
+
const labels = useFeedbackLabels();
|
|
411
|
+
const operatorLabels = useCiridaeLocale().labels.feedbackOperator;
|
|
412
|
+
const [filters, setFilters] = useState([]);
|
|
413
|
+
const [search, setSearch] = useState("");
|
|
414
|
+
const query = search.trim().toLowerCase();
|
|
415
|
+
const fields = [
|
|
416
|
+
...(ownIds
|
|
417
|
+
? [
|
|
418
|
+
{
|
|
419
|
+
key: "reporter",
|
|
420
|
+
label: operatorLabels.reporter,
|
|
421
|
+
kind: "multi",
|
|
422
|
+
options: [{ value: "mine", label: operatorLabels.mine }],
|
|
423
|
+
},
|
|
424
|
+
]
|
|
425
|
+
: []),
|
|
426
|
+
{
|
|
427
|
+
key: "status",
|
|
428
|
+
label: operatorLabels.status,
|
|
429
|
+
kind: "multi",
|
|
430
|
+
options: feedbackStatusSchema.options.map((value) => ({
|
|
431
|
+
value,
|
|
432
|
+
label: statusDisplay(value, labels).label,
|
|
433
|
+
})),
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
key: "type",
|
|
437
|
+
label: operatorLabels.type,
|
|
438
|
+
kind: "multi",
|
|
439
|
+
options: feedbackTypeSchema.options.map((value) => ({
|
|
440
|
+
value,
|
|
441
|
+
label: typeLabel(value, labels),
|
|
442
|
+
})),
|
|
443
|
+
},
|
|
444
|
+
];
|
|
445
|
+
return (_jsx(FeedbackList, { items: operator.items, rows: (items) => items
|
|
446
|
+
.filter((item) => filters.every((filter) => matchesFilter(filter.field === "reporter"
|
|
447
|
+
? ownIds?.has(item.id)
|
|
448
|
+
? "mine"
|
|
449
|
+
: "others"
|
|
450
|
+
: filter.field === "status"
|
|
451
|
+
? item.status
|
|
452
|
+
: item.type, filter)) &&
|
|
453
|
+
(query === "" ||
|
|
454
|
+
[item.subject, item.description, item.reporterEmail].some((value) => value?.toLowerCase().includes(query))))
|
|
455
|
+
// ISO timestamps in one format sort as text.
|
|
456
|
+
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
|
|
457
|
+
.map((item) => ({
|
|
458
|
+
id: item.id,
|
|
459
|
+
subject: item.subject,
|
|
460
|
+
status: item.status,
|
|
461
|
+
meta: (_jsxs(_Fragment, { children: [item.reporterEmail ?? operatorLabels.reporter, " \u00B7 ", typeLabel(item.type, labels), " \u00B7", " ", _jsx(DateTime, { value: item.updatedAt })] })),
|
|
462
|
+
})), empty: filters.length > 0 || query !== "" ? operatorLabels.noMatches : operatorLabels.noneReceived, toolbar: _jsx(FilterBar, { className: listToolbar, fields: fields, value: filters, onValueChange: setFilters, children: _jsx(SearchInput, { variant: "ghost", "aria-label": operatorLabels.search, placeholder: operatorLabels.search, value: search, onValueCommit: setSearch }) }), errorDetail: errorDetail, onOpen: onOpen }));
|
|
384
463
|
}
|
|
385
464
|
/* What the thread shows in time order: the description, each status change, each message. */
|
|
386
465
|
function listThreadEntries(item, labels) {
|
|
@@ -413,37 +492,60 @@ function listThreadEntries(item, labels) {
|
|
|
413
492
|
];
|
|
414
493
|
return entries.sort((earlier, later) => earlier.at - later.at);
|
|
415
494
|
}
|
|
416
|
-
|
|
495
|
+
/**
|
|
496
|
+
* The operator's side of the same thread: the reporter's description and messages across from
|
|
497
|
+
* them, their own team's replies on their side.
|
|
498
|
+
*/
|
|
499
|
+
function listOperatorThreadEntries(item, reporter) {
|
|
500
|
+
const entries = [
|
|
501
|
+
{
|
|
502
|
+
kind: "message",
|
|
503
|
+
id: "description",
|
|
504
|
+
side: "assistant",
|
|
505
|
+
author: { name: reporter },
|
|
506
|
+
at: Date.parse(item.createdAt),
|
|
507
|
+
body: item.description,
|
|
508
|
+
attachments: item.attachments,
|
|
509
|
+
},
|
|
510
|
+
...item.messages.map((message) => {
|
|
511
|
+
const fromReporter = message.author.role === "reporter";
|
|
512
|
+
return {
|
|
513
|
+
kind: "message",
|
|
514
|
+
id: message.id,
|
|
515
|
+
side: fromReporter ? "assistant" : "user",
|
|
516
|
+
author: fromReporter ? { name: message.author.name } : undefined,
|
|
517
|
+
at: message.createdAt_epoch_milliseconds,
|
|
518
|
+
body: message.body,
|
|
519
|
+
};
|
|
520
|
+
}),
|
|
521
|
+
];
|
|
522
|
+
return entries.sort((earlier, later) => earlier.at - later.at);
|
|
523
|
+
}
|
|
524
|
+
/** Runs `action`, reporting it pending, then idle, or failed with its error. */
|
|
525
|
+
function usePendingAction(action) {
|
|
526
|
+
const [state, setState] = useState({ status: "idle" });
|
|
527
|
+
const run = async (input) => {
|
|
528
|
+
setState({ status: "pending" });
|
|
529
|
+
try {
|
|
530
|
+
await action(input);
|
|
531
|
+
setState({ status: "idle" });
|
|
532
|
+
}
|
|
533
|
+
catch (error) {
|
|
534
|
+
setState({ status: "failed", error });
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
return { state, run: (input) => void run(input) };
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* One item as a thread: its header pinned over the conversation, then the failures and the reply
|
|
541
|
+
* below. Loading and failing to load keep the back button.
|
|
542
|
+
*/
|
|
543
|
+
function FeedbackThreadItem({ feedbackId, item, loaded, headerEnd, notices, replyDisabled, errorDetail, onReply, onBack, }) {
|
|
417
544
|
const labels = useFeedbackLabels();
|
|
418
545
|
const { common } = useCiridaeLocale().labels;
|
|
419
546
|
const [draft, setDraft] = useState("");
|
|
420
547
|
const [reply, setReply] = useState({ status: "idle" });
|
|
421
|
-
const [withdraw, setWithdraw] = useState({ status: "idle" });
|
|
422
|
-
// The item the caller last opened is this one only once its load carries this id.
|
|
423
|
-
const loaded = item?.status === "loaded" && item.data.id === feedbackId ? item.data : null;
|
|
424
|
-
const entries = loaded ? listThreadEntries(loaded, labels) : [];
|
|
425
|
-
if (reply.status === "sending") {
|
|
426
|
-
entries.push({
|
|
427
|
-
kind: "message",
|
|
428
|
-
id: "sending",
|
|
429
|
-
side: "user",
|
|
430
|
-
at: reply.at,
|
|
431
|
-
body: reply.body,
|
|
432
|
-
sending: true,
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
548
|
const back = (_jsx(IconButton, { "aria-label": labels.back, variant: "ghost", size: "small", onClick: onBack, children: _jsx(ArrowLeftIcon, { className: cx(icon({ size: "small" }), directional) }) }));
|
|
436
|
-
const actions = [
|
|
437
|
-
{ label: labels.copyId, onSelect: () => void navigator.clipboard.writeText(feedbackId) },
|
|
438
|
-
];
|
|
439
|
-
if (loaded?.canWithdraw) {
|
|
440
|
-
actions.push({
|
|
441
|
-
label: labels.withdraw,
|
|
442
|
-
variant: "danger",
|
|
443
|
-
disabled: reply.status === "sending",
|
|
444
|
-
onSelect: () => void sendWithdraw(),
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
549
|
const sendReply = async (body) => {
|
|
448
550
|
setReply({ status: "sending", body, at: Date.now() });
|
|
449
551
|
try {
|
|
@@ -455,21 +557,80 @@ function SentItem({ feedbackId, item, errorDetail, onReply, onWithdraw, onBack,
|
|
|
455
557
|
setReply({ status: "failed", error });
|
|
456
558
|
}
|
|
457
559
|
};
|
|
458
|
-
const sendWithdraw = async () => {
|
|
459
|
-
setWithdraw({ status: "pending" });
|
|
460
|
-
try {
|
|
461
|
-
await onWithdraw(feedbackId);
|
|
462
|
-
setWithdraw({ status: "idle" });
|
|
463
|
-
}
|
|
464
|
-
catch (error) {
|
|
465
|
-
setWithdraw({ status: "failed", error });
|
|
466
|
-
}
|
|
467
|
-
};
|
|
468
560
|
if (item?.status === "error") {
|
|
469
561
|
return (_jsxs(DrawerBody, { flush: true, children: [_jsx("div", { className: itemHeader, children: back }), _jsx("div", { className: padded, children: _jsx(FailedNotice, { title: labels.loadItemFailed, action: _jsx("button", { type: "button", onClick: item.retry, children: common.tryAgain }), children: errorDetail?.(item.error) }) })] }));
|
|
470
562
|
}
|
|
471
563
|
if (!loaded) {
|
|
472
564
|
return (_jsxs(DrawerBody, { flush: true, "aria-busy": "true", children: [_jsxs("div", { className: itemHeader, children: [back, _jsxs("span", { className: itemTitle, children: [_jsx(Skeleton, { shape: "text", style: { width: "60%" } }), _jsx(Skeleton, { shape: "text", style: { width: "30%" } })] })] }), _jsx("div", { className: padded, children: _jsxs("span", { className: placeholderLines, children: [_jsx(Skeleton, { shape: "text" }), _jsx(Skeleton, { shape: "text", style: { width: "80%" } }), _jsx(Skeleton, { shape: "text", style: { width: "50%" } })] }) })] }));
|
|
473
565
|
}
|
|
474
|
-
|
|
566
|
+
const entries = reply.status === "sending"
|
|
567
|
+
? [
|
|
568
|
+
...loaded.entries,
|
|
569
|
+
{
|
|
570
|
+
kind: "message",
|
|
571
|
+
id: "sending",
|
|
572
|
+
side: "user",
|
|
573
|
+
at: reply.at,
|
|
574
|
+
body: reply.body,
|
|
575
|
+
sending: true,
|
|
576
|
+
},
|
|
577
|
+
]
|
|
578
|
+
: loaded.entries;
|
|
579
|
+
return (_jsxs(_Fragment, { children: [_jsxs(DrawerBody, { flush: true, children: [_jsxs("div", { className: itemHeader, children: [back, _jsxs("span", { className: itemTitle, children: [_jsx("span", { className: itemSubject, children: loaded.subject }), _jsx("span", { className: sentRowMeta, children: loaded.meta })] }), headerEnd] }), _jsx(ChatThread, { entries: entries })] }), (loaded.canReply || reply.status === "failed" || notices) && (_jsxs("div", { className: itemFoot, children: [reply.status === "failed" && (_jsx(FailedNotice, { title: labels.replyFailed, children: errorDetail?.(reply.error) })), notices, loaded.canReply && (_jsx(ChatComposer, { variant: "sheet", placeholder: labels.reply, ariaLabel: labels.reply, submitLabel: labels.sendReply, value: draft, onValueChange: setDraft, sendDisabled: replyDisabled || reply.status === "sending", onSend: (body) => void sendReply(body.trim()) }))] }))] }));
|
|
580
|
+
}
|
|
581
|
+
function SentItem({ feedbackId, item, errorDetail, onReply, onWithdraw, onBack, }) {
|
|
582
|
+
const labels = useFeedbackLabels();
|
|
583
|
+
const withdraw = usePendingAction(() => onWithdraw(feedbackId));
|
|
584
|
+
const copyId = useCopy(feedbackId);
|
|
585
|
+
// The item the caller last opened is this one only once its load carries this id.
|
|
586
|
+
const loaded = item?.status === "loaded" && item.data.id === feedbackId ? item.data : null;
|
|
587
|
+
const actions = [{ label: labels.copyId, onSelect: copyId.copy }];
|
|
588
|
+
if (loaded?.canWithdraw) {
|
|
589
|
+
actions.push({
|
|
590
|
+
label: labels.withdraw,
|
|
591
|
+
variant: "danger",
|
|
592
|
+
onSelect: () => withdraw.run(undefined),
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
const display = loaded && statusDisplay(loaded.status, labels);
|
|
596
|
+
return (_jsx(FeedbackThreadItem, { feedbackId: feedbackId, item: item, loaded: loaded && {
|
|
597
|
+
subject: loaded.subject,
|
|
598
|
+
meta: (_jsxs(_Fragment, { children: [typeLabel(loaded.type, labels), " \u00B7", " ", _jsx(DateTime, { value: loaded.createdAt_epoch_milliseconds })] })),
|
|
599
|
+
entries: listThreadEntries(loaded, labels),
|
|
600
|
+
canReply: loaded.canReply,
|
|
601
|
+
}, headerEnd: _jsxs(_Fragment, { children: [display && (_jsx(Status, { className: itemStatus, variant: display.variant, children: display.label })), _jsx(Menu, { align: "end", items: actions, trigger: _jsx(IconButton, { "aria-label": labels.actions, variant: "ghost", size: "small", disabled: withdraw.state.status === "pending", children: withdraw.state.status === "pending" ? (_jsx(Spinner, { label: labels.withdrawing })) : (_jsx(DotsThreeIcon, { className: icon({ size: "small" }) })) }) })] }), notices: withdraw.state.status === "failed" && (_jsx(FailedNotice, { title: labels.withdrawFailed, children: errorDetail?.(withdraw.state.error) })), replyDisabled: withdraw.state.status === "pending", errorDetail: errorDetail, onReply: onReply, onBack: onBack }));
|
|
602
|
+
}
|
|
603
|
+
/** An item as its operator sees it: the reporter's thread, a status to set, and a reply. */
|
|
604
|
+
function OperatorItem({ feedbackId, operator, errorDetail, onBack, }) {
|
|
605
|
+
const labels = useFeedbackLabels();
|
|
606
|
+
const operatorLabels = useCiridaeLocale().labels.feedbackOperator;
|
|
607
|
+
const [pendingStatus, setPendingStatus] = useState(null);
|
|
608
|
+
const statusChange = usePendingAction(async (status) => {
|
|
609
|
+
setPendingStatus(status);
|
|
610
|
+
try {
|
|
611
|
+
await operator.onStatusChange(feedbackId, status);
|
|
612
|
+
}
|
|
613
|
+
finally {
|
|
614
|
+
setPendingStatus(null);
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
const { item } = operator;
|
|
618
|
+
const loaded = item?.status === "loaded" && item.data.id === feedbackId ? item.data : null;
|
|
619
|
+
const status = pendingStatus ?? loaded?.status;
|
|
620
|
+
const reporter = loaded?.reporterEmail ?? operatorLabels.reporter;
|
|
621
|
+
const statusOptions = [
|
|
622
|
+
// A withdrawn item is the reporter's call; it shows, but cannot be chosen.
|
|
623
|
+
...(status === "withdrawn" ? ["withdrawn"] : []),
|
|
624
|
+
...feedbackTriageStatusSchema.options,
|
|
625
|
+
].map((value) => ({ value, label: statusDisplay(value, labels).label }));
|
|
626
|
+
return (_jsx(FeedbackThreadItem, { feedbackId: feedbackId, item: item, loaded: loaded && {
|
|
627
|
+
subject: loaded.subject,
|
|
628
|
+
meta: (_jsxs(_Fragment, { children: [reporter, " \u00B7 ", typeLabel(loaded.type, labels), " \u00B7 ", _jsx(DateTime, { value: loaded.createdAt })] })),
|
|
629
|
+
entries: listOperatorThreadEntries(loaded, reporter),
|
|
630
|
+
canReply: loaded.canReply,
|
|
631
|
+
}, headerEnd: _jsx(Select, { label: operatorLabels.status, options: statusOptions, value: status, disabled: status === "withdrawn" || statusChange.state.status === "pending", onValueChange: (value) => {
|
|
632
|
+
const next = feedbackTriageStatusSchema.safeParse(value);
|
|
633
|
+
if (next.success && next.data !== status)
|
|
634
|
+
statusChange.run(next.data);
|
|
635
|
+
} }), notices: statusChange.state.status === "failed" && (_jsx(FailedNotice, { title: operatorLabels.statusFailed, children: errorDetail?.(statusChange.state.error) })), errorDetail: errorDetail, onReply: operator.onReply, onBack: onBack }));
|
|
475
636
|
}
|
|
@@ -8,7 +8,7 @@ export type FieldControl = {
|
|
|
8
8
|
descriptionId?: string;
|
|
9
9
|
invalid: boolean;
|
|
10
10
|
};
|
|
11
|
-
export declare const FieldControlContext: import("
|
|
11
|
+
export declare const FieldControlContext: import("react").Context<FieldControl | null>;
|
|
12
12
|
/** The ids and state a control takes from its field; its own props win. */
|
|
13
13
|
export declare function useFieldControl(own: {
|
|
14
14
|
id?: string;
|
|
@@ -49,7 +49,7 @@ export declare function FilterChip({ label, icon: fieldIcon, operator, values, o
|
|
|
49
49
|
values: ReactNode;
|
|
50
50
|
onEdit?: () => void;
|
|
51
51
|
onRemove: () => void;
|
|
52
|
-
}): import("
|
|
52
|
+
}): import("react").JSX.Element;
|
|
53
53
|
/**
|
|
54
54
|
* The set filters as chips, Clear all, and the Add filter builder: pick a field, then its operator
|
|
55
55
|
* and values, which apply as they are picked. Pressing a chip reopens the builder on its field.
|
|
@@ -63,7 +63,7 @@ export declare function FilterBar({ fields, value, defaultValue, onValueChange,
|
|
|
63
63
|
onValueChange?: (value: FilterValue[]) => void;
|
|
64
64
|
open?: boolean;
|
|
65
65
|
onOpenChange?: (open: boolean) => void;
|
|
66
|
-
}): import("
|
|
66
|
+
}): import("react").JSX.Element;
|
|
67
67
|
/**
|
|
68
68
|
* One field's operator and values, reporting every change so the list follows along. `onBack`
|
|
69
69
|
* adds the way back to the builder's field list; without it the field is fixed, as under a
|
|
@@ -74,4 +74,4 @@ export declare function FilterValueEditor({ field, value, onChange, onBack, }: {
|
|
|
74
74
|
value: FilterValue | undefined;
|
|
75
75
|
onChange: (filter: FilterValue) => void;
|
|
76
76
|
onBack?: () => void;
|
|
77
|
-
}): import("
|
|
77
|
+
}): import("react").JSX.Element;
|
|
@@ -7,14 +7,15 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
7
7
|
* alone, such as a column head's "Filter by". Nothing here filters rows: `matchesFilter` tests one
|
|
8
8
|
* value for a list that filters on the client.
|
|
9
9
|
*/
|
|
10
|
-
import { CaretDownIcon, CaretLeftIcon, PlusIcon, XIcon } from "@phosphor-icons/react";
|
|
10
|
+
import { CaretDownIcon, CaretLeftIcon, CaretRightIcon, PlusIcon, XIcon, } from "@phosphor-icons/react";
|
|
11
11
|
import { useState } from "react";
|
|
12
12
|
import { css, cx } from "#primitives/css";
|
|
13
13
|
import { Button, IconButton } from "./button.js";
|
|
14
14
|
import { CheckboxItem } from "./checkbox-item.js";
|
|
15
15
|
import { useControllableState } from "./controllable-state.js";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { glassMenuDivider, glassMenuRow } from "./glass-menu.js";
|
|
17
|
+
import { faceEdge } from "./gradient-edge.js";
|
|
18
|
+
import { directional, icon } from "./icon.js";
|
|
18
19
|
import { Input, SearchField } from "./input.js";
|
|
19
20
|
import { useCiridaeLocale } from "./locale.js";
|
|
20
21
|
import { Menu } from "./menu.js";
|
|
@@ -90,6 +91,10 @@ export function describeFilter(field, filter, locale) {
|
|
|
90
91
|
: values.join(", "),
|
|
91
92
|
};
|
|
92
93
|
}
|
|
94
|
+
/*
|
|
95
|
+
* The slicer's face: the selected tint, hovering to the raised face. The field reads in the
|
|
96
|
+
* secondary grey, the operator a step quieter.
|
|
97
|
+
*/
|
|
93
98
|
const chip = css({
|
|
94
99
|
display: "inline-flex",
|
|
95
100
|
alignItems: "center",
|
|
@@ -99,7 +104,9 @@ const chip = css({
|
|
|
99
104
|
paddingInlineStart: "control.compactInline",
|
|
100
105
|
paddingInlineEnd: "control.iconGap",
|
|
101
106
|
borderRadius: "pill",
|
|
102
|
-
background: "
|
|
107
|
+
background: "token(gradients.selected)",
|
|
108
|
+
_hover: { background: "token(gradients.raisedHover)" },
|
|
109
|
+
_light: { color: "fg.secondary" },
|
|
103
110
|
color: "fg.muted",
|
|
104
111
|
fontSize: "caption",
|
|
105
112
|
fontWeight: "medium",
|
|
@@ -122,6 +129,7 @@ const chipBody = css({
|
|
|
122
129
|
_focusVisible: { focusRing: "outer" },
|
|
123
130
|
"& svg": { flexShrink: "0" },
|
|
124
131
|
});
|
|
132
|
+
const chipOperator = css({ color: "fg.muted" });
|
|
125
133
|
const chipValue = css({ color: "fg.primary", overflow: "hidden", textOverflow: "ellipsis" });
|
|
126
134
|
const chipRemove = css({
|
|
127
135
|
display: "inline-grid",
|
|
@@ -138,10 +146,11 @@ const chipRemove = css({
|
|
|
138
146
|
_hover: { color: "fg.primary", background: "bg.hover" },
|
|
139
147
|
_focusVisible: { focusRing: "outer" },
|
|
140
148
|
});
|
|
149
|
+
const toolbarEdge = faceEdge("toolbar");
|
|
141
150
|
/** One set filter: the field and operator muted, its values in full; the text reopens it. */
|
|
142
151
|
export function FilterChip({ label, icon: fieldIcon, operator, values, onEdit, onRemove, className, ...props }) {
|
|
143
152
|
const { labels } = useCiridaeLocale();
|
|
144
|
-
return (_jsxs("span", { ...props, className: cx(chip, className), "data-ciridae-ui": "", "data-edge": "
|
|
153
|
+
return (_jsxs("span", { ...props, className: cx(chip, toolbarEdge.className, className), "data-ciridae-ui": "", "data-edge": toolbarEdge["data-edge"], children: [_jsxs("button", { type: "button", className: chipBody, onClick: onEdit, children: [fieldIcon, label, " ", _jsx("span", { className: chipOperator, children: operator }), _jsx("span", { className: chipValue, children: values })] }), _jsx("button", { type: "button", className: chipRemove, "aria-label": labels.filters.remove(label), onClick: onRemove, children: _jsx(XIcon, { className: icon({ size: "small" }) }) })] }));
|
|
145
154
|
}
|
|
146
155
|
const bar = css({
|
|
147
156
|
display: "flex",
|
|
@@ -158,37 +167,41 @@ const chips = css({
|
|
|
158
167
|
gap: "layout.controlGap",
|
|
159
168
|
minWidth: "0",
|
|
160
169
|
});
|
|
170
|
+
// One line: the buttons keep their size and the search after them takes what the row leaves.
|
|
161
171
|
const tools = css({
|
|
162
172
|
display: "flex",
|
|
173
|
+
flex: "1",
|
|
163
174
|
alignItems: "center",
|
|
164
175
|
justifyContent: "flex-end",
|
|
165
|
-
flexWrap: "wrap",
|
|
166
176
|
gap: "layout.controlGap",
|
|
167
177
|
minWidth: "0",
|
|
168
178
|
marginInlineStart: "auto",
|
|
179
|
+
"& > button": { flexShrink: "0" },
|
|
180
|
+
"& > :not(button)": { minWidth: "0" },
|
|
169
181
|
});
|
|
170
|
-
const builder = css({
|
|
171
|
-
const fieldItem = css({
|
|
182
|
+
const builder = css({
|
|
172
183
|
display: "flex",
|
|
173
|
-
|
|
174
|
-
|
|
184
|
+
flexDirection: "column",
|
|
185
|
+
width: "overlay.popover",
|
|
186
|
+
maxWidth: "100%",
|
|
187
|
+
});
|
|
188
|
+
// A menu row: the field's icon, its name, and an arrow on to its operator and values.
|
|
189
|
+
const fieldItem = css(glassMenuRow, {
|
|
175
190
|
width: "100%",
|
|
176
|
-
minHeight: "control.compact",
|
|
177
|
-
paddingInline: "menu.itemInline",
|
|
178
191
|
border: "0",
|
|
179
|
-
borderRadius: "control",
|
|
180
192
|
background: "transparent",
|
|
181
|
-
|
|
182
|
-
font: "inherit",
|
|
183
|
-
fontSize: "body",
|
|
184
|
-
fontWeight: "medium",
|
|
185
|
-
letterSpacing: "compact",
|
|
193
|
+
fontFamily: "inherit",
|
|
186
194
|
textAlign: "start",
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
195
|
+
"& > span": { flex: "1", minWidth: "0" },
|
|
196
|
+
"& svg": {
|
|
197
|
+
flexShrink: "0",
|
|
198
|
+
color: "fg.muted",
|
|
199
|
+
fontSize: "icon.small",
|
|
200
|
+
width: "1em",
|
|
201
|
+
height: "1em",
|
|
202
|
+
},
|
|
191
203
|
});
|
|
204
|
+
const menuDivider = css(glassMenuDivider);
|
|
192
205
|
const groupLabel = css({
|
|
193
206
|
paddingBlockStart: "control.block",
|
|
194
207
|
paddingBlockEnd: "layout.tightGap",
|
|
@@ -198,7 +211,8 @@ const groupLabel = css({
|
|
|
198
211
|
fontWeight: "medium",
|
|
199
212
|
letterSpacing: "compact",
|
|
200
213
|
});
|
|
201
|
-
|
|
214
|
+
// The quiet search's hairline is inset as the dividers are.
|
|
215
|
+
const searchWrap = css({ paddingInline: "menu.itemInline", paddingBlockEnd: "layout.tightGap" });
|
|
202
216
|
const noMatches = css({
|
|
203
217
|
paddingBlock: "control.block",
|
|
204
218
|
paddingInline: "menu.itemInline",
|
|
@@ -232,7 +246,7 @@ export function FilterBar({ fields, value, defaultValue = [], onValueChange, ope
|
|
|
232
246
|
setEditing(field.key);
|
|
233
247
|
setOpen(true);
|
|
234
248
|
}, onRemove: () => setFilters(filters.filter((candidate) => candidate.field !== filter.field)) }, filter.field));
|
|
235
|
-
}) })), _jsxs("div", { className: tools, children: [filters.length > 0 && (_jsx(Button, { variant: "ghost", onClick: () => setFilters([]), children: labels.filters.clearAll })), _jsx(Popover, { open: open, onOpenChange: changeOpen, density: "compact", trigger: _jsx(Button, { variant: "
|
|
249
|
+
}) })), _jsxs("div", { className: tools, children: [filters.length > 0 && (_jsx(Button, { variant: "ghost", onClick: () => setFilters([]), children: labels.filters.clearAll })), _jsx(Popover, { open: open, onOpenChange: changeOpen, density: "compact", trigger: _jsx(Button, { variant: "secondary", size: "medium", icon: _jsx(PlusIcon, { className: icon({ size: "small" }) }), onClick: () => setEditing(null), children: labels.filters.addFilter }), children: open && (_jsx(FilterBuilder, { fields: fields, startField: editing, filters: filters, onChange: (filter) => setFilters(replaceFilter(filters, filter)) }, editing ?? "")) }), children] })] }));
|
|
236
250
|
}
|
|
237
251
|
function FilterBuilder({ fields, startField, filters, onChange, }) {
|
|
238
252
|
const { labels } = useCiridaeLocale();
|
|
@@ -251,10 +265,10 @@ function FilterBuilder({ fields, startField, filters, onChange, }) {
|
|
|
251
265
|
else
|
|
252
266
|
groups.push({ label: candidate.group, fields: [candidate] });
|
|
253
267
|
}
|
|
254
|
-
return (_jsxs("div", { className: builder, children: [_jsx("div", { className: searchWrap, children: _jsx(SearchField, { size: "compact", "aria-label": labels.filters.filterBy, placeholder: labels.filters.filterBy, value: query, onChange: (event) => setQuery(event.target.value) }) }), groups.map((group, index) => (_jsxs("div", { className: css({ display: "grid" }), children: [index > 0 && _jsx(
|
|
268
|
+
return (_jsxs("div", { className: builder, children: [_jsx("div", { className: searchWrap, children: _jsx(SearchField, { size: "compact", quiet: true, "aria-label": labels.filters.filterBy, placeholder: labels.filters.filterBy, value: query, onChange: (event) => setQuery(event.target.value) }) }), groups.map((group, index) => (_jsxs("div", { className: css({ display: "grid" }), children: [index > 0 && _jsx("div", { className: menuDivider, role: "separator" }), group.label && _jsx("span", { className: groupLabel, children: group.label }), group.fields.map((candidate) => (_jsxs("button", { type: "button", className: fieldItem, onClick: () => {
|
|
255
269
|
setFieldKey(candidate.key);
|
|
256
270
|
setQuery("");
|
|
257
|
-
}, children: [candidate.icon, candidate.label] }, candidate.key)))] }, group.label ?? ""))), matching.length === 0 && _jsx("span", { className: noMatches, children: labels.filters.noFields })] }));
|
|
271
|
+
}, children: [candidate.icon, _jsx("span", { children: candidate.label }), _jsx(CaretRightIcon, { className: directional, "aria-hidden": "true" })] }, candidate.key)))] }, group.label ?? ""))), matching.length === 0 && _jsx("span", { className: noMatches, children: labels.filters.noFields })] }));
|
|
258
272
|
}
|
|
259
273
|
const editorHeader = css({
|
|
260
274
|
display: "flex",
|
|
@@ -326,13 +340,13 @@ export function FilterValueEditor({ field, value, onChange, onBack, }) {
|
|
|
326
340
|
const keepsValues = multiValueOperators.includes(candidate) === multiple && candidate !== "between";
|
|
327
341
|
report(candidate, keepsValues ? values : values.slice(0, 1));
|
|
328
342
|
},
|
|
329
|
-
})) })) : (_jsx("span", { className: fixedOperator, children: operatorLabel }))] }), options ? (_jsxs(_Fragment, { children: [field.options && field.options.length > 6 && (_jsx("div", { className: searchWrap, children: _jsx(SearchField, { size: "compact", "aria-label": labels.filters.searchValues(field.label), placeholder: labels.filters.searchValues(field.label), value: query, onChange: (event) => setQuery(event.target.value) }) })), _jsxs("div", { className: valueList, children: [options.map((option) => (_jsxs(CheckboxItem, { checked: values.includes(option.value), onCheckedChange: (checked) => report(operator, multiple
|
|
343
|
+
})) })) : (_jsx("span", { className: fixedOperator, children: operatorLabel }))] }), options ? (_jsxs(_Fragment, { children: [field.options && field.options.length > 6 && (_jsx("div", { className: searchWrap, children: _jsx(SearchField, { size: "compact", quiet: true, "aria-label": labels.filters.searchValues(field.label), placeholder: labels.filters.searchValues(field.label), value: query, onChange: (event) => setQuery(event.target.value) }) })), _jsxs("div", { className: valueList, children: [options.map((option) => (_jsxs(CheckboxItem, { checked: values.includes(option.value), onCheckedChange: (checked) => report(operator, multiple
|
|
330
344
|
? checked
|
|
331
345
|
? [...values, option.value]
|
|
332
346
|
: values.filter((candidate) => candidate !== option.value)
|
|
333
347
|
: checked
|
|
334
348
|
? [option.value]
|
|
335
|
-
: []), meta: option.count, children: [option.icon, option.label] }, option.value))), options.length === 0 && _jsx("span", { className: noMatches, children: labels.filters.noValues })] })] })) : (_jsx("div", { className: inputs, children: (operator === "between" ? [0, 1] : [0]).map((index) => (_jsx(Input, { type: field.kind === "date" ? "date" : field.kind === "number" ? "number" : "text", "aria-label": operator === "between"
|
|
349
|
+
: []), meta: option.count, children: [option.icon, option.label] }, option.value))), options.length === 0 && _jsx("span", { className: noMatches, children: labels.filters.noValues })] })] })) : (_jsx("div", { className: inputs, children: (operator === "between" ? [0, 1] : [0]).map((index) => (_jsx(Input, { size: "small", type: field.kind === "date" ? "date" : field.kind === "number" ? "number" : "text", "aria-label": operator === "between"
|
|
336
350
|
? index === 0
|
|
337
351
|
? labels.filters.from
|
|
338
352
|
: labels.filters.to
|
|
@@ -344,7 +358,7 @@ export function FilterValueEditor({ field, value, onChange, onBack, }) {
|
|
|
344
358
|
const next = [...values];
|
|
345
359
|
next[index] = event.target.value;
|
|
346
360
|
report(operator, next);
|
|
347
|
-
} }, index))) })), _jsx(
|
|
361
|
+
} }, index))) })), _jsx("div", { className: menuDivider, role: "separator" }), _jsxs("div", { className: editorFooter, children: [options
|
|
348
362
|
? `${labels.filters.selectedCount(picked)} · ${labels.filters.updatesAsYouPick}`
|
|
349
363
|
: labels.filters.updatesAsYouPick, _jsx(Button, { variant: "ghost", onClick: () => report(operator, []), children: labels.common.clear })] })] }));
|
|
350
364
|
}
|
|
@@ -23,13 +23,22 @@ export declare const checkMark: import("../styled-system/types/index.js").Recipe
|
|
|
23
23
|
};
|
|
24
24
|
};
|
|
25
25
|
}>;
|
|
26
|
+
/** What a check mark's element carries, so its filled face can draw the gradient edge. */
|
|
27
|
+
export declare const checkMarkEdge: {
|
|
28
|
+
readonly "data-ciridae-ui": "";
|
|
29
|
+
readonly "data-edge": "gradient";
|
|
30
|
+
};
|
|
26
31
|
export declare const checkIndicator: string;
|
|
32
|
+
/** The tick, or the dash of a mixed checkbox. */
|
|
33
|
+
export declare function CheckGlyph({ mixed }: {
|
|
34
|
+
mixed: boolean | undefined;
|
|
35
|
+
}): import("react").JSX.Element;
|
|
27
36
|
export declare function Checkbox({ children, className, wrapperClassName, ...props }: Omit<ComponentProps<typeof BaseCheckbox.Root>, "children" | "className"> & {
|
|
28
37
|
children?: ReactNode;
|
|
29
38
|
className?: string;
|
|
30
39
|
wrapperClassName?: string;
|
|
31
|
-
}): import("
|
|
32
|
-
export declare function Textarea({ className, ...props }: ComponentProps<"textarea">): import("
|
|
40
|
+
}): import("react").JSX.Element;
|
|
41
|
+
export declare function Textarea({ className, ...props }: ComponentProps<"textarea">): import("react").JSX.Element;
|
|
33
42
|
export declare const radioDot: string;
|
|
34
43
|
export type RadioGroupProps = Omit<ComponentProps<typeof BaseRadioGroup>, "children" | "defaultValue" | "name" | "onValueChange" | "value"> & {
|
|
35
44
|
name: string;
|
|
@@ -44,7 +53,7 @@ export type RadioGroupProps = Omit<ComponentProps<typeof BaseRadioGroup>, "child
|
|
|
44
53
|
value?: string;
|
|
45
54
|
onValueChange?: (value: string) => void;
|
|
46
55
|
};
|
|
47
|
-
export declare function RadioGroup({ name, options, label: groupLabel, defaultValue, value, onValueChange, layout, className, ...props }: RadioGroupProps): import("
|
|
56
|
+
export declare function RadioGroup({ name, options, label: groupLabel, defaultValue, value, onValueChange, layout, className, ...props }: RadioGroupProps): import("react").JSX.Element;
|
|
48
57
|
type SelectOption = {
|
|
49
58
|
label: string;
|
|
50
59
|
value: string;
|