@crisp-ui-kit/crisp 0.57.2 → 0.57.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/assigneepicker.css +13 -0
- package/dist/assigneepicker.d.ts +7 -0
- package/dist/assigneepicker.layered.css +15 -0
- package/dist/button.css +3 -0
- package/dist/button.d.ts +2 -2
- package/dist/button.layered.css +3 -0
- package/dist/chip.css +1 -1
- package/dist/chip.layered.css +1 -1
- package/dist/cjs/components/breadcrumb/breadcrumb.js +1 -1
- package/dist/cjs/components/button/button.recipe.js +33 -0
- package/dist/cjs/components/chip/chip.recipe.js +3 -1
- package/dist/cjs/components/datatable/datatable.js +6 -3
- package/dist/cjs/components/datatable/datatable.recipe.js +45 -6
- package/dist/cjs/components/datepickerfield/datepickerfield.js +2 -2
- package/dist/cjs/components/datepickerfield/datepickerfield.recipe.js +16 -3
- package/dist/cjs/components/menu/menu.js +65 -9
- package/dist/cjs/components/multiselect/multiselect.js +3 -0
- package/dist/cjs/components/multiselect/multiselect.recipe.js +18 -0
- package/dist/cjs/components/select/select.js +2 -2
- package/dist/cjs/components/select/select.recipe.js +19 -0
- package/dist/cjs/components/table/table.recipe.js +7 -1
- package/dist/cjs/core/floating-portal.js +7 -4
- package/dist/cjs/core/index.js +2 -1
- package/dist/cjs/core/place-floating.js +38 -0
- package/dist/cjs/index.js +8 -0
- package/dist/cjs/product/assigneepicker/assigneepicker.js +127 -0
- package/dist/cjs/product/assigneepicker/assigneepicker.recipe.js +36 -0
- package/dist/cjs/product/assigneepicker/index.js +7 -0
- package/dist/cjs/product/inbox/inbox.js +11 -3
- package/dist/cjs/product/notesgallery/index.js +7 -0
- package/dist/cjs/product/notesgallery/notesgallery.js +80 -0
- package/dist/cjs/product/notesgallery/notesgallery.recipe.js +46 -0
- package/dist/cjs/product/objectpagemenu/index.js +17 -0
- package/dist/cjs/product/objectpagemenu/objectpagemenu.js +26 -0
- package/dist/cjs/product/objectpagemenu/objectpagemenu.recipe.js +30 -0
- package/dist/cjs/product/objectsettings/objectsettings.js +107 -14
- package/dist/cjs/product/recordpage/index.js +2 -1
- package/dist/cjs/product/recordpage/recordpage.js +112 -7
- package/dist/cjs/product/recordpage/recordpage.recipe.js +63 -0
- package/dist/cjs/product/recordpicker/index.js +7 -0
- package/dist/cjs/product/recordpicker/recordpicker.js +100 -0
- package/dist/cjs/product/recordpicker/recordpicker.recipe.js +36 -0
- package/dist/cjs/product/savedviews/index.js +2 -1
- package/dist/cjs/product/savedviews/savedviews.js +22 -0
- package/dist/cjs/product/savedviews/savedviews.recipe.js +47 -0
- package/dist/cjs/product/share/index.js +7 -0
- package/dist/cjs/product/share/share.js +179 -0
- package/dist/cjs/product/share/share.recipe.js +114 -0
- package/dist/cjs/product/shortcutssheet/index.js +17 -0
- package/dist/cjs/product/shortcutssheet/shortcutssheet.js +125 -0
- package/dist/cjs/product/shortcutssheet/shortcutssheet.recipe.js +50 -0
- package/dist/cjs/product/taskeditpopover/index.js +7 -0
- package/dist/cjs/product/taskeditpopover/taskeditpopover.js +159 -0
- package/dist/cjs/product/taskeditpopover/taskeditpopover.recipe.js +68 -0
- package/dist/cjs/product/workspaceswitchermenu/index.js +17 -0
- package/dist/cjs/product/workspaceswitchermenu/workspaceswitchermenu.js +38 -0
- package/dist/cjs/product/workspaceswitchermenu/workspaceswitchermenu.recipe.js +34 -0
- package/dist/cjs/tokens/palette.js +27 -0
- package/dist/cjs/tokens/semantic.js +20 -0
- package/dist/datatable.css +50 -11
- package/dist/datatable.layered.css +50 -11
- package/dist/datepickerfield.css +8 -2
- package/dist/datepickerfield.d.ts +2 -2
- package/dist/datepickerfield.layered.css +8 -2
- package/dist/esm/components/breadcrumb/breadcrumb.js +1 -1
- package/dist/esm/components/button/button.recipe.js +33 -0
- package/dist/esm/components/chip/chip.recipe.js +3 -1
- package/dist/esm/components/datatable/datatable.js +6 -3
- package/dist/esm/components/datatable/datatable.recipe.js +45 -6
- package/dist/esm/components/datepickerfield/datepickerfield.js +2 -2
- package/dist/esm/components/datepickerfield/datepickerfield.recipe.js +16 -3
- package/dist/esm/components/menu/menu.js +64 -8
- package/dist/esm/components/multiselect/multiselect.js +3 -0
- package/dist/esm/components/multiselect/multiselect.recipe.js +18 -0
- package/dist/esm/components/select/select.js +2 -2
- package/dist/esm/components/select/select.recipe.js +19 -0
- package/dist/esm/components/table/table.recipe.js +7 -1
- package/dist/esm/core/floating-portal.js +7 -4
- package/dist/esm/core/index.js +1 -1
- package/dist/esm/core/place-floating.js +37 -0
- package/dist/esm/index.js +8 -0
- package/dist/esm/product/assigneepicker/assigneepicker.js +91 -0
- package/dist/esm/product/assigneepicker/assigneepicker.recipe.js +33 -0
- package/dist/esm/product/assigneepicker/index.js +2 -0
- package/dist/esm/product/inbox/inbox.js +11 -3
- package/dist/esm/product/notesgallery/index.js +2 -0
- package/dist/esm/product/notesgallery/notesgallery.js +44 -0
- package/dist/esm/product/notesgallery/notesgallery.recipe.js +43 -0
- package/dist/esm/product/objectpagemenu/index.js +1 -0
- package/dist/esm/product/objectpagemenu/objectpagemenu.js +23 -0
- package/dist/esm/product/objectpagemenu/objectpagemenu.recipe.js +27 -0
- package/dist/esm/product/objectsettings/objectsettings.js +106 -13
- package/dist/esm/product/recordpage/index.js +1 -1
- package/dist/esm/product/recordpage/recordpage.js +112 -8
- package/dist/esm/product/recordpage/recordpage.recipe.js +63 -0
- package/dist/esm/product/recordpicker/index.js +2 -0
- package/dist/esm/product/recordpicker/recordpicker.js +64 -0
- package/dist/esm/product/recordpicker/recordpicker.recipe.js +33 -0
- package/dist/esm/product/savedviews/index.js +1 -1
- package/dist/esm/product/savedviews/savedviews.js +22 -1
- package/dist/esm/product/savedviews/savedviews.recipe.js +47 -0
- package/dist/esm/product/share/index.js +2 -0
- package/dist/esm/product/share/share.js +143 -0
- package/dist/esm/product/share/share.recipe.js +111 -0
- package/dist/esm/product/shortcutssheet/index.js +1 -0
- package/dist/esm/product/shortcutssheet/shortcutssheet.js +88 -0
- package/dist/esm/product/shortcutssheet/shortcutssheet.recipe.js +47 -0
- package/dist/esm/product/taskeditpopover/index.js +2 -0
- package/dist/esm/product/taskeditpopover/taskeditpopover.js +123 -0
- package/dist/esm/product/taskeditpopover/taskeditpopover.recipe.js +65 -0
- package/dist/esm/product/workspaceswitchermenu/index.js +1 -0
- package/dist/esm/product/workspaceswitchermenu/workspaceswitchermenu.js +35 -0
- package/dist/esm/product/workspaceswitchermenu/workspaceswitchermenu.recipe.js +31 -0
- package/dist/esm/tokens/palette.js +27 -0
- package/dist/esm/tokens/semantic.js +20 -0
- package/dist/multiselect.css +18 -0
- package/dist/multiselect.layered.css +18 -0
- package/dist/native/tokens.d.ts +1 -1
- package/dist/native/tokens.js +56 -4
- package/dist/notesgallery.css +15 -0
- package/dist/notesgallery.d.ts +7 -0
- package/dist/notesgallery.layered.css +17 -0
- package/dist/objectpagemenu.css +3 -0
- package/dist/objectpagemenu.d.ts +7 -0
- package/dist/objectpagemenu.layered.css +5 -0
- package/dist/recordpage.css +59 -0
- package/dist/recordpage.layered.css +59 -0
- package/dist/recordpicker.css +13 -0
- package/dist/recordpicker.d.ts +7 -0
- package/dist/recordpicker.layered.css +15 -0
- package/dist/savedviews.css +47 -0
- package/dist/savedviews.layered.css +47 -0
- package/dist/select.css +19 -0
- package/dist/select.layered.css +19 -0
- package/dist/share.css +84 -0
- package/dist/share.d.ts +7 -0
- package/dist/share.layered.css +86 -0
- package/dist/shortcutssheet.css +24 -0
- package/dist/shortcutssheet.d.ts +7 -0
- package/dist/shortcutssheet.layered.css +26 -0
- package/dist/styles.css +438 -16
- package/dist/styles.layered.css +438 -16
- package/dist/table.css +7 -2
- package/dist/table.layered.css +7 -2
- package/dist/taskeditpopover.css +36 -0
- package/dist/taskeditpopover.d.ts +7 -0
- package/dist/taskeditpopover.layered.css +38 -0
- package/dist/tokens.css +26 -0
- package/dist/tokens.json +56 -4
- package/dist/types/components/breadcrumb/breadcrumb.d.ts +9 -1
- package/dist/types/components/button/button.d.ts +2 -0
- package/dist/types/components/button/button.recipe.d.ts +12 -0
- package/dist/types/components/chip/chip.recipe.d.ts +1 -1
- package/dist/types/components/datatable/datatable.d.ts +8 -0
- package/dist/types/components/datepickerfield/datepickerfield.d.ts +4 -1
- package/dist/types/components/datepickerfield/datepickerfield.recipe.d.ts +18 -3
- package/dist/types/components/menu/menu.d.ts +50 -7
- package/dist/types/components/multiselect/multiselect.d.ts +9 -0
- package/dist/types/components/select/select.d.ts +10 -0
- package/dist/types/core/floating-portal.d.ts +6 -5
- package/dist/types/core/index.d.ts +1 -1
- package/dist/types/core/place-floating.d.ts +37 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/product/assigneepicker/assigneepicker.d.ts +36 -0
- package/dist/types/product/assigneepicker/assigneepicker.recipe.d.ts +3 -0
- package/dist/types/product/assigneepicker/index.d.ts +2 -0
- package/dist/types/product/inbox/inbox.d.ts +13 -1
- package/dist/types/product/notesgallery/index.d.ts +2 -0
- package/dist/types/product/notesgallery/notesgallery.d.ts +54 -0
- package/dist/types/product/notesgallery/notesgallery.recipe.d.ts +3 -0
- package/dist/types/product/objectpagemenu/index.d.ts +1 -0
- package/dist/types/product/objectpagemenu/objectpagemenu.d.ts +40 -0
- package/dist/types/product/objectpagemenu/objectpagemenu.recipe.d.ts +17 -0
- package/dist/types/product/objectsettings/objectsettings.d.ts +71 -15
- package/dist/types/product/recordpage/index.d.ts +1 -1
- package/dist/types/product/recordpage/recordpage.d.ts +142 -1
- package/dist/types/product/recordpicker/index.d.ts +2 -0
- package/dist/types/product/recordpicker/recordpicker.d.ts +38 -0
- package/dist/types/product/recordpicker/recordpicker.recipe.d.ts +3 -0
- package/dist/types/product/savedviews/index.d.ts +1 -1
- package/dist/types/product/savedviews/savedviews.d.ts +36 -0
- package/dist/types/product/share/index.d.ts +2 -0
- package/dist/types/product/share/share.d.ts +85 -0
- package/dist/types/product/share/share.recipe.d.ts +3 -0
- package/dist/types/product/shortcutssheet/index.d.ts +1 -0
- package/dist/types/product/shortcutssheet/shortcutssheet.d.ts +49 -0
- package/dist/types/product/shortcutssheet/shortcutssheet.recipe.d.ts +3 -0
- package/dist/types/product/taskeditpopover/index.d.ts +2 -0
- package/dist/types/product/taskeditpopover/taskeditpopover.d.ts +67 -0
- package/dist/types/product/taskeditpopover/taskeditpopover.recipe.d.ts +3 -0
- package/dist/types/product/workspaceswitchermenu/index.d.ts +1 -0
- package/dist/types/product/workspaceswitchermenu/workspaceswitchermenu.d.ts +54 -0
- package/dist/types/product/workspaceswitchermenu/workspaceswitchermenu.recipe.d.ts +19 -0
- package/dist/types/tokens/contract.d.ts +13 -0
- package/dist/types/tokens/palette.d.ts +52 -0
- package/dist/types/tokens/semantic.d.ts +20 -0
- package/dist/workspaceswitchermenu.css +4 -0
- package/dist/workspaceswitchermenu.d.ts +7 -0
- package/dist/workspaceswitchermenu.layered.css +6 -0
- package/package.json +41 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { defineRecipe, resolveRecipe } from "../../core/index.js";
|
|
2
|
+
import { contract as c } from "../../tokens/index.js";
|
|
3
|
+
// The standalone Notes gallery -- measured live on app.attio.com's Notes
|
|
4
|
+
// page: a toolbar (leading Sort control, trailing view toggle/View
|
|
5
|
+
// settings/New note, both consumer-composed slots -- crisp doesn't own the
|
|
6
|
+
// exact button set, same reasoning as `ListPage`'s own leading/trailing), a
|
|
7
|
+
// dashed "Favorites" region, then date-grouped sections (a group header +
|
|
8
|
+
// count badge, transcribed from `ListPage`'s own already-measured values
|
|
9
|
+
// rather than re-deriving them -- this IS the same Attio pattern, applied
|
|
10
|
+
// above a card grid instead of a row list) holding a responsive card grid of
|
|
11
|
+
// `EntityCard`s.
|
|
12
|
+
//
|
|
13
|
+
// The grid itself: a single real note card measured 356px wide -- with only
|
|
14
|
+
// one note in the workspace there was no second card to read a column gap or
|
|
15
|
+
// count from, so this uses a responsive `auto-fill` grid at that measured
|
|
16
|
+
// per-card width instead of inventing a fixed column count or gap.
|
|
17
|
+
const config = defineRecipe({
|
|
18
|
+
name: "notesgallery",
|
|
19
|
+
base: `display: flex; flex-direction: column; min-height: 0; box-sizing: border-box; font-family: ${c.font.sans}; color: ${c.fgPrimary};
|
|
20
|
+
-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
21
|
+
|
|
22
|
+
& .crisp-notesgallery-tabs { display: flex; align-items: center; gap: ${c.space["2"]}; height: 44px; flex-shrink: 0; padding: 0 ${c.space["3"]}; }
|
|
23
|
+
|
|
24
|
+
& .crisp-notesgallery-toolbar { display: flex; align-items: center; gap: ${c.space["2"]}; height: ${c.size.header}; flex-shrink: 0; padding: 0 ${c.space["3"]}; }
|
|
25
|
+
& .crisp-notesgallery-toolbar-trail { display: flex; align-items: center; gap: ${c.space["2"]}; margin-inline-start: auto; }
|
|
26
|
+
|
|
27
|
+
& .crisp-notesgallery-favorites-section { background: ${c.bgSubtle}; box-shadow: inset 0 -1px 0 ${c.borderSubtle}; }
|
|
28
|
+
& .crisp-notesgallery-favorites-label { padding: ${c.space["2"]} ${c.space["4"]} 0; font-size: ${c.text.sm}; font-weight: ${c.weight.medium}; color: ${c.fgMuted}; }
|
|
29
|
+
& .crisp-notesgallery-favorites { display: flex; align-items: center; justify-content: center; min-height: 200px; padding: ${c.space["6"]}; }
|
|
30
|
+
& .crisp-notesgallery-favorites-empty { box-sizing: border-box; width: 100%; padding: ${c.space["6"]}; border: 1px dashed ${c.borderDefault}; border-radius: ${c.radius.xl}; text-align: center; font-size: ${c.text.md}; color: ${c.fgMuted}; }
|
|
31
|
+
|
|
32
|
+
& .crisp-notesgallery-body { flex: 1; min-height: 0; overflow-y: auto; padding: 0 ${c.space["3"]} ${c.space["3"]}; }
|
|
33
|
+
& .crisp-notesgallery-group { display: flex; align-items: center; gap: ${c.space["1.5"]}; height: ${c.size.menuRow}; padding: ${c.space["2"]} ${c.space["1"]}; font-size: ${c.text.sm}; font-weight: ${c.weight.medium}; color: ${c.fgMuted}; }
|
|
34
|
+
& .crisp-notesgallery-group-count { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 ${c.space["1"]}; border-radius: ${c.radius.sm}; background: color-mix(in srgb, ${c.fgPrimary} 6%, transparent); font-size: ${c.text.xs}; color: ${c.fgMuted}; }
|
|
35
|
+
& .crisp-notesgallery-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: ${c.space["4"]}; padding-bottom: ${c.space["4"]}; }
|
|
36
|
+
|
|
37
|
+
& .crisp-notesgallery-empty { padding: ${c.space["8"]} ${c.space["3"]}; text-align: center; font-size: ${c.text.md}; color: ${c.fgMuted}; }`,
|
|
38
|
+
variants: {},
|
|
39
|
+
defaultVariants: {},
|
|
40
|
+
compoundVariants: [],
|
|
41
|
+
});
|
|
42
|
+
export const notesgalleryRecipe = resolveRecipe(config);
|
|
43
|
+
export default config;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./objectpagemenu.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Bell, ChevronRight, LayoutGrid, MessageSquare, MoreHorizontal, Settings, } from "lucide-react";
|
|
3
|
+
import { Button } from "../../components/button/index.js";
|
|
4
|
+
import { Menu, MenuItem, MenuSeparator } from "../../components/menu/index.js";
|
|
5
|
+
import { useCrispMessage } from "../../core/index.js";
|
|
6
|
+
/**
|
|
7
|
+
* The page-level "…" menu on Attio's object page: Object settings / Manage
|
|
8
|
+
* attributes / a Notifications submenu ("Connect Slack workspace") — crisp
|
|
9
|
+
* parity checklist rows G-020/P-005 (crisp#616). Live-measured on
|
|
10
|
+
* app.attio.com/techpartners/companies: 208×114 popover (12px radius, 4px
|
|
11
|
+
* padding, 32px rows), a hairline separator before Notifications, and its
|
|
12
|
+
* submenu opening flush against the item with no gap, flipping side when
|
|
13
|
+
* the measured width doesn't fit (`placeSubmenu`, core/place-floating.ts).
|
|
14
|
+
*/
|
|
15
|
+
export function ObjectPageMenu({ trigger, onObjectSettings, onManageAttributes, onConnectSlack, objectSettingsLabel, manageAttributesLabel, notificationsLabel, connectSlackLabel, "aria-label": ariaLabelProp, align = "end", }) {
|
|
16
|
+
const objectSettings = useCrispMessage(objectSettingsLabel, "Object settings");
|
|
17
|
+
const manageAttributes = useCrispMessage(manageAttributesLabel, "Manage attributes");
|
|
18
|
+
const notifications = useCrispMessage(notificationsLabel, "Notifications");
|
|
19
|
+
const connectSlack = useCrispMessage(connectSlackLabel, "Connect Slack workspace");
|
|
20
|
+
const ariaLabel = useCrispMessage(ariaLabelProp, "Show menu");
|
|
21
|
+
return (_jsxs(Menu, { align: align, "aria-label": ariaLabel, trigger: trigger ?? (_jsx(Button, { intent: "ghost", size: "md", shape: "icon", "aria-label": ariaLabel, children: _jsx(MoreHorizontal, { size: 16, strokeWidth: 1.75 }) })), children: [_jsx(MenuItem, { icon: _jsx(Settings, { size: 16, strokeWidth: 1.75 }), onSelect: onObjectSettings, children: objectSettings }), _jsx(MenuItem, { icon: _jsx(LayoutGrid, { size: 16, strokeWidth: 1.75 }), onSelect: onManageAttributes, children: manageAttributes }), _jsx(MenuSeparator, {}), _jsx(Menu, { placement: "submenu", "aria-label": notifications, trigger: _jsx(MenuItem, { icon: _jsx(Bell, { size: 16, strokeWidth: 1.75 }), trailingIcon: _jsx(ChevronRight, { "data-rtl-flip": "", size: 16, strokeWidth: 1.75 }), closeOnSelect: false, children: notifications }), children: _jsx(MenuItem, { icon: _jsx(MessageSquare, { size: 16, strokeWidth: 1.75 }), onSelect: onConnectSlack, children: connectSlack }) })] }));
|
|
22
|
+
}
|
|
23
|
+
ObjectPageMenu.displayName = "ObjectPageMenu";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineRecipe, resolveRecipe } from "../../core/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* `ObjectPageMenu` owns no styling of its own — every visible pixel is
|
|
4
|
+
* `Menu`/`MenuItem`'s own already-measured recipe. The one thing it DOES
|
|
5
|
+
* need is the RTL mirror for its Notifications submenu chevron (crisp#616):
|
|
6
|
+
* that icon is the first direction-bearing one ever passed as a `MenuItem`
|
|
7
|
+
* `trailingIcon`, so nothing already mirrors it.
|
|
8
|
+
*
|
|
9
|
+
* Not written as `${rtlFlipCss}` (core/rtl.ts): that string relies on native
|
|
10
|
+
* CSS nesting's `&` resolving to an ENCLOSING rule, which is exactly what
|
|
11
|
+
* `Menu`'s own recipe deliberately avoids for its portaled content (see its
|
|
12
|
+
* own doc comment) — nesting this under a `.crisp-objectpagemenu` root would
|
|
13
|
+
* need a root class this component doesn't have. A plain absolute selector,
|
|
14
|
+
* matching `Menu`'s own pattern for the identical reason.
|
|
15
|
+
*/
|
|
16
|
+
const config = defineRecipe({
|
|
17
|
+
name: "objectpagemenu",
|
|
18
|
+
base: "",
|
|
19
|
+
globalCss: `
|
|
20
|
+
[dir="rtl"] .crisp-menu-item svg[data-rtl-flip] { transform: scaleX(-1); }
|
|
21
|
+
`,
|
|
22
|
+
variants: {},
|
|
23
|
+
defaultVariants: {},
|
|
24
|
+
compoundVariants: [],
|
|
25
|
+
});
|
|
26
|
+
export const objectpagemenuRecipe = resolveRecipe(config);
|
|
27
|
+
export default config;
|
|
@@ -161,7 +161,7 @@ ObjectAppearanceSection.displayName = "ObjectAppearanceSection";
|
|
|
161
161
|
* measures 40px here — a real, small divergence left to DataTable's owner
|
|
162
162
|
* rather than patched locally (see the PR).
|
|
163
163
|
*/
|
|
164
|
-
export function AttributesTable({ attributes, onOpenAttribute, onEditAttribute, onArchiveAttribute, onReorder, onCreateAttribute, title: titleProp, subtitle: subtitleProp, createLabel: createLabelProp, searchPlaceholder: searchPlaceholderProp, filterLabel: filterLabelProp, nameColumnLabel: nameColumnLabelProp, dataTypeColumnLabel: dataTypeColumnLabelProp, constraintsColumnLabel: constraintsColumnLabelProp, propertiesColumnLabel: propertiesColumnLabelProp, systemLabel: systemLabelProp, enrichedLabel: enrichedLabelProp, editLabel: editLabelProp, archiveLabel: archiveLabelProp, rowActions, emptyLabel, }) {
|
|
164
|
+
export function AttributesTable({ attributes, onOpenAttribute, onEditAttribute, onDuplicateAttribute, onCopyId, onCopySlug, onChangeSlug, onArchiveAttribute, onReorder, onCreateAttribute, title: titleProp, subtitle: subtitleProp, createLabel: createLabelProp, searchPlaceholder: searchPlaceholderProp, filterLabel: filterLabelProp, nameColumnLabel: nameColumnLabelProp, dataTypeColumnLabel: dataTypeColumnLabelProp, constraintsColumnLabel: constraintsColumnLabelProp, propertiesColumnLabel: propertiesColumnLabelProp, systemLabel: systemLabelProp, enrichedLabel: enrichedLabelProp, editLabel: editLabelProp, duplicateLabel: duplicateLabelProp, copyIdLabel: copyIdLabelProp, copySlugLabel: copySlugLabelProp, changeSlugLabel: changeSlugLabelProp, archiveLabel: archiveLabelProp, rowActions, emptyLabel, }) {
|
|
165
165
|
const title = useCrispMessage(titleProp, "Attributes");
|
|
166
166
|
const subtitle = useCrispMessage(subtitleProp, "Modify and add object attributes");
|
|
167
167
|
const createLabel = useCrispMessage(createLabelProp, "Create attribute");
|
|
@@ -173,8 +173,37 @@ export function AttributesTable({ attributes, onOpenAttribute, onEditAttribute,
|
|
|
173
173
|
const propertiesColumnLabel = useCrispMessage(propertiesColumnLabelProp, "Properties");
|
|
174
174
|
const systemLabel = useCrispMessage(systemLabelProp, "System");
|
|
175
175
|
const enrichedLabel = useCrispMessage(enrichedLabelProp, "Enriched");
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
// crisp#673, G-107 (verbatim): "Edit attribute, Duplicate attribute, Copy
|
|
177
|
+
// ID, Copy slug, Change slug, Archive attribute" — corrects this
|
|
178
|
+
// component's former "Edit"/"Archive" defaults, which were a paraphrase
|
|
179
|
+
// from an earlier, less precise measurement.
|
|
180
|
+
const editLabel = useCrispMessage(editLabelProp, "Edit attribute");
|
|
181
|
+
const duplicateLabel = useCrispMessage(duplicateLabelProp, "Duplicate attribute");
|
|
182
|
+
const copyIdLabel = useCrispMessage(copyIdLabelProp, "Copy ID");
|
|
183
|
+
const copySlugLabel = useCrispMessage(copySlugLabelProp, "Copy slug");
|
|
184
|
+
const changeSlugLabel = useCrispMessage(changeSlugLabelProp, "Change slug");
|
|
185
|
+
const archiveLabel = useCrispMessage(archiveLabelProp, "Archive attribute");
|
|
186
|
+
// Default-with-override (same shape as `CommentsPanel`'s `onCopyLink`):
|
|
187
|
+
// a consumer that only wants to know a copy happened (a toast, analytics)
|
|
188
|
+
// gets it for free; one that needs the real copy still owns it.
|
|
189
|
+
// `useCallback`, not a plain closure: the `columns` memo below references
|
|
190
|
+
// these, not `onCopyId`/`onCopySlug` directly, so their own identity has
|
|
191
|
+
// to track those props for the memo to stay correct without re-listing
|
|
192
|
+
// props it never mentions by name.
|
|
193
|
+
const copyId = React.useCallback((id) => {
|
|
194
|
+
if (onCopyId) {
|
|
195
|
+
onCopyId(id);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
navigator.clipboard?.writeText(id).catch(() => { });
|
|
199
|
+
}, [onCopyId]);
|
|
200
|
+
const copySlug = React.useCallback((slug) => {
|
|
201
|
+
if (onCopySlug) {
|
|
202
|
+
onCopySlug(slug);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
navigator.clipboard?.writeText(slug).catch(() => { });
|
|
206
|
+
}, [onCopySlug]);
|
|
178
207
|
const [query, setQuery] = React.useState("");
|
|
179
208
|
const filtered = React.useMemo(() => {
|
|
180
209
|
if (!query.trim())
|
|
@@ -215,7 +244,7 @@ export function AttributesTable({ attributes, onOpenAttribute, onEditAttribute,
|
|
|
215
244
|
cursor: "pointer",
|
|
216
245
|
color: "inherit",
|
|
217
246
|
padding: 4,
|
|
218
|
-
}, children: _jsx(MoreHorizontal, { size: 16 }) }), children: rowActions ? (rowActions(row)) : (_jsxs(_Fragment, { children: [_jsx(MenuItem, { onSelect: () => onEditAttribute?.(row.id), children: editLabel }), _jsx(MenuItem, { danger: true, onSelect: () => onArchiveAttribute?.(row.id), children: archiveLabel })] })) })),
|
|
247
|
+
}, children: _jsx(MoreHorizontal, { size: 16 }) }), children: rowActions ? (rowActions(row)) : (_jsxs(_Fragment, { children: [_jsx(MenuItem, { onSelect: () => onEditAttribute?.(row.id), children: editLabel }), _jsx(MenuItem, { onSelect: () => onDuplicateAttribute?.(row.id), children: duplicateLabel }), _jsx(MenuItem, { onSelect: () => copyId(row.id), children: copyIdLabel }), _jsx(MenuItem, { disabled: !row.slug, onSelect: () => row.slug && copySlug(row.slug), children: copySlugLabel }), _jsx(MenuItem, { onSelect: () => onChangeSlug?.(row.id), children: changeSlugLabel }), _jsx(MenuItem, { danger: true, onSelect: () => onArchiveAttribute?.(row.id), children: archiveLabel })] })) })),
|
|
219
248
|
},
|
|
220
249
|
], [
|
|
221
250
|
nameColumnLabel,
|
|
@@ -225,8 +254,16 @@ export function AttributesTable({ attributes, onOpenAttribute, onEditAttribute,
|
|
|
225
254
|
systemLabel,
|
|
226
255
|
enrichedLabel,
|
|
227
256
|
editLabel,
|
|
257
|
+
duplicateLabel,
|
|
258
|
+
copyIdLabel,
|
|
259
|
+
copySlugLabel,
|
|
260
|
+
changeSlugLabel,
|
|
228
261
|
archiveLabel,
|
|
229
262
|
onEditAttribute,
|
|
263
|
+
onDuplicateAttribute,
|
|
264
|
+
copyId,
|
|
265
|
+
copySlug,
|
|
266
|
+
onChangeSlug,
|
|
230
267
|
onArchiveAttribute,
|
|
231
268
|
rowActions,
|
|
232
269
|
]);
|
|
@@ -287,26 +324,82 @@ export function AttributeTypeSelect({ value, onValueChange, options = ATTRIBUTE_
|
|
|
287
324
|
return (_jsx(Select, { id: id, options: selectOptions, value: value, onValueChange: onValueChange, searchable: true, searchPlaceholder: searchPlaceholder, width: "full" }));
|
|
288
325
|
}
|
|
289
326
|
AttributeTypeSelect.displayName = "AttributeTypeSelect";
|
|
327
|
+
/* ── Currency's own extra required fields (crisp#673, G-104) ──────────
|
|
328
|
+
* Attio's Create-attribute dialog isn't a fixed layout: choosing "Currency"
|
|
329
|
+
* inserts four required selects between Description and the AI-autofill
|
|
330
|
+
* row (Text/Number/Checkbox/Date/Phone Number/Location/User add none).
|
|
331
|
+
* Display, Decimal Places and Grouping are Attio's own small fixed UI
|
|
332
|
+
* vocabulary (verified live, `app.attio.com` Companies object) and ship as
|
|
333
|
+
* defaults below; Currency is real reference data (ISO 4217, ~150+
|
|
334
|
+
* entries) that crisp doesn't own, so it has no default of its own — a
|
|
335
|
+
* consumer supplies its own list, the same "GRC need: later" boundary
|
|
336
|
+
* `aiAutofillTypeOptions` already draws. Each option's own live-formatted
|
|
337
|
+
* preview (e.g. "Code — USD 1,200.50") is deliberately not modelled: it
|
|
338
|
+
* needs real number-formatting logic, a separate, bigger piece of work. */
|
|
339
|
+
export const CURRENCY_DISPLAY_OPTIONS = [
|
|
340
|
+
{ value: "code", label: "Code" },
|
|
341
|
+
{ value: "name", label: "Name" },
|
|
342
|
+
{ value: "narrow-symbol", label: "Narrow symbol" },
|
|
343
|
+
{ value: "symbol", label: "Symbol" },
|
|
344
|
+
];
|
|
345
|
+
export const CURRENCY_DECIMAL_PLACES_OPTIONS = [
|
|
346
|
+
{ value: "2", label: "2 decimals" },
|
|
347
|
+
{ value: "0", label: "No decimals" },
|
|
348
|
+
];
|
|
349
|
+
export const CURRENCY_GROUPING_OPTIONS = [
|
|
350
|
+
{ value: "default", label: "Default" },
|
|
351
|
+
{ value: "none", label: "No groups" },
|
|
352
|
+
];
|
|
290
353
|
/**
|
|
291
|
-
* "Create attribute" (crisp#637, P-125, verified live): Attribute Type
|
|
292
|
-
* select (defaults to Text
|
|
293
|
-
* +
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
354
|
+
* "Create attribute" (crisp#637/#673, P-125, verified live): Attribute Type
|
|
355
|
+
* select (defaults to Text, type-ahead search built in via `Select`'s own
|
|
356
|
+
* `searchable`) + Name (autofocused on open, and again after picking a type
|
|
357
|
+
* — G-106) + Description (optional) + Currency's own four extra required
|
|
358
|
+
* fields when `type === "currency"` (G-104) + an AI-autofill switch row,
|
|
359
|
+
* footer Cancel/Create. Built on `FormDialog` in its plain (non-`createMode`)
|
|
360
|
+
* shape — Attio's own footer here has a real Cancel button, unlike the
|
|
361
|
+
* record-create dialog `createMode` models (crisp#548), so `createMode`
|
|
362
|
+
* would be the wrong fit even though the button carries the same ⌘↵ hint;
|
|
363
|
+
* that hint is rendered locally instead. The outside-click/Escape
|
|
364
|
+
* "Discard changes" confirm gated on dirty state (G-105) needed no work
|
|
365
|
+
* here at all — `FormDialog` already tracks dirtiness generically and shows
|
|
366
|
+
* it, so this dialog inherited it for free; `CreateAttributeDialog`'s own
|
|
367
|
+
* tests below just pin the behaviour to this component specifically.
|
|
298
368
|
*/
|
|
299
|
-
export function CreateAttributeDialog({ type, onTypeChange, typeOptions, aiAutofillTypeOptions, name, onNameChange, description, onDescriptionChange, aiAutofillEnabled = false, onAiAutofillChange, onSubmit, onClose, busy, error, title: titleProp, attributeTypeLabel: attributeTypeLabelProp, nameLabel: nameLabelProp, descriptionLabel: descriptionLabelProp, descriptionPlaceholder: descriptionPlaceholderProp, aiAutofillLabel: aiAutofillLabelProp, setUpAiAutofillLabel: setUpAiAutofillLabelProp, createLabel: createLabelProp, }) {
|
|
369
|
+
export function CreateAttributeDialog({ type, onTypeChange, typeOptions, aiAutofillTypeOptions, name, onNameChange, description, onDescriptionChange, currency, onCurrencyChange, currencyOptions, display, onDisplayChange, displayOptions = CURRENCY_DISPLAY_OPTIONS, decimalPlaces, onDecimalPlacesChange, decimalPlacesOptions = CURRENCY_DECIMAL_PLACES_OPTIONS, grouping, onGroupingChange, groupingOptions = CURRENCY_GROUPING_OPTIONS, currencyFieldLabel: currencyFieldLabelProp, displayFieldLabel: displayFieldLabelProp, decimalPlacesFieldLabel: decimalPlacesFieldLabelProp, groupingFieldLabel: groupingFieldLabelProp, aiAutofillEnabled = false, onAiAutofillChange, onSubmit, onClose, busy, error, title: titleProp, attributeTypeLabel: attributeTypeLabelProp, nameLabel: nameLabelProp, descriptionLabel: descriptionLabelProp, descriptionPlaceholder: descriptionPlaceholderProp, aiAutofillLabel: aiAutofillLabelProp, setUpAiAutofillLabel: setUpAiAutofillLabelProp, createLabel: createLabelProp, }) {
|
|
300
370
|
const title = useCrispMessage(titleProp, "Create attribute");
|
|
301
371
|
const attributeTypeLabel = useCrispMessage(attributeTypeLabelProp, "Attribute Type");
|
|
302
372
|
const nameLabel = useCrispMessage(nameLabelProp, "Name");
|
|
303
373
|
const descriptionLabel = useCrispMessage(descriptionLabelProp, "Description");
|
|
304
374
|
const descriptionPlaceholder = useCrispMessage(descriptionPlaceholderProp, "Add a description for this attribute");
|
|
375
|
+
// Attio's own copy for these four is the label plus a literal "(required)"
|
|
376
|
+
// suffix (not `FormField`'s asterisk convention) — the same "(optional)"-
|
|
377
|
+
// as-label-text shape `descriptionLabel` above already uses, so these are
|
|
378
|
+
// plain overridable strings rather than a new `FormField` capability.
|
|
379
|
+
const currencyFieldLabel = useCrispMessage(currencyFieldLabelProp, "Currency (required)");
|
|
380
|
+
const displayFieldLabel = useCrispMessage(displayFieldLabelProp, "Display (required)");
|
|
381
|
+
const decimalPlacesFieldLabel = useCrispMessage(decimalPlacesFieldLabelProp, "Decimal Places (required)");
|
|
382
|
+
const groupingFieldLabel = useCrispMessage(groupingFieldLabelProp, "Grouping (required)");
|
|
305
383
|
const aiAutofillLabel = useCrispMessage(aiAutofillLabelProp, "AI autofill");
|
|
306
384
|
const setUpAiAutofillLabel = useCrispMessage(setUpAiAutofillLabelProp, "Set up AI autofill");
|
|
307
385
|
const createLabel = useCrispMessage(createLabelProp, "Create attribute");
|
|
308
386
|
const switchId = React.useId();
|
|
309
387
|
const nameId = React.useId();
|
|
310
|
-
|
|
388
|
+
// G-106: selecting a type via the keyboard (filter to one match, Enter)
|
|
389
|
+
// auto-focuses Name right after — measured live. `Select`'s own
|
|
390
|
+
// `onValueChange` fires identically for a keyboard Enter and a mouse
|
|
391
|
+
// click (both go through its one `pick()`), so this can't distinguish
|
|
392
|
+
// input modality; only the keyboard path was independently re-measured,
|
|
393
|
+
// but there is no second code path to special-case even if there were
|
|
394
|
+
// one. The `requestAnimationFrame` wins the same race `Select`'s own
|
|
395
|
+
// `searchable` variant and `SavedViews`'s search box already schedule
|
|
396
|
+
// focus against (Select returns focus to its trigger synchronously on
|
|
397
|
+
// pick; this runs after that paint and wins).
|
|
398
|
+
const nameRef = React.useRef(null);
|
|
399
|
+
const handleTypeChange = (value) => {
|
|
400
|
+
onTypeChange(value);
|
|
401
|
+
requestAnimationFrame(() => nameRef.current?.focus({ preventScroll: true }));
|
|
402
|
+
};
|
|
403
|
+
return (_jsxs(FormDialog, { title: title, submitLabel: _jsxs(_Fragment, { children: [createLabel, _jsxs("span", { className: "crisp-objectsettings-kbd", "aria-hidden": "true", children: [_jsx("kbd", { children: "\u2318" }), _jsx("kbd", { children: "\u21B5" })] })] }), onSubmit: onSubmit, onClose: onClose, busy: busy, error: error, size: 520, className: objectsettingsRecipe(), children: [_jsx(FormField, { label: attributeTypeLabel, children: _jsx(AttributeTypeSelect, { value: type, onValueChange: handleTypeChange, options: typeOptions, aiAutofillOptions: aiAutofillTypeOptions }) }), _jsx(FormField, { label: nameLabel, htmlFor: nameId, children: _jsx(Input, { ref: nameRef, id: nameId, autoFocus: true, value: name, onChange: (e) => onNameChange(e.target.value) }) }), _jsx(FormField, { label: descriptionLabel, optional: true, children: _jsx(Textarea, { placeholder: descriptionPlaceholder, value: description, onChange: (e) => onDescriptionChange(e.target.value) }) }), type === "currency" && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "crisp-objectsettings-fieldrow", children: [_jsx(FormField, { label: currencyFieldLabel, children: _jsx(Select, { options: currencyOptions ?? [], value: currency ?? "", onValueChange: onCurrencyChange, searchable: true }) }), _jsx(FormField, { label: displayFieldLabel, children: _jsx(Select, { options: displayOptions, value: display ?? displayOptions[0]?.value, onValueChange: onDisplayChange }) })] }), _jsxs("div", { className: "crisp-objectsettings-fieldrow", children: [_jsx(FormField, { label: decimalPlacesFieldLabel, children: _jsx(Select, { options: decimalPlacesOptions, value: decimalPlaces ?? decimalPlacesOptions[0]?.value, onValueChange: onDecimalPlacesChange }) }), _jsx(FormField, { label: groupingFieldLabel, children: _jsx(Select, { options: groupingOptions, value: grouping ?? groupingOptions[0]?.value, onValueChange: onGroupingChange }) })] })] })), _jsxs("div", { className: "crisp-objectsettings-aiautofill", children: [_jsx("span", { className: "crisp-objectsettings-aiautofill-label", children: aiAutofillLabel }), _jsxs("label", { className: "crisp-objectsettings-aiautofill-row", htmlFor: switchId, children: [_jsx(Sparkles, { size: 14 }), _jsx("span", { children: setUpAiAutofillLabel }), _jsx(Switch, { id: switchId, checked: aiAutofillEnabled, onCheckedChange: onAiAutofillChange })] })] })] }));
|
|
311
404
|
}
|
|
312
405
|
CreateAttributeDialog.displayName = "CreateAttributeDialog";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { tagTone, } from "./column.js";
|
|
2
2
|
export { editableInputType, useEditableField, } from "./editable-field.js";
|
|
3
|
-
export { RecordDetails, RecordPage, recordPageLabels, } from "./recordpage.js";
|
|
3
|
+
export { RecordDetails, RecordPage, RecordPageMain, recordPageLabels, } from "./recordpage.js";
|
|
4
4
|
export { recordpageRecipe } from "./recordpage.recipe.js";
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Activity as ActivityIcon, CalendarClock, ChevronDown, ChevronUp, CircleDollarSign, Copy, DollarSign, FileText, Folder, Inbox, Landmark, LayoutGrid, Mail, Maximize2, Merge, MessageSquare, Minimize2, MoreHorizontal, Pencil, Phone, Plus, Search, Settings, Sparkles, SquareCheck, Star, Trash2, Users, UsersRound, Workflow, X, Zap, } from "lucide-react";
|
|
2
|
+
import { Activity as ActivityIcon, CalendarClock, ChevronDown, ChevronUp, CircleDollarSign, Copy, DollarSign, FileText, Folder, Inbox, Landmark, LayoutGrid, ListFilter, Mail, Maximize2, Merge, MessageSquare, Minimize2, MoreHorizontal, Pencil, Phone, Plus, Search, Settings, Sparkles, SquareCheck, Star, Trash2, Users, UsersRound, Workflow, X, Zap, } from "lucide-react";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Avatar } from "../../components/avatar/index.js";
|
|
5
|
+
import { Button } from "../../components/button/index.js";
|
|
5
6
|
import { Checkbox } from "../../components/checkbox/index.js";
|
|
6
7
|
import { Drawer } from "../../components/drawer/index.js";
|
|
8
|
+
import { Kbd } from "../../components/kbd/index.js";
|
|
7
9
|
import { Menu, MenuItem, MenuSeparator } from "../../components/menu/index.js";
|
|
8
10
|
import { Select } from "../../components/select/index.js";
|
|
9
11
|
import { Spinner } from "../../components/spinner/index.js";
|
|
@@ -62,6 +64,28 @@ export const recordPageLabels = {
|
|
|
62
64
|
share: "Share",
|
|
63
65
|
comments: "Comments",
|
|
64
66
|
noLists: "This record has not been added to any lists.",
|
|
67
|
+
filters: "Filters",
|
|
68
|
+
addMeeting: "Add meeting",
|
|
69
|
+
noFiles: "No files",
|
|
70
|
+
filesHint: "Drag a file here or choose one from your computer.",
|
|
71
|
+
newFolder: "New folder",
|
|
72
|
+
uploadFile: "Upload file",
|
|
73
|
+
fileMoreActions: "More file actions",
|
|
74
|
+
noDealsFound: "No Deals found",
|
|
75
|
+
noDealsHint: "There are no related records for this relationship.",
|
|
76
|
+
newDeal: "New Deal",
|
|
77
|
+
noEmails: "No emails",
|
|
78
|
+
noEmailsHint: "This record doesn't have any emails, or they may be hidden due to permissions.",
|
|
79
|
+
configureMailbox: "Configure mailbox",
|
|
80
|
+
upgradeToViewCalls: "Upgrade to view calls",
|
|
81
|
+
callsUpgradeHint: "No call recordings yet! Upgrade to Pro to start recording calls.",
|
|
82
|
+
upgrade: "Upgrade",
|
|
83
|
+
selectWorkflowTitle: "Select workflow",
|
|
84
|
+
closeWorkflowPopover: "Close",
|
|
85
|
+
searchWorkflowsPlaceholder: "Search workflows...",
|
|
86
|
+
noWorkflows: "No workflows",
|
|
87
|
+
createNewWorkflow: "Create new workflow",
|
|
88
|
+
navigate: "Navigate",
|
|
65
89
|
highlights: "Highlights",
|
|
66
90
|
recordDetails: "Record Details",
|
|
67
91
|
searchFields: "Search fields",
|
|
@@ -459,7 +483,7 @@ export function RecordDetails({ record, columns, sections, onEdit, labels, class
|
|
|
459
483
|
commit(record.id, col.key, e.currentTarget.value);
|
|
460
484
|
} }), isPending && (_jsx(Spinner, { size: 14, className: "crisp-recordpage-edit-pending" })), fieldError && (_jsx("span", { className: "crisp-recordpage-edit-error", children: fieldError }))] })] }, col.key));
|
|
461
485
|
}
|
|
462
|
-
return (_jsxs("div", { className: "crisp-recordpage-field", children: [label, _jsxs("span", { className: "crisp-recordpage-field-value", "data-editable": canEdit ? "" : undefined, children: [canEdit ? (_jsx("button", { type: "button", className: "crisp-recordpage-edit-trigger", onClick: () => openEditing(col.key), children: _jsx(FieldValue, { col: col, row: record, labels: t }) })) : (_jsx(FieldValue, { col: col, row: record, labels: t })), fieldError && (_jsx("span", { className: "crisp-recordpage-edit-error", children: fieldError }))] })] }, col.key));
|
|
486
|
+
return (_jsxs("div", { className: "crisp-recordpage-field", children: [label, _jsxs("span", { className: "crisp-recordpage-field-value", "data-editable": canEdit ? "" : undefined, children: [canEdit && col.type === "url" ? (_jsxs(_Fragment, { children: [_jsx(FieldValue, { col: col, row: record, labels: t }), _jsx("button", { type: "button", className: "crisp-recordpage-edit-affordance", "aria-label": `Edit ${col.label}`, onClick: () => openEditing(col.key), children: _jsx(Pencil, { size: 16, strokeWidth: 1.75 }) })] })) : canEdit ? (_jsx("button", { type: "button", className: "crisp-recordpage-edit-trigger", onClick: () => openEditing(col.key), children: _jsx(FieldValue, { col: col, row: record, labels: t }) })) : (_jsx(FieldValue, { col: col, row: record, labels: t })), fieldError && (_jsx("span", { className: "crisp-recordpage-edit-error", children: fieldError }))] })] }, col.key));
|
|
463
487
|
})] }, g.title != null ? String(g.title) : `section-${i}`))), canRevealFields && (_jsx("button", { type: "button", className: "crisp-recordpage-viewall crisp-recordpage-viewall--section", onClick: () => {
|
|
464
488
|
// Revealing all fields also opens the (focused) field
|
|
465
489
|
// search; collapsing closes it.
|
|
@@ -477,6 +501,68 @@ export function RecordDetails({ record, columns, sections, onEdit, labels, class
|
|
|
477
501
|
} }))] }));
|
|
478
502
|
}
|
|
479
503
|
RecordDetails.displayName = "RecordDetails";
|
|
504
|
+
/**
|
|
505
|
+
* The main/tab-content column's own box, on its own (#750) — the same
|
|
506
|
+
* "standalone piece" `RecordDetails` already is for the Details column,
|
|
507
|
+
* mirrored to the other side of the layout. `RecordPage` composes this
|
|
508
|
+
* internally for its own tabbed body (below) — this exact box, not a second
|
|
509
|
+
* copy — so a consumer building a custom record page (its own tab set, its
|
|
510
|
+
* own menu, `RecordDetails` for the left column, but not the full
|
|
511
|
+
* `RecordPage`, which hardcodes the CRM tab set) gets the same
|
|
512
|
+
* Attio-measured inset: `flex: 1; min-width: 0; display: flex;
|
|
513
|
+
* flex-direction: column; min-height: 0; overflow-y: auto; padding: 12px
|
|
514
|
+
* 20px` (`recordpage.recipe.ts`'s own `.crisp-recordpage-main`) — without
|
|
515
|
+
* reaching for that internal class name directly, the only option before
|
|
516
|
+
* this existed.
|
|
517
|
+
*
|
|
518
|
+
* Needs the same single `.crisp-recordpage`-scoped ancestor `RecordDetails`
|
|
519
|
+
* already documents — wrap both in `recordpageRecipe()`, the way
|
|
520
|
+
* `RecordPage`'s own root does.
|
|
521
|
+
*/
|
|
522
|
+
export function RecordPageMain({ className, children, ...rest }) {
|
|
523
|
+
return (_jsx("main", { className: cx("crisp-recordpage-main", className), ...rest, children: children }));
|
|
524
|
+
}
|
|
525
|
+
RecordPageMain.displayName = "RecordPageMain";
|
|
526
|
+
/**
|
|
527
|
+
* The "Run workflow" quick action's own popover (#620, P-018) — measured
|
|
528
|
+
* live on `app.attio.com`'s own record page: a centered, top-anchored
|
|
529
|
+
* overlay sharing the exact same frosted-bezel shell `product/command`'s
|
|
530
|
+
* own Cmd-K palette already uses (`elevation.modal`, `bgOverlayFrost` 60% +
|
|
531
|
+
* `blur(15px)`, radius `2xl`/`xl` outer/inner) — duplicated into this
|
|
532
|
+
* recipe rather than imported, the same "own copy per product surface"
|
|
533
|
+
* choice already made elsewhere in this codebase (`product/command` is
|
|
534
|
+
* outside this lane's own `product/recordpage` ownership).
|
|
535
|
+
*
|
|
536
|
+
* Chrome only, like the quick-action row's own Compose email/Add to list
|
|
537
|
+
* buttons above: crisp has no workflow data model to search or list yet, so
|
|
538
|
+
* the search box is real and typable but filters nothing, and the "No
|
|
539
|
+
* workflows" empty state always renders. The measured popover's own
|
|
540
|
+
* illustration is a bespoke Attio graphic (Golden Rule 4: Lucide only) —
|
|
541
|
+
* reuses crisp's own established "illustrated empty state" dotted-grid tile
|
|
542
|
+
* (`.crisp-recordpage-illustrated-icon`, already shipped for Files/Deals)
|
|
543
|
+
* with a `Workflow` glyph instead.
|
|
544
|
+
*/
|
|
545
|
+
function WorkflowPopover({ open, onClose, labels, }) {
|
|
546
|
+
React.useEffect(() => {
|
|
547
|
+
if (!open)
|
|
548
|
+
return;
|
|
549
|
+
const onKey = (e) => {
|
|
550
|
+
if (e.key === "Escape")
|
|
551
|
+
onClose();
|
|
552
|
+
};
|
|
553
|
+
document.addEventListener("keydown", onKey);
|
|
554
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
555
|
+
}, [open, onClose]);
|
|
556
|
+
if (!open)
|
|
557
|
+
return null;
|
|
558
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "crisp-recordpage-workflow-backdrop" }), _jsx("div", { className: "crisp-recordpage-workflow-positioner", onClick: (e) => {
|
|
559
|
+
if (e.target === e.currentTarget)
|
|
560
|
+
onClose();
|
|
561
|
+
}, children: _jsx("div", { className: "crisp-recordpage-workflow-content", children: _jsxs("div", { className: "crisp-recordpage-workflow-card", role: "dialog", "aria-modal": "true", "aria-label": labels.selectWorkflowTitle, children: [_jsxs("div", { className: "crisp-recordpage-workflow-header", children: [_jsx("span", { className: "crisp-recordpage-workflow-title", children: labels.selectWorkflowTitle }), _jsx("button", { type: "button", className: "crisp-recordpage-workflow-close", "aria-label": labels.closeWorkflowPopover, onClick: onClose, children: _jsx(X, { size: 14, strokeWidth: 1.75 }) })] }), _jsx("div", { className: "crisp-recordpage-workflow-search", children: _jsx("input", {
|
|
562
|
+
// biome-ignore lint/a11y/noAutofocus: focusing the just-opened popover's search is expected
|
|
563
|
+
autoFocus: true, type: "text", role: "combobox", "aria-expanded": "true", "aria-label": labels.selectWorkflowTitle, placeholder: labels.searchWorkflowsPlaceholder, className: "crisp-recordpage-workflow-input" }) }), _jsx("div", { className: "crisp-recordpage-workflow-sep" }), _jsxs("div", { className: "crisp-recordpage-workflow-empty", children: [_jsx("span", { className: "crisp-recordpage-illustrated-icon", "aria-hidden": "true", children: _jsx(Workflow, { size: 40, strokeWidth: 1.25 }) }), _jsx("div", { className: "crisp-recordpage-illustrated-desc", children: labels.noWorkflows }), _jsxs("button", { type: "button", className: "crisp-recordpage-workflow-create", children: [_jsx(Plus, { size: 14, strokeWidth: 1.75 }), labels.createNewWorkflow] })] }), _jsx("div", { className: "crisp-recordpage-workflow-footer", children: _jsxs("span", { className: "crisp-recordpage-workflow-footer-nav", children: [_jsx(Kbd, { children: "\u2191" }), _jsx(Kbd, { children: "\u2193" }), labels.navigate] }) })] }) }) })] }));
|
|
564
|
+
}
|
|
565
|
+
WorkflowPopover.displayName = "WorkflowPopover";
|
|
480
566
|
/**
|
|
481
567
|
* The full-page company record view — the page that opens when you click into a
|
|
482
568
|
* record (as opposed to the peek [RecordPanel]). A top breadcrumb bar with
|
|
@@ -485,9 +571,15 @@ RecordDetails.displayName = "RecordDetails";
|
|
|
485
571
|
* Emails sections (Notes & Tasks are live — add and check things off). Drop it
|
|
486
572
|
* into the app's main content area.
|
|
487
573
|
*/
|
|
488
|
-
export function RecordPage({ record, columns, collection = "Records", labels, onBack, onToggleFull, full, onClose, closeLabel = "Close", onPrevious, previousRecordLabel = "Previous record", onNext, nextRecordLabel = "Next record", counterLabel, className, onEdit, tabs: tabsProp, defaultTab, notes: notesProp, onAddNote, tasks: tasksProp, onAddTask, onToggleTask, sections, highlights: highlightsProp, quickActions: quickActionsProp, comments: commentsProp, onPostComment, currentUser, onShare, ...rest }) {
|
|
574
|
+
export function RecordPage({ record, columns, collection = "Records", labels, onBack, onToggleFull, full, onClose, closeLabel = "Close", onPrevious, previousRecordLabel = "Previous record", onNext, nextRecordLabel = "Next record", counterLabel, className, onEdit, tabs: tabsProp, defaultTab, notes: notesProp, onAddNote, tasks: tasksProp, onAddTask, onToggleTask, sections, highlights: highlightsProp, quickActions: quickActionsProp, comments: commentsProp, onPostComment, currentUser, onShare, avatarShape = "rounded", onEditRecord, onDelete, actions, showBreadcrumb = true, showShare = true, showAskAttio = true, showComments = true, ...rest }) {
|
|
489
575
|
const t = useRecordPageLabels(labels);
|
|
490
576
|
const [favorite, setFavorite] = React.useState(false);
|
|
577
|
+
// #620: the "Run workflow" quick action's own popover -- only the
|
|
578
|
+
// BUILT-IN default quick-action list opens it (below); a caller supplying
|
|
579
|
+
// its own `quickActions` owns every button's behaviour itself, the same
|
|
580
|
+
// "caller's own list, not a merge" contract `tabs`/`highlights` already
|
|
581
|
+
// make.
|
|
582
|
+
const [workflowPopoverOpen, setWorkflowPopoverOpen] = React.useState(false);
|
|
491
583
|
// Notes/Tasks are controlled when the caller supplies `notes`/`tasks` (even
|
|
492
584
|
// `[]`) -- otherwise RecordPage keeps its own self-contained state, as
|
|
493
585
|
// before (#379). "Controlled" here means the array prop alone decides the
|
|
@@ -604,7 +696,13 @@ export function RecordPage({ record, columns, collection = "Records", labels, on
|
|
|
604
696
|
// choice `tabs` (#379) and `highlights` (#528) already make.
|
|
605
697
|
const defaultQuickActions = [
|
|
606
698
|
{ label: t.logANote, icon: FileText },
|
|
607
|
-
|
|
699
|
+
// #620: only the built-in default wires this to crisp's own popover --
|
|
700
|
+
// see `workflowPopoverOpen`'s own comment above.
|
|
701
|
+
{
|
|
702
|
+
label: t.runWorkflow,
|
|
703
|
+
icon: Workflow,
|
|
704
|
+
onSelect: () => setWorkflowPopoverOpen(true),
|
|
705
|
+
},
|
|
608
706
|
{ label: t.createATask, icon: SquareCheck },
|
|
609
707
|
];
|
|
610
708
|
const quickActions = quickActionsProp ?? defaultQuickActions;
|
|
@@ -717,13 +815,19 @@ export function RecordPage({ record, columns, collection = "Records", labels, on
|
|
|
717
815
|
case "overview":
|
|
718
816
|
return (_jsxs("div", { className: "crisp-recordpage-panel", children: [highlights !== false && (_jsxs("section", { children: [_jsx("div", { className: "crisp-recordpage-section-title", children: t.highlights }), _jsx("div", { className: "crisp-recordpage-hl-card", children: _jsx("div", { className: "crisp-recordpage-hl-grid", children: highlights.map((h) => (_jsxs("div", { className: "crisp-recordpage-hl-cell", children: [h.icon && (_jsx(h.icon, { className: "crisp-recordpage-hl-icon", size: 15, strokeWidth: 1.75 })), _jsx("div", { className: "crisp-recordpage-hl-label", children: h.label }), _jsx("div", { className: "crisp-recordpage-hl-value", children: h.value ?? (_jsx("span", { className: "crisp-recordpage-hl-value--empty", children: h.empty })) })] }, h.label))) }) })] })), activityFeed, _jsxs("section", { children: [_jsx(SectionHead, { title: t.tabs.emails, count: 0 }), emailsEmpty] }), _jsx(NotesSection, { notes: notes, onAdd: addNote, labels: t }), _jsx(TasksSection, { tasks: tasks, onAdd: addTask, onToggle: toggleTask, labels: t })] }));
|
|
719
817
|
case "activity":
|
|
720
|
-
return _jsx("div", { className: "crisp-recordpage-panel", children:
|
|
818
|
+
return (_jsx("div", { className: "crisp-recordpage-panel", children: _jsxs("section", { children: [_jsx(SectionHead, { title: t.tabs.activity, action: _jsxs("span", { className: "crisp-recordpage-tabtoolbar", children: [_jsxs("button", { type: "button", className: "crisp-recordpage-action", children: [_jsx(ListFilter, { size: 14, strokeWidth: 1.75 }), t.filters] }), _jsxs("button", { type: "button", className: "crisp-recordpage-action", children: [_jsx(Plus, { size: 14, strokeWidth: 1.75 }), t.addMeeting] })] }) }), _jsx("div", { className: "crisp-recordpage-feed", children: activity.map((a) => (_jsxs("div", { className: "crisp-recordpage-feed-item", children: [_jsx("span", { className: "crisp-recordpage-feed-dot", children: _jsx(ActivityIcon, { size: 13, strokeWidth: 2 }) }), _jsxs("div", { className: "crisp-recordpage-feed-body", children: [a.text, _jsx("div", { className: "crisp-recordpage-feed-time", children: a.time })] })] }, a.id))) })] }) }));
|
|
819
|
+
case "files":
|
|
820
|
+
return (_jsx("div", { className: "crisp-recordpage-panel crisp-recordpage-panel--center", children: _jsxs("div", { className: "crisp-recordpage-illustrated", children: [_jsx("span", { className: "crisp-recordpage-illustrated-icon", "aria-hidden": "true", children: _jsx(Folder, { size: 40, strokeWidth: 1.25 }) }), _jsx("div", { className: "crisp-recordpage-illustrated-title", children: t.noFiles }), _jsx("div", { className: "crisp-recordpage-illustrated-desc", children: t.filesHint }), _jsxs("div", { className: "crisp-recordpage-illustrated-actions", children: [_jsxs("button", { type: "button", className: "crisp-recordpage-action", children: [_jsx(Plus, { size: 14, strokeWidth: 1.75 }), t.newFolder] }), _jsxs("button", { type: "button", className: "crisp-recordpage-action", children: [_jsx(Plus, { size: 14, strokeWidth: 1.75 }), t.uploadFile] }), _jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", "aria-label": t.fileMoreActions, children: _jsx(MoreHorizontal, { size: 16, strokeWidth: 1.75 }) })] })] }) }));
|
|
821
|
+
case "deals":
|
|
822
|
+
return (_jsx("div", { className: "crisp-recordpage-panel crisp-recordpage-panel--center", children: _jsxs("div", { className: "crisp-recordpage-illustrated", children: [_jsx("span", { className: "crisp-recordpage-illustrated-icon", "aria-hidden": "true", children: _jsx(Merge, { size: 40, strokeWidth: 1.25 }) }), _jsx("div", { className: "crisp-recordpage-illustrated-title", children: t.noDealsFound }), _jsx("div", { className: "crisp-recordpage-illustrated-desc", children: t.noDealsHint }), _jsx("div", { className: "crisp-recordpage-illustrated-actions", children: _jsxs(Button, { intent: "primary", size: "md", children: [_jsx(Plus, { size: 15, strokeWidth: 1.75 }), t.newDeal] }) })] }) }));
|
|
721
823
|
case "notes":
|
|
722
824
|
return (_jsx("div", { className: "crisp-recordpage-panel", children: _jsx(NotesSection, { notes: notes, onAdd: addNote, labels: t }) }));
|
|
723
825
|
case "tasks":
|
|
724
826
|
return (_jsx("div", { className: "crisp-recordpage-panel", children: _jsx(TasksSection, { tasks: tasks, onAdd: addTask, onToggle: toggleTask, labels: t }) }));
|
|
725
827
|
case "emails":
|
|
726
|
-
return _jsx("div", { className: "crisp-recordpage-panel", children:
|
|
828
|
+
return (_jsxs("div", { className: "crisp-recordpage-panel", children: [_jsx(SectionHead, { title: t.tabs.emails }), _jsx("div", { className: "crisp-recordpage-panel crisp-recordpage-panel--center", children: _jsxs("div", { className: "crisp-recordpage-illustrated", children: [_jsx("span", { className: "crisp-recordpage-illustrated-icon", "aria-hidden": "true", children: _jsx(Mail, { size: 40, strokeWidth: 1.25 }) }), _jsx("div", { className: "crisp-recordpage-illustrated-title", children: t.noEmails }), _jsx("div", { className: "crisp-recordpage-illustrated-desc", children: t.noEmailsHint }), _jsx("div", { className: "crisp-recordpage-illustrated-actions", children: _jsx(Button, { intent: "primary", size: "md", children: t.configureMailbox }) })] }) })] }));
|
|
829
|
+
case "calls":
|
|
830
|
+
return (_jsx("div", { className: "crisp-recordpage-panel crisp-recordpage-panel--center", children: _jsxs("div", { className: "crisp-recordpage-illustrated", children: [_jsx("span", { className: "crisp-recordpage-illustrated-icon", "aria-hidden": "true", children: _jsx(Phone, { size: 40, strokeWidth: 1.25 }) }), _jsx("div", { className: "crisp-recordpage-illustrated-title crisp-recordpage-illustrated-title--lg", children: t.upgradeToViewCalls }), _jsx("div", { className: "crisp-recordpage-illustrated-desc", children: t.callsUpgradeHint }), _jsx("div", { className: "crisp-recordpage-illustrated-actions", children: _jsxs(Button, { intent: "primary", size: "md", children: [_jsx(Zap, { size: 15, strokeWidth: 1.75 }), t.upgrade] }) })] }) }));
|
|
727
831
|
default:
|
|
728
832
|
return (_jsx("div", { className: "crisp-recordpage-panel", children: _jsx(EmptyCard, { icon: _jsx(Folder, { size: 22, strokeWidth: 1.5 }), children: t.nothingHereYet }) }));
|
|
729
833
|
}
|
|
@@ -741,7 +845,7 @@ export function RecordPage({ record, columns, collection = "Records", labels, on
|
|
|
741
845
|
const effectivePanel = tabsProp
|
|
742
846
|
? (active) => tabsProp.find((tb) => tb.id === active)?.render()
|
|
743
847
|
: builtinPanel;
|
|
744
|
-
return (_jsxs("div", { className: recordpageRecipe({ className }), ...rest, children: [_jsxs("div", { className: "crisp-recordpage-topbar", "data-peek": onClose ? "" : undefined, children: [onClose ? (_jsxs("div", { className: "crisp-recordpage-peekbar", children: [_jsx("button", { type: "button", className: "crisp-recordpage-peek-iconbtn", "aria-label": closeLabel, onClick: onClose, children: _jsx(X, { size: 16, strokeWidth: 1.75 }) }), (onPrevious || onNext) && (_jsxs("span", { className: "crisp-recordpage-peek-pager", children: [_jsx("button", { type: "button", className: "crisp-recordpage-peek-navbtn", "aria-label": previousRecordLabel, disabled: !onPrevious, onClick: onPrevious, children: _jsx(ChevronUp, { size: 16, strokeWidth: 1.75 }) }), _jsx("button", { type: "button", className: "crisp-recordpage-peek-navbtn", "aria-label": nextRecordLabel, disabled: !onNext, onClick: onNext, children: _jsx(ChevronDown, { size: 16, strokeWidth: 1.75 }) })] })), counterLabel != null && (_jsx("span", { className: "crisp-recordpage-peek-counter", children: counterLabel }))] })) : (_jsxs("nav", { className: "crisp-recordpage-crumbs", "aria-label": t.breadcrumb, children: [_jsx("button", { type: "button", className: "crisp-recordpage-crumb", onClick: onBack, "aria-label": `Back to ${collection}`, children: collection }), _jsx("span", { className: "crisp-recordpage-crumb-sep", children: "/" }), _jsx("span", { className: "crisp-recordpage-crumb-name", children: name })] })), _jsxs("div", { className: "crisp-recordpage-actions", children: [_jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", onClick: onShare, children: t.share }), _jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: () => setCommentsOpen(true), "aria-label": t.comments, children: _jsx(MessageSquare, { size: 15, strokeWidth: 1.75 }) }), _jsxs("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", children: [_jsx(Sparkles, { size: 14, strokeWidth: 1.75, color: "var(--crisp-bg-brand-solid)" }), t.askAttio] }), onToggleFull && (_jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: onToggleFull, "aria-label": full ? t.exitFullScreen : t.fullScreen, children: full ? (_jsx(Minimize2, { size: 15, strokeWidth: 1.75 })) : (_jsx(Maximize2, { size: 15, strokeWidth: 1.75 })) })), _jsxs(Menu, { align: "end", "aria-label": t.recordActions, trigger: _jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", "aria-label": t.recordActions, children: _jsx(MoreHorizontal, { size: 16, strokeWidth: 1.75 }) }), children: [_jsx(MenuItem, { icon: _jsx(Merge, { size: 15, strokeWidth: 1.75 }), children: t.mergeRecords }), _jsx(MenuItem, { icon: _jsx(Settings, { size: 15, strokeWidth: 1.75 }), children: t.configurePage }), _jsx(MenuItem, { icon: _jsx(Copy, { size: 15, strokeWidth: 1.75 }), children: t.copyPageUrl }), _jsx(MenuItem, { icon: _jsx(Copy, { size: 15, strokeWidth: 1.75 }), children: t.copyRecordId }), _jsx(MenuSeparator, {}), _jsx(MenuItem, { icon: _jsx(Star, { size: 15, strokeWidth: 1.75 }), onSelect: () => setFavorite((f) => !f), children: favorite ? t.removeFromFavourites : t.addToFavourites }), _jsx(MenuSeparator, {}), _jsx(MenuItem, { icon: _jsx(Trash2, { size: 15, strokeWidth: 1.75 }), danger: true, children: t.deleteRecord })] })] })] }), _jsxs("div", { className: "crisp-recordpage-body", children: [_jsxs("aside", { className: "crisp-recordpage-details", style: { width: detailsW }, children: [_jsxs("div", { className: "crisp-recordpage-entity", children: [entity?.avatar ? (_jsx("img", { className: "crisp-recordpage-entity-logo", src: entity.avatar, alt: "" })) : (_jsx(Avatar, { size: "lg", shape:
|
|
848
|
+
return (_jsxs("div", { className: recordpageRecipe({ className }), ...rest, children: [_jsxs("div", { className: "crisp-recordpage-topbar", "data-peek": onClose ? "" : undefined, children: [onClose ? (_jsxs("div", { className: "crisp-recordpage-peekbar", children: [_jsx("button", { type: "button", className: "crisp-recordpage-peek-iconbtn", "aria-label": closeLabel, onClick: onClose, children: _jsx(X, { size: 16, strokeWidth: 1.75 }) }), (onPrevious || onNext) && (_jsxs("span", { className: "crisp-recordpage-peek-pager", children: [_jsx("button", { type: "button", className: "crisp-recordpage-peek-navbtn", "aria-label": previousRecordLabel, disabled: !onPrevious, onClick: onPrevious, children: _jsx(ChevronUp, { size: 16, strokeWidth: 1.75 }) }), _jsx("button", { type: "button", className: "crisp-recordpage-peek-navbtn", "aria-label": nextRecordLabel, disabled: !onNext, onClick: onNext, children: _jsx(ChevronDown, { size: 16, strokeWidth: 1.75 }) })] })), counterLabel != null && (_jsx("span", { className: "crisp-recordpage-peek-counter", children: counterLabel }))] })) : (showBreadcrumb && (_jsxs("nav", { className: "crisp-recordpage-crumbs", "aria-label": t.breadcrumb, children: [_jsx("button", { type: "button", className: "crisp-recordpage-crumb", onClick: onBack, "aria-label": `Back to ${collection}`, children: collection }), _jsx("span", { className: "crisp-recordpage-crumb-sep", children: "/" }), _jsx("span", { className: "crisp-recordpage-crumb-name", children: name })] }))), _jsxs("div", { className: "crisp-recordpage-actions", children: [showShare && (_jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", onClick: onShare, children: t.share })), showComments && (_jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: () => setCommentsOpen(true), "aria-label": t.comments, children: _jsx(MessageSquare, { size: 15, strokeWidth: 1.75 }) })), showAskAttio && (_jsxs("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", children: [_jsx(Sparkles, { size: 14, strokeWidth: 1.75, color: "var(--crisp-bg-brand-solid)" }), t.askAttio] })), onToggleFull && (_jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: onToggleFull, "aria-label": full ? t.exitFullScreen : t.fullScreen, children: full ? (_jsx(Minimize2, { size: 15, strokeWidth: 1.75 })) : (_jsx(Maximize2, { size: 15, strokeWidth: 1.75 })) })), _jsxs(Menu, { align: "end", "aria-label": t.recordActions, trigger: _jsx("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", "aria-label": t.recordActions, children: _jsx(MoreHorizontal, { size: 16, strokeWidth: 1.75 }) }), children: [_jsx(MenuItem, { icon: _jsx(Merge, { size: 15, strokeWidth: 1.75 }), children: t.mergeRecords }), _jsx(MenuItem, { icon: _jsx(Settings, { size: 15, strokeWidth: 1.75 }), children: t.configurePage }), _jsx(MenuItem, { icon: _jsx(Copy, { size: 15, strokeWidth: 1.75 }), children: t.copyPageUrl }), _jsx(MenuItem, { icon: _jsx(Copy, { size: 15, strokeWidth: 1.75 }), children: t.copyRecordId }), _jsx(MenuSeparator, {}), _jsx(MenuItem, { icon: _jsx(Star, { size: 15, strokeWidth: 1.75 }), onSelect: () => setFavorite((f) => !f), children: favorite ? t.removeFromFavourites : t.addToFavourites }), actions && actions.length > 0 && (_jsxs(_Fragment, { children: [_jsx(MenuSeparator, {}), actions.map((a) => (_jsx(MenuItem, { icon: _jsx(a.icon, { size: 15, strokeWidth: 1.75 }), onSelect: a.onSelect, danger: a.danger, children: a.label }, a.label)))] })), _jsx(MenuSeparator, {}), _jsx(MenuItem, { icon: _jsx(Trash2, { size: 15, strokeWidth: 1.75 }), onSelect: onDelete, danger: true, children: t.deleteRecord })] })] })] }), _jsxs("div", { className: "crisp-recordpage-body", children: [_jsxs("aside", { className: "crisp-recordpage-details", style: { width: detailsW }, children: [_jsxs("div", { className: "crisp-recordpage-entity", children: [entity?.avatar ? (_jsx("img", { className: "crisp-recordpage-entity-logo", src: entity.avatar, alt: "" })) : (_jsx(Avatar, { size: "lg", shape: avatarShape, tone: "brand", children: name.charAt(0) })), _jsx("span", { className: "crisp-recordpage-entity-name", children: name }), _jsx("button", { type: "button", className: "crisp-recordpage-entity-iconbtn", "aria-label": t.editRecord, onClick: onEditRecord, children: _jsx(Pencil, { size: 15, strokeWidth: 1.75 }) }), _jsx("button", { type: "button", className: "crisp-recordpage-entity-iconbtn", "data-active": favorite ? "" : undefined, "aria-pressed": favorite, onClick: () => setFavorite((f) => !f), "aria-label": favorite ? t.removeFromFavourites : t.addToFavourites, children: _jsx(Star, { size: 15, strokeWidth: 1.75 }) })] }), _jsxs("div", { className: "crisp-recordpage-quickactions", children: [_jsxs("button", { type: "button", className: "crisp-recordpage-compose", children: [_jsx(Mail, { size: 16, strokeWidth: 1.75 }), t.composeEmail] }), _jsxs("button", { type: "button", className: "crisp-recordpage-compose", children: [_jsx(Plus, { size: 16, strokeWidth: 1.75 }), t.addToList] }), quickActions.map((a, i) => (_jsx("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": a.label, onClick: a.onSelect, children: _jsx(a.icon, { size: 15, strokeWidth: 1.75 }) }, i)))] }), _jsx(RecordDetails, { record: record, columns: columns, sections: sections, onEdit: onEdit, labels: labels }), _jsxs("div", { className: "crisp-recordpage-section", children: [_jsx(SectionHead, { title: t.tabs.lists, action: _jsxs("button", { type: "button", className: "crisp-recordpage-addbtn", children: [_jsx(Plus, { size: 14, strokeWidth: 2 }), t.addToList] }) }), _jsx("div", { className: "crisp-recordpage-emptystate", children: t.noLists })] })] }), _jsx("div", { className: "crisp-recordpage-resizer", "data-active": resizing ? "" : undefined, role: "separator", "aria-orientation": "vertical", "aria-label": t.resizeDetails, "aria-valuenow": Math.round(detailsW), "aria-valuemin": DETAILS_MIN_W, "aria-valuemax": DETAILS_MAX_W, tabIndex: 0, onPointerDown: onResizeDown, onPointerMove: onResizeMove, onPointerUp: onResizeUp, onKeyDown: (e) => {
|
|
745
849
|
if (e.key !== "ArrowLeft" && e.key !== "ArrowRight")
|
|
746
850
|
return;
|
|
747
851
|
// The pointer drag learned its direction in #212; this did not.
|
|
@@ -753,6 +857,6 @@ export function RecordPage({ record, columns, collection = "Records", labels, on
|
|
|
753
857
|
setDetailsW((w) => grow
|
|
754
858
|
? Math.min(DETAILS_MAX_W, w + 16)
|
|
755
859
|
: Math.max(DETAILS_MIN_W, w - 16));
|
|
756
|
-
} }), _jsx(
|
|
860
|
+
} }), _jsx(RecordPageMain, { children: _jsx(Tabs, { tabs: effectiveTabs, defaultValue: tabsProp ? defaultTab : "overview", "aria-label": `${name} record`, children: effectivePanel }) })] }), _jsx(Drawer, { open: commentsOpen, onOpenChange: setCommentsOpen, "aria-label": t.comments, style: { width: 320 }, children: _jsx(CommentsPanel, { threads: comments, currentUserId: me.id, onPost: postComment, onClose: () => setCommentsOpen(false), role: undefined }) }), _jsx(WorkflowPopover, { open: workflowPopoverOpen, onClose: () => setWorkflowPopoverOpen(false), labels: t })] }));
|
|
757
861
|
}
|
|
758
862
|
RecordPage.displayName = "RecordPage";
|