pixelize-design-library 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -1
- package/COMPONENT-GOTCHAS.md +37 -13
- package/DESIGN-GUIDE.md +4 -1
- package/dist/Components/NotificationBell/NotificationBellProps.d.ts +3 -3
- package/dist/Components/NotificationBell/NotificationItem.d.ts +1 -1
- package/dist/Components/NotificationBell/NotificationItem.js +1 -1
- package/dist/Components/NotificationBell/NotificationList.d.ts +1 -1
- package/dist/Components/NotificationBell/NotificationList.js +1 -1
- package/dist/Components/NotificationBell/NotificationPanel.d.ts +1 -1
- package/dist/Components/NotificationBell/NotificationPanel.js +1 -1
- package/dist/Components/NotificationCenter/center/CenterPanel.d.ts +15 -0
- package/dist/Components/NotificationCenter/center/CenterPanel.js +87 -0
- package/dist/Components/NotificationCenter/center/NotificationCenter.d.ts +4 -0
- package/dist/Components/NotificationCenter/center/NotificationCenter.js +130 -0
- package/dist/Components/NotificationCenter/center/NotificationCenterProps.d.ts +71 -0
- package/dist/Components/NotificationCenter/center/NotificationCenterProps.js +2 -0
- package/dist/Components/NotificationCenter/center/PanelRows.d.ts +10 -0
- package/dist/Components/NotificationCenter/center/PanelRows.js +35 -0
- package/dist/Components/NotificationCenter/center/PanelStates.d.ts +17 -0
- package/dist/Components/NotificationCenter/center/PanelStates.js +21 -0
- package/dist/Components/NotificationCenter/center/centerLabels.d.ts +4 -0
- package/dist/Components/NotificationCenter/center/centerLabels.js +38 -0
- package/dist/Components/NotificationCenter/center/useArrivals.d.ts +14 -0
- package/dist/Components/NotificationCenter/center/useArrivals.js +71 -0
- package/dist/Components/NotificationCenter/center/useIsSheet.d.ts +2 -0
- package/dist/Components/NotificationCenter/center/useIsSheet.js +20 -0
- package/dist/Components/NotificationCenter/core/NotificationCenterProvider.d.ts +17 -0
- package/dist/Components/NotificationCenter/core/NotificationCenterProvider.js +46 -0
- package/dist/Components/NotificationCenter/core/NotificationEngineProps.d.ts +182 -0
- package/dist/Components/NotificationCenter/core/NotificationEngineProps.js +2 -0
- package/dist/Components/NotificationCenter/core/NotificationSourceProps.d.ts +284 -0
- package/dist/Components/NotificationCenter/core/NotificationSourceProps.js +2 -0
- package/dist/Components/NotificationCenter/core/actionError.d.ts +4 -0
- package/dist/Components/NotificationCenter/core/actionError.js +45 -0
- package/dist/Components/NotificationCenter/core/format.d.ts +4 -0
- package/dist/Components/NotificationCenter/core/format.js +91 -0
- package/dist/Components/NotificationCenter/core/grouping.d.ts +14 -0
- package/dist/Components/NotificationCenter/core/grouping.js +102 -0
- package/dist/Components/NotificationCenter/core/labels.d.ts +6 -0
- package/dist/Components/NotificationCenter/core/labels.js +94 -0
- package/dist/Components/NotificationCenter/core/overlay.d.ts +109 -0
- package/dist/Components/NotificationCenter/core/overlay.js +281 -0
- package/dist/Components/NotificationCenter/core/useNotificationEngine.d.ts +13 -0
- package/dist/Components/NotificationCenter/core/useNotificationEngine.js +415 -0
- package/dist/Components/NotificationCenter/inbox/InboxList.d.ts +17 -0
- package/dist/Components/NotificationCenter/inbox/InboxList.js +25 -0
- package/dist/Components/NotificationCenter/inbox/InboxStates.d.ts +17 -0
- package/dist/Components/NotificationCenter/inbox/InboxStates.js +27 -0
- package/dist/Components/NotificationCenter/inbox/NotificationInbox.d.ts +4 -0
- package/dist/Components/NotificationCenter/inbox/NotificationInbox.js +434 -0
- package/dist/Components/NotificationCenter/inbox/NotificationInboxProps.d.ts +106 -0
- package/dist/Components/NotificationCenter/inbox/NotificationInboxProps.js +2 -0
- package/dist/Components/NotificationCenter/inbox/ShortcutsDialog.d.ts +8 -0
- package/dist/Components/NotificationCenter/inbox/ShortcutsDialog.js +63 -0
- package/dist/Components/NotificationCenter/inbox/inboxLabels.d.ts +4 -0
- package/dist/Components/NotificationCenter/inbox/inboxLabels.js +56 -0
- package/dist/Components/NotificationCenter/inbox/inboxModel.d.ts +17 -0
- package/dist/Components/NotificationCenter/inbox/inboxModel.js +47 -0
- package/dist/Components/NotificationCenter/inbox/useControllable.d.ts +1 -0
- package/dist/Components/NotificationCenter/inbox/useControllable.js +15 -0
- package/dist/Components/NotificationCenter/index.d.ts +8 -0
- package/dist/Components/NotificationCenter/index.js +26 -0
- package/dist/Components/NotificationCenter/preferences/NotificationPreferences.d.ts +4 -0
- package/dist/Components/NotificationCenter/preferences/NotificationPreferences.js +72 -0
- package/dist/Components/NotificationCenter/preferences/NotificationPreferencesProps.d.ts +111 -0
- package/dist/Components/NotificationCenter/preferences/NotificationPreferencesProps.js +2 -0
- package/dist/Components/NotificationCenter/preferences/PreferenceCells.d.ts +16 -0
- package/dist/Components/NotificationCenter/preferences/PreferenceCells.js +35 -0
- package/dist/Components/NotificationCenter/preferences/PreferenceGroups.d.ts +15 -0
- package/dist/Components/NotificationCenter/preferences/PreferenceGroups.js +26 -0
- package/dist/Components/NotificationCenter/preferences/preferencesLabels.d.ts +2 -0
- package/dist/Components/NotificationCenter/preferences/preferencesLabels.js +28 -0
- package/dist/Components/NotificationCenter/preferences/useAutosave.d.ts +8 -0
- package/dist/Components/NotificationCenter/preferences/useAutosave.js +41 -0
- package/dist/Components/NotificationCenter/row/NotificationDetail.d.ts +4 -0
- package/dist/Components/NotificationCenter/row/NotificationDetail.js +65 -0
- package/dist/Components/NotificationCenter/row/NotificationDetailProps.d.ts +15 -0
- package/dist/Components/NotificationCenter/row/NotificationDetailProps.js +2 -0
- package/dist/Components/NotificationCenter/row/NotificationRow.d.ts +3 -0
- package/dist/Components/NotificationCenter/row/NotificationRow.js +124 -0
- package/dist/Components/NotificationCenter/row/NotificationRowProps.d.ts +33 -0
- package/dist/Components/NotificationCenter/row/NotificationRowProps.js +2 -0
- package/dist/Components/NotificationCenter/row/ReasonForm.d.ts +14 -0
- package/dist/Components/NotificationCenter/row/ReasonForm.js +59 -0
- package/dist/Components/NotificationCenter/row/RowActions.d.ts +16 -0
- package/dist/Components/NotificationCenter/row/RowActions.js +36 -0
- package/dist/Components/NotificationCenter/row/RowQuickActions.d.ts +17 -0
- package/dist/Components/NotificationCenter/row/RowQuickActions.js +57 -0
- package/dist/Components/NotificationCenter/row/RowStatus.d.ts +10 -0
- package/dist/Components/NotificationCenter/row/RowStatus.js +42 -0
- package/dist/Components/NotificationCenter/row/category.d.ts +8 -0
- package/dist/Components/NotificationCenter/row/category.js +13 -0
- package/dist/Components/NotificationCenter/row/rowMenu.d.ts +14 -0
- package/dist/Components/NotificationCenter/row/rowMenu.js +37 -0
- package/dist/Components/NotificationCenter/row/rowParts.d.ts +40 -0
- package/dist/Components/NotificationCenter/row/rowParts.js +72 -0
- package/dist/Components/NotificationCenter/row/rowStyles.d.ts +11 -0
- package/dist/Components/NotificationCenter/row/rowStyles.js +25 -0
- package/dist/Components/SegmentedControl/segmentedInk.d.ts +4 -0
- package/dist/Components/SegmentedControl/segmentedInk.js +16 -2
- package/dist/Components/SideBar/SecondaryBar.js +2 -2
- package/dist/Components/SideBar/SideBar.js +18 -8
- package/dist/Components/SideBar/SideBarProps.d.ts +4 -2
- package/dist/Components/SideBar/components/MenuItemBox.d.ts +0 -2
- package/dist/Components/SideBar/components/MenuItemBox.js +2 -3
- package/dist/Components/SideBar/components/MenuItems.js +2 -2
- package/dist/Components/SideBar/components/PinnedSection.js +3 -4
- package/dist/Components/SideBar/sidebarScale.d.ts +0 -2
- package/dist/Components/SideBar/sidebarScale.js +0 -2
- package/dist/Components/Switch/Switch.d.ts +1 -1
- package/dist/Components/Switch/Switch.js +2 -2
- package/dist/Components/Switch/SwitchProps.d.ts +3 -0
- package/dist/Components/Toggle/TableToggle.js +4 -5
- package/dist/Components/Toggle/tableToggleInk.d.ts +7 -0
- package/dist/Components/Toggle/tableToggleInk.js +28 -0
- package/dist/esm/Components/NotificationBell/NotificationItem.js +1 -1
- package/dist/esm/Components/NotificationBell/NotificationList.js +1 -1
- package/dist/esm/Components/NotificationBell/NotificationPanel.js +1 -1
- package/dist/esm/Components/NotificationCenter/center/CenterPanel.js +81 -0
- package/dist/esm/Components/NotificationCenter/center/NotificationCenter.js +92 -0
- package/dist/esm/Components/NotificationCenter/center/NotificationCenterProps.js +1 -0
- package/dist/esm/Components/NotificationCenter/center/PanelRows.js +28 -0
- package/dist/esm/Components/NotificationCenter/center/PanelStates.js +11 -0
- package/dist/esm/Components/NotificationCenter/center/centerLabels.js +34 -0
- package/dist/esm/Components/NotificationCenter/center/useArrivals.js +67 -0
- package/dist/esm/Components/NotificationCenter/center/useIsSheet.js +16 -0
- package/dist/esm/Components/NotificationCenter/core/NotificationCenterProvider.js +35 -0
- package/dist/esm/Components/NotificationCenter/core/NotificationEngineProps.js +1 -0
- package/dist/esm/Components/NotificationCenter/core/NotificationSourceProps.js +1 -0
- package/dist/esm/Components/NotificationCenter/core/actionError.js +39 -0
- package/dist/esm/Components/NotificationCenter/core/format.js +87 -0
- package/dist/esm/Components/NotificationCenter/core/grouping.js +88 -0
- package/dist/esm/Components/NotificationCenter/core/labels.js +88 -0
- package/dist/esm/Components/NotificationCenter/core/overlay.js +274 -0
- package/dist/esm/Components/NotificationCenter/core/useNotificationEngine.js +408 -0
- package/dist/esm/Components/NotificationCenter/inbox/InboxList.js +18 -0
- package/dist/esm/Components/NotificationCenter/inbox/InboxStates.js +18 -0
- package/dist/esm/Components/NotificationCenter/inbox/NotificationInbox.js +396 -0
- package/dist/esm/Components/NotificationCenter/inbox/NotificationInboxProps.js +1 -0
- package/dist/esm/Components/NotificationCenter/inbox/ShortcutsDialog.js +23 -0
- package/dist/esm/Components/NotificationCenter/inbox/inboxLabels.js +52 -0
- package/dist/esm/Components/NotificationCenter/inbox/inboxModel.js +37 -0
- package/dist/esm/Components/NotificationCenter/inbox/useControllable.js +11 -0
- package/dist/esm/Components/NotificationCenter/index.js +8 -0
- package/dist/esm/Components/NotificationCenter/preferences/NotificationPreferences.js +67 -0
- package/dist/esm/Components/NotificationCenter/preferences/NotificationPreferencesProps.js +1 -0
- package/dist/esm/Components/NotificationCenter/preferences/PreferenceCells.js +27 -0
- package/dist/esm/Components/NotificationCenter/preferences/PreferenceGroups.js +21 -0
- package/dist/esm/Components/NotificationCenter/preferences/preferencesLabels.js +24 -0
- package/dist/esm/Components/NotificationCenter/preferences/useAutosave.js +37 -0
- package/dist/esm/Components/NotificationCenter/row/NotificationDetail.js +60 -0
- package/dist/esm/Components/NotificationCenter/row/NotificationDetailProps.js +1 -0
- package/dist/esm/Components/NotificationCenter/row/NotificationRow.js +119 -0
- package/dist/esm/Components/NotificationCenter/row/NotificationRowProps.js +1 -0
- package/dist/esm/Components/NotificationCenter/row/ReasonForm.js +52 -0
- package/dist/esm/Components/NotificationCenter/row/RowActions.js +28 -0
- package/dist/esm/Components/NotificationCenter/row/RowQuickActions.js +49 -0
- package/dist/esm/Components/NotificationCenter/row/RowStatus.js +35 -0
- package/dist/esm/Components/NotificationCenter/row/category.js +9 -0
- package/dist/esm/Components/NotificationCenter/row/rowMenu.js +30 -0
- package/dist/esm/Components/NotificationCenter/row/rowParts.js +59 -0
- package/dist/esm/Components/NotificationCenter/row/rowStyles.js +19 -0
- package/dist/esm/Components/SegmentedControl/segmentedInk.js +15 -2
- package/dist/esm/Components/SideBar/SecondaryBar.js +3 -3
- package/dist/esm/Components/SideBar/SideBar.js +18 -8
- package/dist/esm/Components/SideBar/components/MenuItemBox.js +2 -3
- package/dist/esm/Components/SideBar/components/MenuItems.js +2 -2
- package/dist/esm/Components/SideBar/components/PinnedSection.js +3 -4
- package/dist/esm/Components/SideBar/sidebarScale.js +0 -2
- package/dist/esm/Components/Switch/Switch.js +2 -2
- package/dist/esm/Components/Toggle/TableToggle.js +4 -5
- package/dist/esm/Components/Toggle/tableToggleInk.js +24 -0
- package/dist/esm/index.js +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +16 -3
- package/package.json +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Lock } from "lucide-react";
|
|
3
|
+
import { Box, Flex } from "../../Primitives/Primitives.js";
|
|
4
|
+
import Checkbox from "../../Checkbox/Checkbox.js";
|
|
5
|
+
import ToggleSwitch from "../../ToggleSwitch/ToggleSwitch.js";
|
|
6
|
+
import ToolTip from "../../ToolTip/ToolTip.js";
|
|
7
|
+
import { useCustomTheme } from "../../../Theme/useCustomTheme.js";
|
|
8
|
+
const LockMark = ({ label }) => {
|
|
9
|
+
const { colors } = useCustomTheme();
|
|
10
|
+
return (_jsx(Box, { as: "span", role: "img", "aria-label": label, tabIndex: 0, display: "inline-flex", color: colors.textMuted, borderRadius: "sm", children: _jsx(Lock, { size: 12, "aria-hidden": "true" }) }));
|
|
11
|
+
};
|
|
12
|
+
export const GridCell = ({ row, channel, labels, isOn, isLocked, isFrozen, isPending, onToggle, "data-testid": testId }) => {
|
|
13
|
+
const name = `${row.label}: ${channel.label}`;
|
|
14
|
+
const checkbox = (_jsx(Checkbox, { "aria-label": isLocked ? `${name}, ${labels.alwaysOn}` : name, isChecked: isOn, isDisabled: isLocked || isFrozen, onChange: (event) => onToggle(event.target.checked) }));
|
|
15
|
+
if (!isLocked) {
|
|
16
|
+
return (_jsx(Flex, { "data-testid": testId, justify: "center", opacity: isPending ? 0.6 : 1, children: checkbox }));
|
|
17
|
+
}
|
|
18
|
+
return (_jsx(ToolTip, { label: labels.lockedTip, placement: "top", children: _jsxs(Flex, { "data-testid": testId, justify: "center", position: "relative", w: "fit-content", mx: "auto", children: [checkbox, _jsx(Box, { position: "absolute", left: "calc(100% + 0.375rem)", top: "50%", transform: "translateY(-50%)", children: _jsx(LockMark, { label: labels.lockedTip }) })] }) }));
|
|
19
|
+
};
|
|
20
|
+
const touchTarget = {
|
|
21
|
+
"& label": { display: "inline-flex", alignItems: "center", minHeight: "2.75rem" },
|
|
22
|
+
"& input[role='switch']": { top: "-0.6875rem", height: "calc(100% + 1.375rem)", left: "-0.25rem", width: "calc(100% + 0.5rem)" },
|
|
23
|
+
};
|
|
24
|
+
export const SwitchCell = ({ row, channel, labels, isOn, isLocked, isFrozen, isPending, onToggle, "data-testid": testId }) => {
|
|
25
|
+
const name = `${row.label}: ${channel.label}`;
|
|
26
|
+
return (_jsxs(Flex, { "data-testid": testId, align: "center", gap: 1.5, minH: "2.75rem", opacity: isPending ? 0.6 : 1, sx: touchTarget, children: [_jsx(ToggleSwitch, { size: "md", label: isLocked ? `${channel.label} (${labels.alwaysOn})` : channel.label, "aria-label": isLocked ? `${name}, ${labels.alwaysOn}` : name, isChecked: isOn, isDisabled: isLocked || isFrozen, onChange: onToggle }), isLocked && _jsx(LockMark, { label: labels.lockedTip })] }));
|
|
27
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Flex, Text } from "../../Primitives/Primitives.js";
|
|
3
|
+
import SettingRow from "../../SettingRow/SettingRow.js";
|
|
4
|
+
import SettingsSection from "../../SettingRow/SettingsSection.js";
|
|
5
|
+
import VisuallyHidden from "../../VisuallyHidden/VisuallyHidden.js";
|
|
6
|
+
import { useCustomTheme } from "../../../Theme/useCustomTheme.js";
|
|
7
|
+
import { cardRule } from "../../../Theme/tokens/builders/planeStep.js";
|
|
8
|
+
import { GridCell, SwitchCell } from "./PreferenceCells.js";
|
|
9
|
+
const isLockedCell = (row, channelId) => Boolean(row.locked && row.locked.includes(channelId));
|
|
10
|
+
const cellId = (testId, rowId, channelId) => (testId ? `${testId}-${rowId}-${channelId}` : undefined);
|
|
11
|
+
const GroupTable = ({ group, channels, labels, isFrozen, valueOf, isPending, onToggle, testId }) => {
|
|
12
|
+
const { colors } = useCustomTheme();
|
|
13
|
+
const rule = cardRule(colors);
|
|
14
|
+
return (_jsxs(Box, { as: "table", w: "100%", sx: { borderCollapse: "collapse" }, children: [_jsx(Box, { as: "thead", children: _jsxs(Box, { as: "tr", children: [_jsx(Box, { as: "th", scope: "col", textAlign: "left", pb: 2, children: _jsx(VisuallyHidden, { children: labels.typeColumn }) }), channels.map((channel) => (_jsx(Box, { as: "th", scope: "col", w: "7rem", pb: 2, textAlign: "center", fontSize: "xs", fontWeight: 500, color: colors.textMuted, children: channel.label }, channel.id)))] }) }), _jsx(Box, { as: "tbody", children: group.rows.map((row) => (_jsxs(Box, { as: "tr", borderTop: "1px solid", borderColor: rule, children: [_jsxs(Box, { as: "th", scope: "row", textAlign: "left", fontWeight: 400, py: 3, pr: 4, children: [_jsx(Text, { fontSize: "sm", fontWeight: 500, color: colors.text[800], children: row.label }), row.hint && (_jsx(Text, { fontSize: "xs", color: colors.textMuted, children: row.hint }))] }), channels.map((channel) => (_jsx(Box, { as: "td", textAlign: "center", verticalAlign: "middle", py: 3, children: _jsx(GridCell, { row: row, channel: channel, labels: labels, isOn: isLockedCell(row, channel.id) || valueOf(row.id, channel.id), isLocked: isLockedCell(row, channel.id), isFrozen: isFrozen, isPending: isPending(row.id, channel.id), onToggle: (next) => onToggle(row, channel, next), "data-testid": cellId(testId, row.id, channel.id) }) }, channel.id)))] }, row.id))) })] }));
|
|
15
|
+
};
|
|
16
|
+
const GroupRows = ({ group, channels, labels, isFrozen, valueOf, isPending, onToggle, testId }) => (_jsx(_Fragment, { children: group.rows.map((row) => {
|
|
17
|
+
var _a;
|
|
18
|
+
return (_jsx(SettingRow, { title: row.label, description: (_a = row.hint) !== null && _a !== void 0 ? _a : null, control: _jsx(Flex, { role: "group", "aria-label": row.label, wrap: "wrap", columnGap: 5, rowGap: 0, children: channels.map((channel) => (_jsx(SwitchCell, { row: row, channel: channel, labels: labels, isOn: isLockedCell(row, channel.id) || valueOf(row.id, channel.id), isLocked: isLockedCell(row, channel.id), isFrozen: isFrozen, isPending: isPending(row.id, channel.id), onToggle: (next) => onToggle(row, channel, next), "data-testid": cellId(testId, row.id, channel.id) }, channel.id))) }) }, row.id));
|
|
19
|
+
}) }));
|
|
20
|
+
const PreferenceGroups = (props) => (_jsx(_Fragment, { children: props.groups.map((group) => (_jsx(SettingsSection, { title: group.label, children: props.variant === "phone" ? _jsx(GroupRows, { ...props, group: group }) : _jsx(GroupTable, { ...props, group: group }) }, group.id))) }));
|
|
21
|
+
export default PreferenceGroups;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { mergeDefined } from "../core/labels.js";
|
|
2
|
+
const DEFAULT_PREFERENCES_LABELS = {
|
|
3
|
+
title: "Notification settings",
|
|
4
|
+
subtitle: (appName) => (appName ? `Choose where ${appName} tells you about things.` : "Choose where you hear about things."),
|
|
5
|
+
email: "Email",
|
|
6
|
+
emailTiming: "Email timing",
|
|
7
|
+
emailHint: (appName) => appName ? `A summary skips anything you've already read in ${appName}.` : "A summary skips anything you've already read.",
|
|
8
|
+
typeColumn: "Notification type",
|
|
9
|
+
lockedTip: "Always on for this type",
|
|
10
|
+
asItHappens: "As it happens",
|
|
11
|
+
daily: "Daily at 9:00 am",
|
|
12
|
+
alwaysOn: "always on",
|
|
13
|
+
saving: "Saving…",
|
|
14
|
+
saved: "Saved",
|
|
15
|
+
autosave: "Changes save automatically",
|
|
16
|
+
saveFailed: (what) => `Couldn't save that change. ${what} was put back.`,
|
|
17
|
+
readOnlyBanner: "Your admin manages notification settings for your role. You can see them, but not change them.",
|
|
18
|
+
offlineBanner: "You're offline. Settings can't be changed until you reconnect.",
|
|
19
|
+
loading: "Loading settings",
|
|
20
|
+
errorTitle: "Couldn't load your settings",
|
|
21
|
+
errorDescription: "Nothing was changed. Try again in a moment.",
|
|
22
|
+
retry: "Try again",
|
|
23
|
+
};
|
|
24
|
+
export const resolvePreferencesLabels = (overrides) => mergeDefined(DEFAULT_PREFERENCES_LABELS, overrides);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
export const useAutosave = (saved, onFailed) => {
|
|
3
|
+
const [draft, setDraft] = useState({});
|
|
4
|
+
const [pendingKeys, setPendingKeys] = useState([]);
|
|
5
|
+
const [hasSaved, setHasSaved] = useState(false);
|
|
6
|
+
const inFlight = useRef(new Set());
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
setDraft((current) => {
|
|
9
|
+
const kept = Object.keys(current).filter((key) => inFlight.current.has(key));
|
|
10
|
+
return kept.length === Object.keys(current).length ? current : Object.fromEntries(kept.map((key) => [key, current[key]]));
|
|
11
|
+
});
|
|
12
|
+
}, [saved]);
|
|
13
|
+
const commit = useCallback((key, next, save, what) => {
|
|
14
|
+
if (inFlight.current.has(key))
|
|
15
|
+
return;
|
|
16
|
+
inFlight.current.add(key);
|
|
17
|
+
setDraft((current) => ({ ...current, [key]: next }));
|
|
18
|
+
setPendingKeys(Array.from(inFlight.current));
|
|
19
|
+
const settle = () => {
|
|
20
|
+
inFlight.current.delete(key);
|
|
21
|
+
setPendingKeys(Array.from(inFlight.current));
|
|
22
|
+
};
|
|
23
|
+
save().then(() => {
|
|
24
|
+
setHasSaved(true);
|
|
25
|
+
settle();
|
|
26
|
+
}, () => {
|
|
27
|
+
setDraft((current) => {
|
|
28
|
+
const rest = { ...current };
|
|
29
|
+
delete rest[key];
|
|
30
|
+
return rest;
|
|
31
|
+
});
|
|
32
|
+
settle();
|
|
33
|
+
onFailed(what);
|
|
34
|
+
});
|
|
35
|
+
}, [onFailed]);
|
|
36
|
+
return { draft, pendingKeys, hasSaved, commit };
|
|
37
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useId, useState } from "react";
|
|
3
|
+
import { chakra } from "@chakra-ui/react";
|
|
4
|
+
import { Archive, ArchiveRestore, ArrowLeft, ExternalLink, Hourglass, Lock, Mail, MailOpen } from "lucide-react";
|
|
5
|
+
import { Box, Flex, Heading, Icon, Text } from "../../Primitives/Primitives.js";
|
|
6
|
+
import Avatar from "../../Avatar/Avatar.js";
|
|
7
|
+
import Button from "../../Button/Button.js";
|
|
8
|
+
import DescriptionList from "../../DescriptionList/DescriptionList.js";
|
|
9
|
+
import EmptyState from "../../EmptyState/EmptyState.js";
|
|
10
|
+
import Tag from "../../Tag/Tag.js";
|
|
11
|
+
import { useCustomTheme } from "../../../Theme/useCustomTheme.js";
|
|
12
|
+
import { focusVisibleRing } from "../../../Theme/chakra/focusRing.styles.js";
|
|
13
|
+
import { ruleOn } from "../../../Theme/tokens/builders/planeStep.js";
|
|
14
|
+
import { BORDER_WIDTHS } from "../../../Theme/tokens/primitives/borderWidths.js";
|
|
15
|
+
import { RADIUS_ROLE } from "../../../Theme/tokens/primitives/radii.js";
|
|
16
|
+
import { useNotificationShared } from "../core/NotificationCenterProvider.js";
|
|
17
|
+
import { categoryFor } from "./category.js";
|
|
18
|
+
import { ReasonForm } from "./ReasonForm.js";
|
|
19
|
+
import { OfflineNote, RowActions } from "./RowActions.js";
|
|
20
|
+
import { RowStatus } from "./RowStatus.js";
|
|
21
|
+
import { ChangeChips, TitleText, useToneTile } from "./rowParts.js";
|
|
22
|
+
import { isDecision, visibleActionsFor } from "./rowMenu.js";
|
|
23
|
+
import { WAITING_AFTER_DAYS, iconSizeFor, testIdFor, touchTargetFor } from "./rowStyles.js";
|
|
24
|
+
/** One notification in full for a reading pane or a phone screen; text only, never markup. */
|
|
25
|
+
const NotificationDetail = ({ item, variant = "desktop", onBack, titleRef }) => {
|
|
26
|
+
var _a, _b, _c;
|
|
27
|
+
const { colors } = useCustomTheme();
|
|
28
|
+
const shared = useNotificationShared();
|
|
29
|
+
const { labels, format, now, categories, features, isOffline, appName, actions } = shared;
|
|
30
|
+
const { entry, state } = item;
|
|
31
|
+
const titleId = useId();
|
|
32
|
+
const [asking, setAsking] = useState(null);
|
|
33
|
+
const isPhone = variant === "phone";
|
|
34
|
+
const size = isPhone ? "md" : "sm";
|
|
35
|
+
const iconPx = iconSizeFor(size);
|
|
36
|
+
const category = categoryFor(categories, entry.category, labels.otherCategory);
|
|
37
|
+
const tile = useToneTile(category.tone);
|
|
38
|
+
const isUnread = entry.isRead === false;
|
|
39
|
+
const actionList = visibleActionsFor(entry, features);
|
|
40
|
+
const decides = actionList.some(isDecision);
|
|
41
|
+
const days = format.daysSince(entry.createdAt, now);
|
|
42
|
+
const isWaiting = state.zone === "needsYou" && decides && days >= WAITING_AFTER_DAYS;
|
|
43
|
+
const request = state.reasonRequest;
|
|
44
|
+
const askingAction = asking && asking.id === entry.id ? asking.action : undefined;
|
|
45
|
+
const requested = request ? actionList.find((action) => action.id === request.actionId) : undefined;
|
|
46
|
+
const formAction = state.status === "open" ? (askingAction !== null && askingAction !== void 0 ? askingAction : requested) : undefined;
|
|
47
|
+
const isQuiet = state.status === "open";
|
|
48
|
+
const plane = colors.surfaceRaised;
|
|
49
|
+
const closeForm = () => {
|
|
50
|
+
setAsking(null);
|
|
51
|
+
if (request)
|
|
52
|
+
actions.clearReasonRequest(entry.id);
|
|
53
|
+
};
|
|
54
|
+
const archiveButton = features.archive && state.zone === "activity" ? (_jsx(Button, { size: size, variant: entry.blocked ? "outline" : "ghost", colorScheme: "gray", leftIcon: _jsx(Archive, { size: iconPx, "aria-hidden": "true" }), label: labels.archive, isDisabled: isOffline, sx: touchTargetFor(size), "data-testid": testIdFor(entry, "archive"), onClick: () => actions.archive([entry.id]) })) : null;
|
|
55
|
+
return (_jsxs(chakra.article, { "aria-labelledby": titleId, display: "flex", flexDirection: "column", gap: 4, px: isPhone ? 4 : 8, pt: isPhone ? 2 : 6, pb: 8, maxW: "47.5rem", "data-testid": testIdFor(entry, "detail"), children: [isPhone && onBack && (_jsx(Box, { mx: -2, children: _jsx(Button, { size: "md", variant: "ghost", colorScheme: "gray", leftIcon: _jsx(ArrowLeft, { size: iconSizeFor("md"), "aria-hidden": "true" }), label: labels.back, sx: touchTargetFor("md"), onClick: onBack }) })), _jsxs(Box, { as: "header", display: "grid", gap: 1.5, children: [_jsxs(Flex, { wrap: "wrap", align: "center", gap: 1.5, fontSize: "xs", fontWeight: 500, color: colors.textMuted, children: [_jsx(Flex, { boxSize: "1.25rem", align: "center", justify: "center", borderRadius: RADIUS_ROLE.option, bg: tile.bg, color: tile.fg, "aria-hidden": "true", children: _jsx(Icon, { as: category.icon, boxSize: "0.75rem" }) }), _jsx("span", { children: category.label }), entry.eyebrow && (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", children: "\u00B7" }), _jsx(chakra.span, { fontFamily: "mono", children: entry.eyebrow })] })), isUnread && _jsx(Tag, { size: "xs", colorScheme: "primary", label: labels.unread })] }), _jsx(Heading, { as: "h2", id: titleId, ref: titleRef, tabIndex: -1, fontSize: "lg", fontWeight: 500, lineHeight: "1.4", color: colors.text[800], borderRadius: "sm", sx: { textWrap: "balance", "&:focus-visible": focusVisibleRing(colors) }, children: _jsx(TitleText, { parts: entry.title, isUnread: true }) }), _jsxs(Flex, { wrap: "wrap", align: "center", gap: 2, fontSize: "xs-plus", color: colors.textMuted, children: [entry.actor && (_jsxs(_Fragment, { children: [_jsx(Avatar, { name: entry.actor.name, src: entry.actor.avatarUrl, size: "xs" }), _jsx("span", { children: entry.actor.name }), _jsx("span", { "aria-hidden": "true", children: "\u00B7" })] })), _jsx(chakra.time, { dateTime: format.iso(entry.createdAt), children: format.absolute(entry.createdAt, now) }), isWaiting && _jsx(Tag, { size: "xs", tone: "warning", leadingIcon: Hourglass, label: labels.waiting(days) })] })] }), entry.blocked ? (_jsx(Box, { borderRadius: RADIUS_ROLE.control, bg: colors.backgroundColor.subtle, children: _jsx(EmptyState, { icon: Lock, size: "sm", titleAs: "h3", title: labels.blockedTitle, description: entry.blocked, action: archiveButton }) })) : (_jsxs(_Fragment, { children: [entry.context && (_jsx(Text, { mt: -2, fontSize: "xs-plus", color: colors.textMuted, children: entry.context })), ((_a = entry.body) !== null && _a !== void 0 ? _a : entry.preview) && (_jsx(Text, { fontSize: "sm", lineHeight: "1.65", color: colors.text[800], maxW: "65ch", whiteSpace: "pre-line", children: (_b = entry.body) !== null && _b !== void 0 ? _b : entry.preview })), entry.change && _jsx(ChangeChips, { change: entry.change, labels: labels }), entry.fields && entry.fields.length > 0 && (_jsx(Box, { p: 4, borderRadius: RADIUS_ROLE.control, bg: colors.backgroundColor.subtle, children: _jsx(DescriptionList, { items: entry.fields.map(([label, value]) => ({ label, value })), layout: isPhone ? "stacked" : "inline", columns: { base: 1 } }) })), state.status !== "open" ? (_jsx(RowStatus, { item: item, shared: shared, size: size })) : formAction ? (_jsx(Box, { maxW: "32.5rem", children: _jsx(ReasonForm, { entry: entry, action: formAction, labels: labels, size: size, isOffline: isOffline, serverMessage: request ? ((_c = request.message) !== null && _c !== void 0 ? _c : labels.reasonRequiredMessage) : undefined, onCancel: closeForm, onSubmit: (reason) => {
|
|
56
|
+
setAsking(null);
|
|
57
|
+
actions.decide([entry.id], formAction, reason);
|
|
58
|
+
} }) })) : (actionList.length > 0 && (_jsxs(Box, { display: "grid", gap: 2, children: [_jsx(RowActions, { entry: entry, actionList: actionList, shared: shared, size: size, emphasis: "detail", onAsk: (action) => setAsking({ id: entry.id, action }) }), isOffline && decides && _jsx(OfflineNote, { text: labels.offlineMessage })] })))] })), _jsxs(Flex, { wrap: "wrap", gap: 2, pt: 4, borderTop: `${BORDER_WIDTHS.hairline} solid ${ruleOn(colors, plane)}`, children: [!entry.blocked && (_jsx(Button, { size: size, variant: decides ? "outline" : "solid", colorScheme: decides ? "gray" : "primary", rightIcon: _jsx(ExternalLink, { size: iconPx, "aria-hidden": "true" }), label: labels.openIn(appName), sx: touchTargetFor(size), "data-testid": testIdFor(entry, "open"), onClick: (event) => actions.open(entry, event) })), isQuiet && features.markRead && entry.isRead !== undefined && (_jsx(Button, { size: size, variant: "ghost", colorScheme: "gray", leftIcon: isUnread ? _jsx(MailOpen, { size: iconPx, "aria-hidden": "true" }) : _jsx(Mail, { size: iconPx, "aria-hidden": "true" }), label: isUnread ? labels.markRead : labels.markUnread, isDisabled: isOffline, sx: touchTargetFor(size), "data-testid": testIdFor(entry, "read"), onClick: () => actions.setRead([entry.id], isUnread) })), isQuiet && !entry.blocked && archiveButton, isQuiet && features.restore && state.zone === "archived" && (_jsx(Button, { size: size, variant: "ghost", colorScheme: "gray", leftIcon: _jsx(ArchiveRestore, { size: iconPx, "aria-hidden": "true" }), label: labels.restore, isDisabled: isOffline, sx: touchTargetFor(size), "data-testid": testIdFor(entry, "restore"), onClick: () => actions.restore([entry.id]) }))] })] }));
|
|
59
|
+
};
|
|
60
|
+
export default NotificationDetail;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { memo, useMemo, useState } from "react";
|
|
3
|
+
import { chakra } from "@chakra-ui/react";
|
|
4
|
+
import { ChevronDown, ChevronRight } from "lucide-react";
|
|
5
|
+
import { Box, Flex } from "../../Primitives/Primitives.js";
|
|
6
|
+
import Button from "../../Button/Button.js";
|
|
7
|
+
import Checkbox from "../../Checkbox/Checkbox.js";
|
|
8
|
+
import VisuallyHidden from "../../VisuallyHidden/VisuallyHidden.js";
|
|
9
|
+
import { useCustomTheme } from "../../../Theme/useCustomTheme.js";
|
|
10
|
+
import { listRowBackgrounds } from "../../../Theme/tokens/builders/rowStates.js";
|
|
11
|
+
import { insetFocusRingShadow } from "../../../Theme/chakra/focusRing.styles.js";
|
|
12
|
+
import { reducedMotionSx } from "../../../Theme/chakra/motion.js";
|
|
13
|
+
import { MOTION_MS, transitionFor } from "../../../Theme/tokens/primitives/motion.js";
|
|
14
|
+
import { RADIUS_ROLE } from "../../../Theme/tokens/primitives/radii.js";
|
|
15
|
+
import { useNotificationShared } from "../core/NotificationCenterProvider.js";
|
|
16
|
+
import { plainTitle } from "../core/grouping.js";
|
|
17
|
+
import { categoryFor } from "./category.js";
|
|
18
|
+
import { ReasonForm } from "./ReasonForm.js";
|
|
19
|
+
import { RowActions } from "./RowActions.js";
|
|
20
|
+
import { RowMoreMenu, RowQuickActions, QUICK_SELECTOR } from "./RowQuickActions.js";
|
|
21
|
+
import { RowStatus } from "./RowStatus.js";
|
|
22
|
+
import { ChangeChips, ROW_PLANE_VAR, RowContext, RowLeading, RowPreview, RowTime, TitleText } from "./rowParts.js";
|
|
23
|
+
import { quickActionsFor, rowScope, visibleActionsFor } from "./rowMenu.js";
|
|
24
|
+
import { CHECKBOX_TOUCH_TARGET_SX, ROW_BUTTON_SIZE, WAITING_AFTER_DAYS, iconSizeFor, testIdFor } from "./rowStyles.js";
|
|
25
|
+
const COLLAPSE = transitionFor(["grid-template-rows", "opacity"], "base");
|
|
26
|
+
const HIDE = `${COLLAPSE}, visibility ${MOTION_MS.base}ms step-end`;
|
|
27
|
+
const SHOW = `${COLLAPSE}, visibility ${MOTION_MS.base}ms step-start`;
|
|
28
|
+
const isPlainClick = (event) => event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;
|
|
29
|
+
/** One notification as a list row: lead avatar or tile, title, context, decisions with undo, and hover actions. */
|
|
30
|
+
const NotificationRow = ({ item, variant = "panel", isSelected = false, isChecked = false, onCheckedChange, isThreadChild = false, thread, isThreadOpen = false, onThreadToggle, initialReasonActionId, onOpen, linkRef, }) => {
|
|
31
|
+
var _a;
|
|
32
|
+
const { colors } = useCustomTheme();
|
|
33
|
+
const shared = useNotificationShared();
|
|
34
|
+
const { labels, format, now, categories, features, isOffline, actions } = shared;
|
|
35
|
+
const { entry, state } = item;
|
|
36
|
+
const [askingId, setAskingId] = useState(initialReasonActionId);
|
|
37
|
+
const rows = useMemo(() => listRowBackgrounds(colors), [colors]);
|
|
38
|
+
const size = ROW_BUTTON_SIZE[variant];
|
|
39
|
+
const isPhone = variant === "phone";
|
|
40
|
+
const isUnread = entry.isRead === false;
|
|
41
|
+
const isHidden = state.isHidden;
|
|
42
|
+
const category = categoryFor(categories, entry.category, labels.otherCategory);
|
|
43
|
+
const title = plainTitle(entry.title);
|
|
44
|
+
const actionList = visibleActionsFor(entry, features);
|
|
45
|
+
const days = format.daysSince(entry.createdAt, now);
|
|
46
|
+
const hasDecision = actionList.some((action) => action.href === undefined);
|
|
47
|
+
const waitingDays = state.zone === "needsYou" && hasDecision && days >= WAITING_AFTER_DAYS ? days : null;
|
|
48
|
+
const request = state.reasonRequest;
|
|
49
|
+
const formActionId = askingId !== undefined ? askingId : request ? request.actionId : undefined;
|
|
50
|
+
const formAction = state.status === "open" && formActionId !== undefined
|
|
51
|
+
? actionList.find((action) => action.id === formActionId)
|
|
52
|
+
: undefined;
|
|
53
|
+
const isQuiet = state.status === "open" && formAction === undefined;
|
|
54
|
+
const quick = isQuiet ? quickActionsFor(item, rowScope(item, thread, isThreadOpen), shared) : [];
|
|
55
|
+
const rest = thread ? thread.rest.length : 0;
|
|
56
|
+
const resting = isSelected || isChecked ? rows.selected : colors.surfaceRaised;
|
|
57
|
+
const hover = isSelected || isChecked ? rows.selectedActive : rows.active;
|
|
58
|
+
const open = (event) => {
|
|
59
|
+
if (onOpen)
|
|
60
|
+
onOpen(item, event);
|
|
61
|
+
else
|
|
62
|
+
actions.open(entry, event);
|
|
63
|
+
};
|
|
64
|
+
const onLinkClick = (event) => {
|
|
65
|
+
if (entry.href !== undefined) {
|
|
66
|
+
if (!isPlainClick(event))
|
|
67
|
+
return;
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
}
|
|
70
|
+
open(event);
|
|
71
|
+
};
|
|
72
|
+
const closeForm = () => {
|
|
73
|
+
setAskingId(undefined);
|
|
74
|
+
if (request)
|
|
75
|
+
actions.clearReasonRequest(entry.id);
|
|
76
|
+
};
|
|
77
|
+
const sendForm = (action, reason) => {
|
|
78
|
+
setAskingId(undefined);
|
|
79
|
+
actions.decide([entry.id], action, reason);
|
|
80
|
+
};
|
|
81
|
+
const linkSx = {
|
|
82
|
+
flex: 1,
|
|
83
|
+
minW: 0,
|
|
84
|
+
textAlign: "left",
|
|
85
|
+
fontSize: "sm",
|
|
86
|
+
lineHeight: "1.43",
|
|
87
|
+
color: "inherit",
|
|
88
|
+
textDecoration: "none",
|
|
89
|
+
"&::after": {
|
|
90
|
+
content: '""',
|
|
91
|
+
position: "absolute",
|
|
92
|
+
inset: 0,
|
|
93
|
+
zIndex: 1,
|
|
94
|
+
borderRadius: RADIUS_ROLE.option,
|
|
95
|
+
},
|
|
96
|
+
"&:focus-visible": { outline: "none" },
|
|
97
|
+
"&:focus-visible::after": {
|
|
98
|
+
boxShadow: insetFocusRingShadow(colors),
|
|
99
|
+
outline: "2px solid transparent",
|
|
100
|
+
outlineOffset: "-2px",
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
const linkBody = (_jsxs(_Fragment, { children: [isUnread && _jsx(VisuallyHidden, { children: `${labels.unread}. ` }), entry.eyebrow && (_jsxs(chakra.span, { fontFamily: "mono", fontSize: "xs", fontWeight: 500, color: colors.textMuted, children: [entry.eyebrow, " "] })), _jsx(TitleText, { parts: entry.title, isUnread: isUnread })] }));
|
|
104
|
+
return (_jsx(chakra.li, { listStyleType: "none", display: "grid", gridTemplateRows: isHidden ? "0fr" : "1fr", opacity: isHidden ? 0 : 1, visibility: isHidden ? "hidden" : "visible", "aria-hidden": isHidden ? true : undefined, "data-testid": entry.testId, "data-notification-id": entry.id, "data-status": state.status, sx: {
|
|
105
|
+
transition: isHidden ? HIDE : SHOW,
|
|
106
|
+
contentVisibility: "auto",
|
|
107
|
+
containIntrinsicSize: "auto 4.5rem",
|
|
108
|
+
...reducedMotionSx(),
|
|
109
|
+
}, children: _jsx(Box, { minH: 0, overflow: "hidden", children: _jsxs(Flex, { position: "relative", align: "flex-start", gap: variant === "inbox" ? 2.5 : 3, pt: isPhone ? 3 : 2.5, pb: isPhone ? 3 : 2.5, pr: isPhone ? 2 : 3, pl: isThreadChild ? 16 : 5, borderRadius: RADIUS_ROLE.option, bg: resting, sx: {
|
|
110
|
+
[ROW_PLANE_VAR]: resting,
|
|
111
|
+
transition: transitionFor(["background-color"]),
|
|
112
|
+
"&:hover": { bg: hover, [ROW_PLANE_VAR]: hover },
|
|
113
|
+
[`&:hover ${QUICK_SELECTOR}, &:focus-within ${QUICK_SELECTOR}`]: { opacity: 1 },
|
|
114
|
+
...reducedMotionSx(),
|
|
115
|
+
}, children: [isSelected && (_jsx(Box, { position: "absolute", left: 0, top: 2, bottom: 2, w: "0.1875rem", borderRightRadius: "sm", bg: colors.primary[500], "aria-hidden": "true" })), isUnread && (_jsx(Box, { position: "absolute", left: isThreadChild ? "3.125rem" : "0.4375rem", top: isThreadChild ? "0.875rem" : isPhone ? "1.125rem" : "1rem", boxSize: "0.5rem", borderRadius: "full", bg: colors.accentText, "aria-hidden": "true", "data-testid": testIdFor(entry, "dot") })), onCheckedChange && (_jsx(Box, { position: "relative", zIndex: 2, mt: 1, sx: CHECKBOX_TOUCH_TARGET_SX, children: _jsx(Checkbox, { isChecked: isChecked, "aria-label": labels.select(title), onChange: (event) => onCheckedChange(item, event.target.checked) }) })), _jsx(RowLeading, { entry: entry, category: category, size: isThreadChild ? "xs" : isPhone ? "md" : "sm" }), _jsxs(Box, { flex: 1, minW: 0, children: [_jsxs(Flex, { align: "baseline", gap: 2, children: [entry.href !== undefined ? (_jsx(chakra.a, { ref: linkRef, href: entry.href, "aria-current": isSelected ? "true" : undefined, "data-testid": testIdFor(entry, "link"), onClick: onLinkClick, sx: linkSx, children: linkBody })) : (_jsx(chakra.button, { ref: linkRef, type: "button", "aria-current": isSelected ? "true" : undefined, "data-testid": testIdFor(entry, "link"), onClick: onLinkClick, sx: linkSx, children: linkBody })), _jsx(RowTime, { at: entry.createdAt, now: now, format: format })] }), _jsx(RowContext, { entry: entry, waitingDays: waitingDays, labels: labels }), entry.preview && _jsx(RowPreview, { text: entry.preview, isLarge: isPhone }), entry.change && (_jsx(Box, { mt: 1.5, children: _jsx(ChangeChips, { change: entry.change, labels: labels }) })), formAction ? (_jsx(ReasonForm, { entry: entry, action: formAction, labels: labels, size: size, isOffline: isOffline, serverMessage: request ? ((_a = request.message) !== null && _a !== void 0 ? _a : labels.reasonRequiredMessage) : undefined, onCancel: closeForm, onSubmit: (reason) => sendForm(formAction, reason) })) : (state.status === "open" &&
|
|
116
|
+
actionList.length > 0 && (_jsx(RowActions, { entry: entry, actionList: actionList, shared: shared, size: size, emphasis: "row", onAsk: (action) => setAskingId(action.id) }))), _jsx(RowStatus, { item: item, shared: shared, size: size }), thread && rest > 0 && onThreadToggle && (_jsx(Box, { position: "relative", zIndex: 2, mt: 2, children: _jsx(Button, { size: "xs", variant: "link", leftIcon: isThreadOpen ? (_jsx(ChevronDown, { size: iconSizeFor("xs"), "aria-hidden": "true" })) : (_jsx(ChevronRight, { size: iconSizeFor("xs"), "aria-hidden": "true" })), label: isThreadOpen ? labels.threadHide(rest) : labels.threadShow(rest), "aria-expanded": isThreadOpen, "data-testid": testIdFor(entry, "thread"), onClick: () => onThreadToggle(thread) }) }))] }), quick.length > 0 &&
|
|
117
|
+
(isPhone ? (_jsx(RowMoreMenu, { entry: entry, items: quick, isOffline: isOffline, label: labels.moreActions(title) })) : (_jsx(RowQuickActions, { entry: entry, items: quick, isOffline: isOffline })))] }) }) }));
|
|
118
|
+
};
|
|
119
|
+
export default memo(NotificationRow);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
3
|
+
import { chakra } from "@chakra-ui/react";
|
|
4
|
+
import { CircleAlert } from "lucide-react";
|
|
5
|
+
import { Flex, Icon } from "../../Primitives/Primitives.js";
|
|
6
|
+
import Button from "../../Button/Button.js";
|
|
7
|
+
import InputTextArea from "../../InputTextArea/InputTextArea.js";
|
|
8
|
+
import Kbd from "../../Kbd/Kbd.js";
|
|
9
|
+
import { useCustomTheme } from "../../../Theme/useCustomTheme.js";
|
|
10
|
+
import { touchTargetFor } from "./rowStyles.js";
|
|
11
|
+
export const ReasonForm = ({ entry, action, labels, size, isOffline, serverMessage, onCancel, onSubmit, }) => {
|
|
12
|
+
var _a;
|
|
13
|
+
const { colors } = useCustomTheme();
|
|
14
|
+
const id = useId();
|
|
15
|
+
const hintId = `${id}-hint`;
|
|
16
|
+
const field = useRef(null);
|
|
17
|
+
const [text, setText] = useState("");
|
|
18
|
+
const [touched, setTouched] = useState(false);
|
|
19
|
+
const isServerRequest = serverMessage !== undefined;
|
|
20
|
+
const isRequired = action.reason === "required" || isServerRequest;
|
|
21
|
+
const isNegative = action.intent === "negative";
|
|
22
|
+
const isMissing = isRequired && !text.trim();
|
|
23
|
+
const missingMessage = isNegative ? labels.reasonMissingNegative : labels.reasonMissing;
|
|
24
|
+
const errorText = touched && isMissing ? missingMessage : isServerRequest && !text.trim() ? serverMessage : undefined;
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (field.current)
|
|
27
|
+
field.current.focus();
|
|
28
|
+
}, []);
|
|
29
|
+
const submit = () => {
|
|
30
|
+
setTouched(true);
|
|
31
|
+
if (isMissing) {
|
|
32
|
+
if (field.current)
|
|
33
|
+
field.current.focus();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
onSubmit(text.trim());
|
|
37
|
+
};
|
|
38
|
+
const onKeyDown = (event) => {
|
|
39
|
+
if (event.key === "Escape") {
|
|
40
|
+
event.stopPropagation();
|
|
41
|
+
onCancel();
|
|
42
|
+
}
|
|
43
|
+
else if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
submit();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
return (_jsxs(chakra.form, { noValidate: true, position: "relative", zIndex: 2, display: "grid", gap: 1.5, mt: 2.5, onSubmit: (event) => {
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
submit();
|
|
51
|
+
}, children: [_jsxs(chakra.label, { htmlFor: id, fontSize: "xs-plus", fontWeight: 500, color: colors.text[700], children: [(_a = action.reasonLabel) !== null && _a !== void 0 ? _a : labels.reasonLabel, !isRequired && (_jsxs(chakra.span, { fontWeight: 400, color: colors.textMuted, children: [" ", labels.reasonOptional] }))] }), _jsx(InputTextArea, { ref: field, id: id, rows: 2, size: size === "md" ? "md" : "sm", resize: "vertical", value: text, error: errorText !== undefined, "aria-describedby": hintId, placeholder: isNegative ? labels.reasonPlaceholder : undefined, onChange: (event) => setText(event.target.value), onKeyDown: onKeyDown }), _jsx(Flex, { id: hintId, align: "center", wrap: "wrap", gap: 1, fontSize: "xs", fontWeight: errorText !== undefined ? 500 : 400, color: errorText !== undefined ? colors.semanticText.error : colors.textMuted, children: errorText !== undefined ? (_jsxs(_Fragment, { children: [_jsx(Icon, { as: CircleAlert, boxSize: "0.75rem", "aria-hidden": "true" }), errorText] })) : (_jsxs(_Fragment, { children: [_jsx(Kbd, { size: "xs", children: labels.keyCtrl }), _jsx(Kbd, { size: "xs", children: labels.keyEnter }), _jsxs("span", { children: [labels.toSend, " \u00B7"] }), _jsx(Kbd, { size: "xs", children: labels.keyEsc }), _jsx("span", { children: labels.toCancel })] })) }), _jsxs(Flex, { justify: "flex-end", gap: 2, children: [_jsx(Button, { size: size, variant: "ghost", colorScheme: "gray", label: labels.cancel, sx: touchTargetFor(size), onClick: onCancel }), _jsx(Button, { type: "submit", size: size, variant: "solid", colorScheme: isNegative ? "red" : "primary", label: action.label, "aria-label": labels.actionName(action.label, entry.subject), isDisabled: isOffline, sx: touchTargetFor(size), "data-testid": entry.testId ? `${entry.testId}-${action.id}-send` : undefined })] })] }));
|
|
52
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Check, ChevronRight, WifiOff } from "lucide-react";
|
|
3
|
+
import { Flex, Icon } from "../../Primitives/Primitives.js";
|
|
4
|
+
import Button from "../../Button/Button.js";
|
|
5
|
+
import { useCustomTheme } from "../../../Theme/useCustomTheme.js";
|
|
6
|
+
import { isDecision } from "./rowMenu.js";
|
|
7
|
+
import { iconSizeFor, testIdFor, touchTargetFor } from "./rowStyles.js";
|
|
8
|
+
export const OfflineNote = ({ text }) => {
|
|
9
|
+
const { colors } = useCustomTheme();
|
|
10
|
+
return (_jsxs(Flex, { as: "span", display: "inline-flex", align: "center", gap: 1, fontSize: "xs", color: colors.textMuted, children: [_jsx(Icon, { as: WifiOff, boxSize: "0.75rem", "aria-hidden": "true" }), text] }));
|
|
11
|
+
};
|
|
12
|
+
export const RowActions = ({ entry, actionList, shared, size, emphasis, onAsk }) => {
|
|
13
|
+
const { labels, actions, isOffline } = shared;
|
|
14
|
+
const iconPx = iconSizeFor(size);
|
|
15
|
+
return (_jsxs(Flex, { position: "relative", zIndex: 2, wrap: "wrap", align: "center", gap: 2, mt: emphasis === "row" ? 2.5 : 0, children: [actionList.map((action) => {
|
|
16
|
+
const isLink = !isDecision(action);
|
|
17
|
+
const isStrong = emphasis === "detail" && (action.intent === "positive" || action.intent === "primary");
|
|
18
|
+
return (_jsx(Button, { size: size, variant: isStrong ? "solid" : "outline", colorScheme: isStrong ? "primary" : "gray", label: action.label, "aria-label": labels.actionName(action.label, entry.subject), leftIcon: !isLink && action.intent === "positive" ? _jsx(Check, { size: iconPx, "aria-hidden": "true" }) : undefined, rightIcon: isLink ? _jsx(ChevronRight, { size: iconPx, "aria-hidden": "true" }) : undefined, isDisabled: isOffline && !isLink, sx: touchTargetFor(size), "data-testid": testIdFor(entry, action.id), onClick: (event) => {
|
|
19
|
+
event.stopPropagation();
|
|
20
|
+
if (isLink)
|
|
21
|
+
actions.open(entry, event, action);
|
|
22
|
+
else if (action.reason)
|
|
23
|
+
onAsk(action);
|
|
24
|
+
else
|
|
25
|
+
actions.decide([entry.id], action);
|
|
26
|
+
} }, action.id));
|
|
27
|
+
}), isOffline && actionList.some(isDecision) && _jsx(OfflineNote, { text: labels.offlineNote })] }));
|
|
28
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Ellipsis } from "lucide-react";
|
|
3
|
+
import { Flex, Icon } from "../../Primitives/Primitives.js";
|
|
4
|
+
import IconButton from "../../IconButton/IconButton.js";
|
|
5
|
+
import ToolTip from "../../ToolTip/ToolTip.js";
|
|
6
|
+
import Dropdown from "../../Dropdown/DropDown.js";
|
|
7
|
+
import { useCustomTheme } from "../../../Theme/useCustomTheme.js";
|
|
8
|
+
import { BORDER_WIDTHS } from "../../../Theme/tokens/primitives/borderWidths.js";
|
|
9
|
+
import { RADIUS_ROLE } from "../../../Theme/tokens/primitives/radii.js";
|
|
10
|
+
import { transitionFor } from "../../../Theme/tokens/primitives/motion.js";
|
|
11
|
+
import { reducedMotionSx } from "../../../Theme/chakra/motion.js";
|
|
12
|
+
import { TOUCH_TARGET_SX, iconSizeFor, testIdFor } from "./rowStyles.js";
|
|
13
|
+
export const QUICK_SELECTOR = "[data-nc-quick]";
|
|
14
|
+
export const RowQuickActions = ({ entry, items, isOffline }) => {
|
|
15
|
+
const { colors } = useCustomTheme();
|
|
16
|
+
return (_jsx(Flex, { "data-nc-quick": "", position: "absolute", top: "0.375rem", right: "0.375rem", zIndex: 2, gap: "0.125rem", p: "0.125rem", borderRadius: RADIUS_ROLE.control, bg: colors.surfaceRaised, border: `${BORDER_WIDTHS.hairline} solid ${colors.boxborder[200]}`, boxShadow: "sm", opacity: 0, sx: {
|
|
17
|
+
transition: transitionFor(["opacity"]),
|
|
18
|
+
"@media (hover: none)": {
|
|
19
|
+
position: "relative",
|
|
20
|
+
top: "auto",
|
|
21
|
+
right: "auto",
|
|
22
|
+
opacity: 1,
|
|
23
|
+
alignSelf: "flex-start",
|
|
24
|
+
border: "none",
|
|
25
|
+
boxShadow: "none",
|
|
26
|
+
bg: "transparent",
|
|
27
|
+
},
|
|
28
|
+
...reducedMotionSx(),
|
|
29
|
+
}, children: items.map((item) => (_jsx(ToolTip, { label: item.label, children: _jsx(IconButton, { size: "xs", variant: "ghost", colorScheme: "gray", "aria-label": item.label, icon: _jsx(Icon, { as: item.icon, boxSize: `${iconSizeFor("xs") / 16}rem`, "aria-hidden": "true" }), isDisabled: isOffline, "data-testid": testIdFor(entry, item.id), onClick: (event) => {
|
|
30
|
+
event.stopPropagation();
|
|
31
|
+
item.run();
|
|
32
|
+
} }) }, item.id))) }));
|
|
33
|
+
};
|
|
34
|
+
export const RowMoreMenu = ({ entry, items, isOffline, label }) => {
|
|
35
|
+
const trigger = {
|
|
36
|
+
variant: "ghost",
|
|
37
|
+
colorScheme: "gray",
|
|
38
|
+
px: 0,
|
|
39
|
+
minW: "2.5rem",
|
|
40
|
+
isDisabled: isOffline,
|
|
41
|
+
sx: TOUCH_TARGET_SX,
|
|
42
|
+
"data-testid": testIdFor(entry, "more"),
|
|
43
|
+
};
|
|
44
|
+
return (_jsx(Flex, { position: "relative", zIndex: 2, flexShrink: 0, mt: "-0.5rem", mr: "-0.5rem", children: _jsx(Dropdown, { size: "md", options: items.map((item) => ({ id: item.id, label: item.label, image: item.icon })), handleOptionSelect: (id) => {
|
|
45
|
+
const hit = items.find((item) => item.id === id);
|
|
46
|
+
if (hit)
|
|
47
|
+
hit.run();
|
|
48
|
+
}, DropdownIcon: _jsx(Ellipsis, { size: iconSizeFor("md"), "aria-hidden": "true" }), isVisibleIconShow: false, ariaLabel: label, buttonProps: trigger }) }));
|
|
49
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { chakra } from "@chakra-ui/react";
|
|
3
|
+
import { CircleAlert, CircleCheck, CircleX, Info, Lock, RefreshCw } from "lucide-react";
|
|
4
|
+
import { Flex, Icon } from "../../Primitives/Primitives.js";
|
|
5
|
+
import Button from "../../Button/Button.js";
|
|
6
|
+
import Spinner from "../../Spinner/Spinner.js";
|
|
7
|
+
import { useCustomTheme } from "../../../Theme/useCustomTheme.js";
|
|
8
|
+
import { resolveSoftTone, toneScale } from "../../../Theme/tokens/builders/tone.js";
|
|
9
|
+
import { RADIUS_ROLE } from "../../../Theme/tokens/primitives/radii.js";
|
|
10
|
+
import { iconSizeFor, testIdFor, touchTargetFor } from "./rowStyles.js";
|
|
11
|
+
const StatusLine = ({ tone, icon, role, children }) => {
|
|
12
|
+
const { colors } = useCustomTheme();
|
|
13
|
+
const soft = resolveSoftTone(colors, toneScale(colors, tone), colors.surfaceRaised);
|
|
14
|
+
return (_jsxs(Flex, { role: role, position: "relative", zIndex: 2, wrap: "wrap", align: "center", columnGap: 2, rowGap: 1.5, mt: 2.5, px: 2.5, py: 1.5, borderRadius: RADIUS_ROLE.control, bg: soft.bg, color: soft.fg, fontSize: "xs-plus", lineHeight: "1.45", onClick: (event) => event.stopPropagation(), children: [icon, children] }));
|
|
15
|
+
};
|
|
16
|
+
const glyph = (as) => _jsx(Icon, { as: as, boxSize: "0.875rem", flexShrink: 0, "aria-hidden": "true" });
|
|
17
|
+
const LINK_SX = { color: "inherit", fontWeight: 600 };
|
|
18
|
+
export const RowStatus = ({ item, shared, size }) => {
|
|
19
|
+
const { entry, state } = item;
|
|
20
|
+
const { labels, actions, isOffline } = shared;
|
|
21
|
+
const { decision, error } = state;
|
|
22
|
+
if (!decision || state.status === "open")
|
|
23
|
+
return null;
|
|
24
|
+
if (state.status === "failed") {
|
|
25
|
+
return (_jsxs(StatusLine, { tone: "error", icon: glyph(CircleAlert), role: "alert", children: [_jsxs(chakra.span, { fontWeight: 500, children: [_jsx(chakra.strong, { fontWeight: 600, children: labels.couldNot(decision.label) }), " ", error && error.message ? error.message : labels.failedMessage] }), _jsx(Button, { size: size, variant: "outline", colorScheme: "gray", leftIcon: _jsx(RefreshCw, { size: iconSizeFor(size), "aria-hidden": "true" }), label: labels.retry, "aria-label": labels.actionName(labels.retry, entry.subject), isDisabled: isOffline, ml: "auto", sx: touchTargetFor(size), "data-testid": testIdFor(entry, "retry"), onClick: () => actions.retry(entry.id) })] }));
|
|
26
|
+
}
|
|
27
|
+
if (state.status === "denied") {
|
|
28
|
+
const isForbidden = error !== undefined && error.kind === "forbidden";
|
|
29
|
+
const fallback = isForbidden ? labels.forbiddenMessage : labels.conflictMessage;
|
|
30
|
+
return (_jsxs(StatusLine, { tone: "info", icon: glyph(isForbidden ? Lock : Info), children: [_jsx(chakra.span, { fontWeight: 500, children: error && error.message ? error.message : fallback }), _jsx(Button, { size: "xs", variant: "link", label: labels.dismiss, "aria-label": labels.actionName(labels.dismiss, entry.subject), sx: LINK_SX, "data-testid": testIdFor(entry, "dismiss"), onClick: () => actions.dismiss(entry.id) })] }));
|
|
31
|
+
}
|
|
32
|
+
const isNegative = decision.intent === "negative";
|
|
33
|
+
const isSending = state.status === "sending";
|
|
34
|
+
return (_jsxs(StatusLine, { tone: isNegative ? "neutral" : "success", icon: isSending ? _jsx(Spinner, { size: "xs", color: "currentColor", "aria-hidden": "true" }) : glyph(isNegative ? CircleX : CircleCheck), children: [_jsx(chakra.span, { fontWeight: 500, children: isSending ? labels.sending : decision.doneLabel }), state.status === "undo" && (_jsx(Button, { size: "xs", variant: "link", label: labels.undo, "aria-label": labels.actionName(labels.undo, entry.subject), sx: LINK_SX, "data-testid": testIdFor(entry, "undo"), onClick: () => actions.undo(entry.id) })), decision.reason && (_jsx(chakra.span, { flexBasis: "100%", pl: "1.375rem", fontStyle: "italic", opacity: 0.85, children: labels.reasonQuote(decision.reason) }))] }));
|
|
35
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Bell } from "lucide-react";
|
|
2
|
+
import { findCategory } from "../core/grouping.js";
|
|
3
|
+
export const categoryFor = (categories, key, otherLabel) => {
|
|
4
|
+
var _a;
|
|
5
|
+
const category = findCategory(categories, key);
|
|
6
|
+
if (!category)
|
|
7
|
+
return { label: otherLabel, icon: Bell, tone: "neutral" };
|
|
8
|
+
return { label: category.label, icon: category.icon, tone: (_a = category.tone) !== null && _a !== void 0 ? _a : "neutral" };
|
|
9
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Archive, ArchiveRestore, Mail, MailOpen } from "lucide-react";
|
|
2
|
+
import { threadEntries } from "../core/grouping.js";
|
|
3
|
+
export const rowScope = (item, thread, isThreadOpen) => (thread && !isThreadOpen ? threadEntries(thread) : [item]);
|
|
4
|
+
export const quickActionsFor = (item, scope, shared) => {
|
|
5
|
+
const { features, labels, actions } = shared;
|
|
6
|
+
const { entry, state } = item;
|
|
7
|
+
const ids = scope.map((member) => member.entry.id);
|
|
8
|
+
const list = [];
|
|
9
|
+
if (features.markRead && entry.isRead !== undefined) {
|
|
10
|
+
const unread = scope.some((member) => member.entry.isRead === false);
|
|
11
|
+
list.push({
|
|
12
|
+
id: "read",
|
|
13
|
+
label: unread ? labels.markRead : labels.markUnread,
|
|
14
|
+
icon: unread ? MailOpen : Mail,
|
|
15
|
+
run: () => actions.setRead(ids, unread),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
if (features.archive && state.zone === "activity") {
|
|
19
|
+
list.push({ id: "archive", label: labels.archive, icon: Archive, run: () => actions.archive(ids) });
|
|
20
|
+
}
|
|
21
|
+
if (features.restore && state.zone === "archived") {
|
|
22
|
+
list.push({ id: "restore", label: labels.restore, icon: ArchiveRestore, run: () => actions.restore(ids) });
|
|
23
|
+
}
|
|
24
|
+
actions.extraActionsFor(entry).forEach((extra) => {
|
|
25
|
+
list.push({ id: extra.id, label: extra.label, icon: extra.icon, run: () => actions.runExtraAction(entry, extra) });
|
|
26
|
+
});
|
|
27
|
+
return list;
|
|
28
|
+
};
|
|
29
|
+
export const visibleActionsFor = (entry, features) => { var _a; return ((_a = entry.actions) !== null && _a !== void 0 ? _a : []).filter((action) => action.href !== undefined || features.decide); };
|
|
30
|
+
export const isDecision = (action) => action.href === undefined;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment } from "react";
|
|
3
|
+
import { chakra } from "@chakra-ui/react";
|
|
4
|
+
import { ChevronRight, Hourglass, Lock } from "lucide-react";
|
|
5
|
+
import { Box, Flex, Icon, Text } from "../../Primitives/Primitives.js";
|
|
6
|
+
import Avatar from "../../Avatar/Avatar.js";
|
|
7
|
+
import Tag from "../../Tag/Tag.js";
|
|
8
|
+
import VisuallyHidden from "../../VisuallyHidden/VisuallyHidden.js";
|
|
9
|
+
import { useCustomTheme } from "../../../Theme/useCustomTheme.js";
|
|
10
|
+
import { isDarkCanvas } from "../../../Theme/tokens/builders/accentText.js";
|
|
11
|
+
import { resolveStatusTone } from "../../../Theme/tokens/builders/statusTone.js";
|
|
12
|
+
import { toneScale } from "../../../Theme/tokens/builders/tone.js";
|
|
13
|
+
import { ruleOn } from "../../../Theme/tokens/builders/planeStep.js";
|
|
14
|
+
import { BORDER_WIDTHS } from "../../../Theme/tokens/primitives/borderWidths.js";
|
|
15
|
+
import { RADIUS_ROLE } from "../../../Theme/tokens/primitives/radii.js";
|
|
16
|
+
import { NUMERIC_TEXT } from "../../../Theme/tokens/primitives/typography.js";
|
|
17
|
+
export const ROW_PLANE_VAR = "--nc-row-bg";
|
|
18
|
+
export const TitleText = ({ parts, isUnread }) => {
|
|
19
|
+
const { colors } = useCustomTheme();
|
|
20
|
+
return (_jsx(chakra.span, { color: isUnread ? colors.text[800] : colors.text[700], children: parts.map((part, index) => typeof part === "string" ? (_jsx(Fragment, { children: part }, index)) : (_jsx(chakra.strong, { fontWeight: isUnread ? 600 : 500, color: isUnread ? colors.header[900] : colors.text[800], children: part.b }, index))) }));
|
|
21
|
+
};
|
|
22
|
+
const LEAD_BOX = {
|
|
23
|
+
"2xs": "1rem",
|
|
24
|
+
xs: "1.5rem",
|
|
25
|
+
sm: "2rem",
|
|
26
|
+
md: "2.5rem",
|
|
27
|
+
lg: "3rem",
|
|
28
|
+
xl: "4rem",
|
|
29
|
+
};
|
|
30
|
+
export const useToneTile = (tone) => {
|
|
31
|
+
const { colors } = useCustomTheme();
|
|
32
|
+
return resolveStatusTone(toneScale(colors, tone), isDarkCanvas(colors.backgroundColor.main) ? "dark" : "light");
|
|
33
|
+
};
|
|
34
|
+
export const RowLeading = ({ entry, category, size }) => {
|
|
35
|
+
const tile = useToneTile(category.tone);
|
|
36
|
+
const iconPx = size === "md" || size === "lg" || size === "xl" ? 18 : 16;
|
|
37
|
+
if (entry.actor) {
|
|
38
|
+
return (_jsxs(Box, { position: "relative", flexShrink: 0, boxSize: LEAD_BOX[size], "aria-hidden": "true", children: [_jsx(Avatar, { name: entry.actor.name, src: entry.actor.avatarUrl, size: size }), _jsx(Flex, { position: "absolute", right: "-0.375rem", bottom: "-0.375rem", boxSize: "1.25rem", align: "center", justify: "center", borderRadius: "full", bg: tile.bg, color: tile.fg, border: `${BORDER_WIDTHS.thick} solid var(${ROW_PLANE_VAR})`, children: _jsx(Icon, { as: category.icon, boxSize: "0.625rem", strokeWidth: 2.5 }) })] }));
|
|
39
|
+
}
|
|
40
|
+
return (_jsx(Flex, { flexShrink: 0, boxSize: LEAD_BOX[size], align: "center", justify: "center", borderRadius: RADIUS_ROLE.media, bg: tile.bg, color: tile.fg, "aria-hidden": "true", children: _jsx(Icon, { as: category.icon, boxSize: `${iconPx / 16}rem` }) }));
|
|
41
|
+
};
|
|
42
|
+
export const RowTime = ({ at, now, format }) => {
|
|
43
|
+
const { colors } = useCustomTheme();
|
|
44
|
+
return (_jsxs(chakra.time, { dateTime: format.iso(at), title: format.absolute(at, now), flexShrink: 0, fontSize: "xs", color: colors.textMuted, whiteSpace: "nowrap", sx: NUMERIC_TEXT, children: [_jsx("span", { "aria-hidden": "true", children: format.shortAge(at, now) }), _jsx(VisuallyHidden, { children: format.longAge(at, now) })] }));
|
|
45
|
+
};
|
|
46
|
+
export const RowContext = ({ entry, waitingDays, labels }) => {
|
|
47
|
+
const { colors } = useCustomTheme();
|
|
48
|
+
if (waitingDays === null && !entry.blocked && !entry.context)
|
|
49
|
+
return null;
|
|
50
|
+
return (_jsxs(Flex, { wrap: "wrap", align: "center", columnGap: 2, rowGap: 1, mt: 1, fontSize: "xs", lineHeight: "1.5", color: colors.textMuted, children: [waitingDays !== null && _jsx(Tag, { size: "xs", tone: "warning", leadingIcon: Hourglass, label: labels.waiting(waitingDays) }), entry.blocked && _jsx(Tag, { size: "xs", tone: "neutral", leadingIcon: Lock, label: labels.noAccess }), entry.context && _jsx(chakra.span, { minW: 0, children: entry.context })] }));
|
|
51
|
+
};
|
|
52
|
+
export const RowPreview = ({ text, isLarge }) => {
|
|
53
|
+
const { colors } = useCustomTheme();
|
|
54
|
+
return (_jsx(Text, { mt: 1.5, pl: 2.5, borderLeft: `${BORDER_WIDTHS.thick} solid ${ruleOn(colors, colors.surfaceRaised)}`, fontSize: isLarge ? "sm" : "xs-plus", lineHeight: "1.5", color: colors.text[700], noOfLines: 2, children: text }));
|
|
55
|
+
};
|
|
56
|
+
export const ChangeChips = ({ change, labels }) => {
|
|
57
|
+
const { colors } = useCustomTheme();
|
|
58
|
+
return (_jsxs(Flex, { align: "center", wrap: "wrap", gap: 1.5, color: colors.textMuted, children: [_jsx(Tag, { size: "xs", tone: "neutral", label: change.from }), _jsx(Icon, { as: ChevronRight, boxSize: "0.75rem", "aria-hidden": "true" }), _jsx(VisuallyHidden, { children: labels.changedTo }), _jsx(Tag, { size: "xs", colorScheme: "primary", label: change.to })] }));
|
|
59
|
+
};
|