@crisp-ui-kit/crisp 0.57.2 → 0.57.3
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/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 +28 -0
- package/dist/cjs/components/chip/chip.recipe.js +3 -1
- package/dist/cjs/components/datatable/datatable.recipe.js +10 -4
- 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 +18 -2
- package/dist/cjs/components/table/table.recipe.js +7 -1
- package/dist/cjs/core/floating-portal.js +7 -4
- package/dist/cjs/product/inbox/inbox.js +11 -3
- package/dist/cjs/product/recordpage/index.js +2 -1
- package/dist/cjs/product/recordpage/recordpage.js +49 -3
- package/dist/cjs/product/recordpage/recordpage.recipe.js +25 -0
- package/dist/datatable.css +10 -4
- package/dist/datatable.layered.css +10 -4
- 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 +28 -0
- package/dist/esm/components/chip/chip.recipe.js +3 -1
- package/dist/esm/components/datatable/datatable.recipe.js +10 -4
- 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 +18 -2
- package/dist/esm/components/table/table.recipe.js +7 -1
- package/dist/esm/core/floating-portal.js +7 -4
- package/dist/esm/product/inbox/inbox.js +11 -3
- package/dist/esm/product/recordpage/index.js +1 -1
- package/dist/esm/product/recordpage/recordpage.js +49 -4
- package/dist/esm/product/recordpage/recordpage.recipe.js +25 -0
- package/dist/recordpage.css +23 -0
- package/dist/recordpage.layered.css +23 -0
- package/dist/styles.css +52 -9
- package/dist/styles.layered.css +52 -9
- package/dist/table.css +7 -2
- package/dist/table.layered.css +7 -2
- 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 +13 -0
- package/dist/types/components/chip/chip.recipe.d.ts +1 -1
- package/dist/types/components/datepickerfield/datepickerfield.d.ts +4 -1
- package/dist/types/components/datepickerfield/datepickerfield.recipe.d.ts +18 -3
- package/dist/types/core/floating-portal.d.ts +6 -5
- package/dist/types/product/inbox/inbox.d.ts +13 -1
- package/dist/types/product/recordpage/index.d.ts +1 -1
- package/dist/types/product/recordpage/recordpage.d.ts +46 -0
- package/package.json +1 -1
package/dist/button.css
CHANGED
|
@@ -24,6 +24,9 @@
|
|
|
24
24
|
.crisp-button--size_sm {
|
|
25
25
|
height: 24px; padding-inline: var(--crisp-space-1-5); font-size: var(--crisp-text-md); border-radius: var(--crisp-radius-md);
|
|
26
26
|
}
|
|
27
|
+
.crisp-button--size_xs {
|
|
28
|
+
height: 20px; padding-inline: var(--crisp-space-1-5); font-size: 10px; font-weight: var(--crisp-weight-regular); line-height: 15px; letter-spacing: normal; border-radius: var(--crisp-radius-sm); gap: var(--crisp-space-1);
|
|
29
|
+
}
|
|
27
30
|
.crisp-button--size_md {
|
|
28
31
|
height: 28px; padding-inline: var(--crisp-space-2); font-size: var(--crisp-text-lg); border-radius: var(--crisp-radius-md);
|
|
29
32
|
&[data-icon-start] { padding-inline-start: var(--crisp-space-1-5); }
|
package/dist/button.d.ts
CHANGED
|
@@ -2,9 +2,9 @@ import type { RecipeFn } from "../core/types";
|
|
|
2
2
|
|
|
3
3
|
export interface ButtonVariants {
|
|
4
4
|
intent?: "primary" | "neutral" | "danger" | "ghost";
|
|
5
|
-
size?: "sm" | "md" | "lg";
|
|
5
|
+
size?: "sm" | "xs" | "md" | "lg";
|
|
6
6
|
shape?: "text" | "icon";
|
|
7
7
|
touchTarget?: boolean;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export declare const buttonRecipe: RecipeFn<{ intent: { primary: string; neutral: string; danger: string; ghost: string }; size: { sm: string; md: string; lg: string }; shape: { text: string; icon: string }; touchTarget: { true: string; false: string } }>;
|
|
10
|
+
export declare const buttonRecipe: RecipeFn<{ intent: { primary: string; neutral: string; danger: string; ghost: string }; size: { sm: string; xs: string; md: string; lg: string }; shape: { text: string; icon: string }; touchTarget: { true: string; false: string } }>;
|
package/dist/button.layered.css
CHANGED
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
.crisp-button--size_sm {
|
|
26
26
|
height: 24px; padding-inline: var(--crisp-space-1-5); font-size: var(--crisp-text-md); border-radius: var(--crisp-radius-md);
|
|
27
27
|
}
|
|
28
|
+
.crisp-button--size_xs {
|
|
29
|
+
height: 20px; padding-inline: var(--crisp-space-1-5); font-size: 10px; font-weight: var(--crisp-weight-regular); line-height: 15px; letter-spacing: normal; border-radius: var(--crisp-radius-sm); gap: var(--crisp-space-1);
|
|
30
|
+
}
|
|
28
31
|
.crisp-button--size_md {
|
|
29
32
|
height: 28px; padding-inline: var(--crisp-space-2); font-size: var(--crisp-text-lg); border-radius: var(--crisp-radius-md);
|
|
30
33
|
&[data-icon-start] { padding-inline-start: var(--crisp-space-1-5); }
|
package/dist/chip.css
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
display: inline-flex; align-items: center; gap: 3px; height: var(--crisp-size-chip); min-width: 0; max-width: 100%; padding-inline: var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap;
|
|
3
3
|
}
|
|
4
4
|
.crisp-chip--truncate_true {
|
|
5
|
-
& .crisp-chip-label { min-width: 0; overflow:
|
|
5
|
+
& .crisp-chip-label { min-width: 0; overflow: clip; text-overflow: ellipsis; white-space: nowrap; display: block; }
|
|
6
6
|
}
|
|
7
7
|
.crisp-chip--truncate_false {
|
|
8
8
|
|
package/dist/chip.layered.css
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
display: inline-flex; align-items: center; gap: 3px; height: var(--crisp-size-chip); min-width: 0; max-width: 100%; padding-inline: var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap;
|
|
4
4
|
}
|
|
5
5
|
.crisp-chip--truncate_true {
|
|
6
|
-
& .crisp-chip-label { min-width: 0; overflow:
|
|
6
|
+
& .crisp-chip-label { min-width: 0; overflow: clip; text-overflow: ellipsis; white-space: nowrap; display: block; }
|
|
7
7
|
}
|
|
8
8
|
.crisp-chip--truncate_false {
|
|
9
9
|
|
|
@@ -55,7 +55,7 @@ function Breadcrumb({ items, separator, "aria-label": ariaLabelProp, className,
|
|
|
55
55
|
return ((0, jsx_runtime_1.jsx)("nav", { "aria-label": ariaLabel, className: (0, breadcrumb_recipe_1.breadcrumbRecipe)({ className }), ...rest, children: items.map((item, i) => {
|
|
56
56
|
const last = i === items.length - 1;
|
|
57
57
|
const content = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [item.icon, item.label] }));
|
|
58
|
-
return ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [last ? ((0, jsx_runtime_1.jsx)("span", { className: "crisp-breadcrumb-item", "aria-current": "page", children: content })) : item.href ? ((0, jsx_runtime_1.jsx)("a", { className: "crisp-breadcrumb-item", href: item.href, children: content })) : ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-breadcrumb-item", onClick: item.onClick, children: content })), !last && (0, jsx_runtime_1.jsx)("span", { className: "crisp-breadcrumb-sep", children: sep })] }, i));
|
|
58
|
+
return ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [last ? ((0, jsx_runtime_1.jsx)("span", { className: "crisp-breadcrumb-item", "aria-current": "page", children: content })) : item.href ? ((0, jsx_runtime_1.jsx)("a", { className: "crisp-breadcrumb-item", href: item.href, onClick: item.onClick, children: content })) : ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-breadcrumb-item", onClick: item.onClick, children: content })), !last && (0, jsx_runtime_1.jsx)("span", { className: "crisp-breadcrumb-sep", children: sep })] }, i));
|
|
59
59
|
}) }));
|
|
60
60
|
}
|
|
61
61
|
Breadcrumb.displayName = "Breadcrumb";
|
|
@@ -124,6 +124,34 @@ const spec = (0, core_1.defineSpec)({
|
|
|
124
124
|
fontSize: tokens_1.contract.text.md,
|
|
125
125
|
borderRadius: tokens_1.contract.radius.md,
|
|
126
126
|
},
|
|
127
|
+
// crisp#688: Attio's own 20px small button, split out of #682 rather
|
|
128
|
+
// than redefining `sm` -- 72 existing `size="sm"` call sites (sidebar,
|
|
129
|
+
// comments, recordpage, workspacegeneralsettings, personchip,
|
|
130
|
+
// datatable) already rely on sm's 24px, unverified against `xs`'s own
|
|
131
|
+
// context. Box measured live on app.attio.com Home/Tasks ("View all",
|
|
132
|
+
// "Today, <date>", the date-stepper's Previous/Next-day icon
|
|
133
|
+
// buttons): 20px height, 6px radius, 6px inline padding on text
|
|
134
|
+
// buttons (a 20x20 icon-only button centers a 16px icon the same way
|
|
135
|
+
// shape="icon" already does for every other size, so it needs no
|
|
136
|
+
// extra rule), 4px gap between multiple children.
|
|
137
|
+
//
|
|
138
|
+
// The label itself is NOT 14px/#682's tracking -- getComputedStyle on
|
|
139
|
+
// the live button (fonts loaded, DPR 2, zero zoom/transform anywhere
|
|
140
|
+
// in the ancestor chain) reads 10px/regular/15px line-height/normal
|
|
141
|
+
// tracking, independently matching `recordpanel.recipe.ts`'s own
|
|
142
|
+
// "View all" measurement (10px/400) elsewhere in this codebase. 10px
|
|
143
|
+
// sits below the text scale's 11px floor, so it's a literal here, the
|
|
144
|
+
// same as recordpanel's.
|
|
145
|
+
xs: {
|
|
146
|
+
height: "20px",
|
|
147
|
+
paddingInline: tokens_1.contract.space["1.5"],
|
|
148
|
+
fontSize: "10px",
|
|
149
|
+
fontWeight: tokens_1.contract.weight.regular,
|
|
150
|
+
lineHeight: "15px",
|
|
151
|
+
letterSpacing: "normal",
|
|
152
|
+
borderRadius: tokens_1.contract.radius.sm,
|
|
153
|
+
gap: tokens_1.contract.space["1"],
|
|
154
|
+
},
|
|
127
155
|
md: {
|
|
128
156
|
decls: {
|
|
129
157
|
height: "28px",
|
|
@@ -76,7 +76,9 @@ const spec = (0, core_1.defineSpec)({
|
|
|
76
76
|
selector: "& .crisp-chip-label",
|
|
77
77
|
decls: {
|
|
78
78
|
minWidth: 0,
|
|
79
|
-
|
|
79
|
+
// #719: `clip`, not `hidden` -- immune to a programmatic
|
|
80
|
+
// `scrollLeft` assignment; renders the ellipsis identically.
|
|
81
|
+
overflow: "clip",
|
|
80
82
|
textOverflow: "ellipsis",
|
|
81
83
|
whiteSpace: "nowrap",
|
|
82
84
|
display: "block",
|
|
@@ -116,7 +116,13 @@ const config = (0, core_1.defineRecipe)({
|
|
|
116
116
|
& .crisp-datatable-group-count { margin-inline-start: ${tokens_1.contract.space["1.5"]}; color: ${tokens_1.contract.fgMuted}; font-variant-numeric: tabular-nums; }
|
|
117
117
|
& .crisp-datatable-th-rename { width: 100%; min-width: 0; height: ${tokens_1.contract.size.row}; margin: 0; padding: 0 ${tokens_1.contract.space["1"]}; border: none; border-radius: ${tokens_1.contract.radius.md}; background: ${tokens_1.contract.bgSurface}; box-shadow: inset 0 0 0 1px ${tokens_1.contract.bgBrandSolid}; font-family: inherit; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgPrimary}; outline: none; }
|
|
118
118
|
& .crisp-datatable-th-rename:focus { box-shadow: inset 0 0 0 1px ${tokens_1.contract.bgBrandSolid}; }
|
|
119
|
-
|
|
119
|
+
/* #719: overflow: clip, not hidden, on every truncation rule below --
|
|
120
|
+
hidden still honours a programmatic scrollLeft assignment (found via
|
|
121
|
+
a screenshot tool that pans anything with scrollWidth > clientWidth),
|
|
122
|
+
blanking or fragmenting the visible text even though real user
|
|
123
|
+
scrolling was already blocked. clip renders the ellipsis identically
|
|
124
|
+
and leaves no scroll position to move. */
|
|
125
|
+
& .crisp-datatable-th-label { overflow: clip; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
120
126
|
/* #465: 12px per the comment above (and Attio's measured header cell
|
|
121
127
|
padding: 0 12px) -- was still c.space["2"] (8px), drifted from its
|
|
122
128
|
own documented value. */
|
|
@@ -203,7 +209,7 @@ const config = (0, core_1.defineRecipe)({
|
|
|
203
209
|
/* reference picker: a leading 16px avatar per option, plus a trailing
|
|
204
210
|
"Create X" row when the consumer supplies one (#449) -- same measured
|
|
205
211
|
values as RecordsTable's own .crisp-recordstable-refoption*. */
|
|
206
|
-
& .crisp-datatable-refoption { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; min-width: 0; overflow:
|
|
212
|
+
& .crisp-datatable-refoption { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; min-width: 0; overflow: clip; text-overflow: ellipsis; }
|
|
207
213
|
& .crisp-datatable-refoption-avatar { flex-shrink: 0; }
|
|
208
214
|
& .crisp-datatable-refoption--create { color: ${tokens_1.contract.fgMuted}; }
|
|
209
215
|
& .crisp-datatable-empty { padding: ${tokens_1.contract.space["8"]} ${tokens_1.contract.space["3"]}; text-align: center; color: ${tokens_1.contract.fgMuted}; font-size: ${tokens_1.contract.text.lg}; }
|
|
@@ -289,7 +295,7 @@ const config = (0, core_1.defineRecipe)({
|
|
|
289
295
|
(#833/#835/#848) that happened to apply the same 500 to every
|
|
290
296
|
primary-column body cell regardless of type. Made explicit here so
|
|
291
297
|
fixing that leak doesn't also (wrongly) drop entity names to 400. */
|
|
292
|
-
& .crisp-datatable-entity-name { overflow:
|
|
298
|
+
& .crisp-datatable-entity-name { overflow: clip; text-overflow: ellipsis; white-space: nowrap; font-weight: ${tokens_1.contract.weight.medium}; text-decoration: underline; text-decoration-color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 9%, transparent); }
|
|
293
299
|
/* #893: shared by the button (onEntityClick, a different record) and
|
|
294
300
|
the anchor (href, the row's own record) -- was button-only, which
|
|
295
301
|
silently left a real <a> unstyled the day #893 added one. */
|
|
@@ -335,7 +341,7 @@ const config = (0, core_1.defineRecipe)({
|
|
|
335
341
|
own .crisp-recordstable-link, which this mirrors: no underline at
|
|
336
342
|
rest, underline on hover, own-colour text rather than a browser-blue
|
|
337
343
|
link. */
|
|
338
|
-
& .crisp-datatable-link { color: ${tokens_1.contract.fgPrimary}; text-decoration: none; overflow:
|
|
344
|
+
& .crisp-datatable-link { color: ${tokens_1.contract.fgPrimary}; text-decoration: none; overflow: clip; text-overflow: ellipsis; }
|
|
339
345
|
& .crisp-datatable-link:hover { text-decoration: underline; }
|
|
340
346
|
/* phone cell type/editor (#765) -- measured live: the dial-code prefix
|
|
341
347
|
is a muted rgba(0,0,0,0.63) at 14px, matching every other muted-text
|
|
@@ -58,7 +58,7 @@ const fromDate = (d) => ({
|
|
|
58
58
|
* popover. This is what `DatePicker` used to be; `DatePicker` is now
|
|
59
59
|
* seed-design's bare calendar, and this composes it.
|
|
60
60
|
*/
|
|
61
|
-
function DatePickerField({ value, defaultValue, onValueChange, onChange, placeholder: placeholderProp, variant = "default", format = defaultFormat, clearable = true, clearLabel: clearLabelProp, presets, calendarProps, "aria-label": ariaLabel, className, ...rest }) {
|
|
61
|
+
function DatePickerField({ value, defaultValue, onValueChange, onChange, placeholder: placeholderProp, variant = "default", width = "full", format = defaultFormat, clearable = true, clearLabel: clearLabelProp, presets, calendarProps, "aria-label": ariaLabel, className, ...rest }) {
|
|
62
62
|
// crisp/#471: same resolution order as Select's own.
|
|
63
63
|
const placeholder = (0, core_1.useCrispMessage)(placeholderProp, "Set date…");
|
|
64
64
|
const clearLabel = (0, core_1.useCrispMessage)(clearLabelProp, "Clear date");
|
|
@@ -79,7 +79,7 @@ function DatePickerField({ value, defaultValue, onValueChange, onChange, placeho
|
|
|
79
79
|
onValueChange?.(null);
|
|
80
80
|
onChange?.(null);
|
|
81
81
|
};
|
|
82
|
-
return ((0, jsx_runtime_1.jsx)("div", { className: (0, datepickerfield_recipe_1.datepickerfieldRecipe)({ className }), ...rest, children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-datepickerfield-box", "data-ghost": variant === "ghost" ? "" : undefined, "data-placeholder": selected ? undefined : "", children: [(0, jsx_runtime_1.jsx)(popover_1.Popover, { open: open, onOpenChange: setOpen, "aria-label": ariaLabel, trigger: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-datepickerfield-trigger", "aria-label": ariaLabel, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CalendarDays, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), selected ? format(selected) : placeholder] }), children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-datepickerfield-panel", children: [(0, jsx_runtime_1.jsx)(datepicker_1.DatePicker
|
|
82
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: (0, datepickerfield_recipe_1.datepickerfieldRecipe)({ width, className }), ...rest, children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-datepickerfield-box", "data-ghost": variant === "ghost" ? "" : undefined, "data-placeholder": selected ? undefined : "", children: [(0, jsx_runtime_1.jsx)(popover_1.Popover, { open: open, onOpenChange: setOpen, "aria-label": ariaLabel, trigger: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-datepickerfield-trigger", "aria-label": ariaLabel, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CalendarDays, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), selected ? format(selected) : placeholder] }), children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-datepickerfield-panel", children: [(0, jsx_runtime_1.jsx)(datepicker_1.DatePicker
|
|
83
83
|
// DatePickerProps is a union over selectionMode, and TypeScript
|
|
84
84
|
// cannot narrow it through a spread: it tries every branch and
|
|
85
85
|
// reports the range one as missing props. The field is always
|
|
@@ -13,7 +13,7 @@ const config = (0, core_1.defineRecipe)({
|
|
|
13
13
|
display: inline-flex; font-family: ${tokens_1.contract.font.sans};
|
|
14
14
|
|
|
15
15
|
& .crisp-datepickerfield-box {
|
|
16
|
-
display: inline-flex; align-items: center;
|
|
16
|
+
display: inline-flex; align-items: center; width: 100%;
|
|
17
17
|
box-sizing: border-box; height: 32px; margin: 0;
|
|
18
18
|
border-radius: ${tokens_1.contract.radius.md}; background: transparent;
|
|
19
19
|
box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.fgPrimary} 7%, transparent);
|
|
@@ -22,6 +22,7 @@ const config = (0, core_1.defineRecipe)({
|
|
|
22
22
|
& .crisp-datepickerfield-box[data-ghost] { background: transparent; box-shadow: none; }
|
|
23
23
|
& .crisp-datepickerfield-box[data-ghost]:hover { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 4%, transparent); }
|
|
24
24
|
& .crisp-datepickerfield-trigger {
|
|
25
|
+
flex: 1; min-width: 0;
|
|
25
26
|
display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["2"]};
|
|
26
27
|
height: 100%; padding: 0 ${tokens_1.contract.space["2.5"]}; margin: 0;
|
|
27
28
|
border: none; background: none; color: inherit; font-family: inherit;
|
|
@@ -51,8 +52,20 @@ const config = (0, core_1.defineRecipe)({
|
|
|
51
52
|
}
|
|
52
53
|
& .crisp-datepickerfield-preset:hover { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 8%, transparent); }
|
|
53
54
|
`,
|
|
54
|
-
variants: {
|
|
55
|
-
|
|
55
|
+
variants: {
|
|
56
|
+
// crisp#980: full (default) fills its container, matching Select's own
|
|
57
|
+
// width variant/default -- so a form composing DatePickerField alongside
|
|
58
|
+
// Input/Select via FormField gets one consistent column width across
|
|
59
|
+
// every field type, not just some of them. auto sizes to content, the
|
|
60
|
+
// behaviour every consumer got unconditionally before this.
|
|
61
|
+
width: {
|
|
62
|
+
full: `width: 100%;`,
|
|
63
|
+
auto: `width: auto;`,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
defaultVariants: {
|
|
67
|
+
width: "full",
|
|
68
|
+
},
|
|
56
69
|
compoundVariants: [],
|
|
57
70
|
});
|
|
58
71
|
exports.datepickerfieldRecipe = Object.assign((0, core_1.resolveRecipe)(config), {
|
|
@@ -144,12 +144,28 @@ function Menu({ trigger, children, align = "start", open: openProp, defaultOpen,
|
|
|
144
144
|
document.addEventListener("keydown", onKey);
|
|
145
145
|
return () => document.removeEventListener("keydown", onKey);
|
|
146
146
|
}, [open, close]);
|
|
147
|
-
const { onKeyDown: onContentKeyDown } = (0, menu_keyboard_1.useMenuKeyboard)({
|
|
147
|
+
const { onKeyDown: onContentKeyDown, items } = (0, menu_keyboard_1.useMenuKeyboard)({
|
|
148
148
|
open,
|
|
149
149
|
close,
|
|
150
150
|
dismiss: () => setOpen(false),
|
|
151
151
|
contentRef,
|
|
152
152
|
});
|
|
153
|
+
// Roving tabindex (crisp#964): every item hardcodes tabIndex={-1} for the
|
|
154
|
+
// arrow-key model above, which means none of them is ever a real Tab stop
|
|
155
|
+
// on its own -- kept in sync with wherever focus actually lands, same as
|
|
156
|
+
// MenuSheet's identical fix (#966).
|
|
157
|
+
const onContentFocus = React.useCallback((e) => {
|
|
158
|
+
for (const el of items())
|
|
159
|
+
el.tabIndex = -1;
|
|
160
|
+
e.target.tabIndex = 0;
|
|
161
|
+
}, [items]);
|
|
162
|
+
React.useEffect(() => {
|
|
163
|
+
if (!open)
|
|
164
|
+
return;
|
|
165
|
+
const [first] = items();
|
|
166
|
+
if (first)
|
|
167
|
+
first.tabIndex = 0;
|
|
168
|
+
}, [open, items]);
|
|
153
169
|
const triggerEl = React.cloneElement(trigger, {
|
|
154
170
|
ref: triggerRef,
|
|
155
171
|
id: triggerId,
|
|
@@ -165,7 +181,7 @@ function Menu({ trigger, children, align = "start", open: openProp, defaultOpen,
|
|
|
165
181
|
});
|
|
166
182
|
return ((0, jsx_runtime_1.jsxs)("div", { ref: rootRef, className: (0, menu_recipe_1.menuRecipe)({ className }), children: [triggerEl, open && ((0, jsx_runtime_1.jsx)(core_1.FloatingPortal, { sourceRef: rootRef, children: (0, jsx_runtime_1.jsx)("div", { ref: contentRef, className: contentClassName
|
|
167
183
|
? `crisp-menu-content ${contentClassName}`
|
|
168
|
-
: "crisp-menu-content", role: "menu", id: surfaceId, "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : triggerId, "data-align": align, "data-up": up ? "" : undefined, style: pos, onKeyDown: onContentKeyDown, children: (0, jsx_runtime_1.jsx)(MenuContext.Provider, { value: { close }, children: children }) }) }))] }));
|
|
184
|
+
: "crisp-menu-content", role: "menu", id: surfaceId, "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : triggerId, "data-align": align, "data-up": up ? "" : undefined, style: pos, onKeyDown: onContentKeyDown, onFocus: onContentFocus, children: (0, jsx_runtime_1.jsx)(MenuContext.Provider, { value: { close }, children: children }) }) }))] }));
|
|
169
185
|
}
|
|
170
186
|
Menu.displayName = "Menu";
|
|
171
187
|
function MenuItem({ children, icon, shortcut, count, danger, disabled, onSelect, closeOnSelect = true, }) {
|
|
@@ -24,7 +24,13 @@ const config = (0, core_1.defineRecipe)({
|
|
|
24
24
|
(padding: 0 12px) and independently on a body cell's text inset
|
|
25
25
|
(getBoundingClientRect delta from the cell edge). Matches
|
|
26
26
|
ColumnCalc's own already-correct 12px trigger inset. */
|
|
27
|
-
|
|
27
|
+
/* #719: overflow: clip, not hidden -- both render the ellipsis
|
|
28
|
+
identically, but hidden still honours a programmatic scrollLeft
|
|
29
|
+
assignment (a screenshot tool, a browser extension) even though it
|
|
30
|
+
already blocks user-driven wheel/scrollbar input; clip leaves no
|
|
31
|
+
scroll position for anything to move, so the truncated text can
|
|
32
|
+
never be panned out of view. */
|
|
33
|
+
& th, & td { position: relative; height: ${tokens_1.contract.size.tableRow}; padding: 0 ${tokens_1.contract.space["3"]}; border-bottom: 1px solid ${tokens_1.contract.borderSubtle}; border-inline-end: 1px solid ${tokens_1.contract.borderSubtle}; text-align: start; vertical-align: middle; white-space: nowrap; overflow: clip; text-overflow: ellipsis; }
|
|
28
34
|
/* #554: sticky on the vertical axis, mirroring RecordsTable's own
|
|
29
35
|
\`.crisp-recordstable-th\`. z-index 2 sits above a plain body cell (auto)
|
|
30
36
|
and below a sticky-column corner cell (DataTable's \`th[data-sticky]\`,
|
|
@@ -52,10 +52,11 @@ const react_dom_1 = require("react-dom");
|
|
|
52
52
|
* `Dialog` was rendering offset by the dialog's own box instead of anchored
|
|
53
53
|
* to its trigger.
|
|
54
54
|
*
|
|
55
|
-
* Every caller renders this conditionally on its own `open` state
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
55
|
+
* Every caller renders this conditionally on its own `open` state. A fixture
|
|
56
|
+
* CAN set that true at mount (crisp#964 needs exactly that, to give Menu's
|
|
57
|
+
* roving-tabindex guard something to inspect), so this checks for `window`
|
|
58
|
+
* itself rather than assuming the caller never does — the same guard
|
|
59
|
+
* `@zag-js/react`'s own `Portal` uses for the identical reason.
|
|
59
60
|
*
|
|
60
61
|
* `sourceRef` is the caller's own root — still in its original DOM position,
|
|
61
62
|
* never portaled — used to carry a `data-theme` set on some ancestor of it
|
|
@@ -71,6 +72,8 @@ const react_dom_1 = require("react-dom");
|
|
|
71
72
|
* element carrying that attribute.
|
|
72
73
|
*/
|
|
73
74
|
function FloatingPortal({ children, sourceRef, }) {
|
|
75
|
+
if (typeof window === "undefined")
|
|
76
|
+
return children;
|
|
74
77
|
const theme = sourceRef.current
|
|
75
78
|
?.closest("[data-theme]")
|
|
76
79
|
?.getAttribute("data-theme");
|
|
@@ -54,10 +54,18 @@ function Inbox({ notifications = [], title: titleProp, tabs, activeTab, onTabCha
|
|
|
54
54
|
// own `empty`.
|
|
55
55
|
const crispMessages = (0, core_1.useCrispMessages)();
|
|
56
56
|
const title = titleProp ?? crispMessages.Notifications ?? "Notifications";
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
// A tab's own empty copy wins over the instance-level prop, which wins
|
|
58
|
+
// over the built-in default — same precedence as `title`/`emptyAction`.
|
|
59
|
+
const activeTabDef = tabs?.find((t) => t.id === activeTab);
|
|
60
|
+
const emptyTitle = activeTabDef?.emptyTitle ??
|
|
61
|
+
emptyTitleProp ??
|
|
62
|
+
crispMessages["No notifications"] ??
|
|
63
|
+
"No notifications";
|
|
64
|
+
const emptySubtitle = activeTabDef?.emptySubtitle ??
|
|
65
|
+
emptySubtitleProp ??
|
|
59
66
|
crispMessages["Notifications such as mentions and important updates will be displayed here."] ??
|
|
60
67
|
"Notifications such as mentions and important updates will be displayed here.";
|
|
68
|
+
const resolvedEmptyAction = activeTabDef?.emptyAction ?? emptyAction;
|
|
61
69
|
const tabsLabel = (0, core_1.useCrispMessage)(tabsLabelProp, "Notification views");
|
|
62
70
|
// crisp's shared tablist model, the one `Tabs` runs (#195). Hand-rolling a
|
|
63
71
|
// second one is how Inbox ended up with no arrow keys, no roving tabindex
|
|
@@ -68,6 +76,6 @@ function Inbox({ notifications = [], title: titleProp, tabs, activeTab, onTabCha
|
|
|
68
76
|
select: (id) => onTabChange?.(id),
|
|
69
77
|
listRef,
|
|
70
78
|
});
|
|
71
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, inbox_recipe_1.inboxRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-inbox-head", children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-inbox-title", children: title }), tabs && tabs.length > 0 && ((0, jsx_runtime_1.jsx)("div", { ref: listRef, className: "crisp-inbox-tabs", role: "tablist", "aria-label": tabsLabel, onKeyDown: onKeyDown, children: tabs.map((t) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", role: "tab", "aria-selected": t.id === activeTab, tabIndex: tabIndexFor(t.id), "data-value": t.id, disabled: t.disabled, className: "crisp-inbox-tab", "data-active": t.id === activeTab ? "" : undefined, onClick: () => onTabChange?.(t.id), children: [t.label, t.count != null && ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-inbox-tab-count", children: ["(", t.count, ")"] }))] }, t.id))) }))] }), notifications.length === 0 ? ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-inbox-empty", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-inbox-empty-icon", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Bell, { size: 40, strokeWidth: 1.25 }) }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-inbox-empty-title", children: emptyTitle }), emptySubtitle != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-inbox-empty-sub", children: emptySubtitle })),
|
|
79
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, inbox_recipe_1.inboxRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-inbox-head", children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-inbox-title", children: title }), tabs && tabs.length > 0 && ((0, jsx_runtime_1.jsx)("div", { ref: listRef, className: "crisp-inbox-tabs", role: "tablist", "aria-label": tabsLabel, onKeyDown: onKeyDown, children: tabs.map((t) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", role: "tab", "aria-selected": t.id === activeTab, tabIndex: tabIndexFor(t.id), "data-value": t.id, disabled: t.disabled, className: "crisp-inbox-tab", "data-active": t.id === activeTab ? "" : undefined, onClick: () => onTabChange?.(t.id), children: [t.label, t.count != null && ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-inbox-tab-count", children: ["(", t.count, ")"] }))] }, t.id))) }))] }), notifications.length === 0 ? ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-inbox-empty", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-inbox-empty-icon", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Bell, { size: 40, strokeWidth: 1.25 }) }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-inbox-empty-title", children: emptyTitle }), emptySubtitle != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-inbox-empty-sub", children: emptySubtitle })), resolvedEmptyAction != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-inbox-empty-action", children: resolvedEmptyAction }))] })) : ((0, jsx_runtime_1.jsx)("div", { className: "crisp-inbox-list", children: notifications.map((n) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-inbox-row", onClick: n.onClick, children: [n.avatar != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-inbox-row-avatar", children: n.avatar })), (0, jsx_runtime_1.jsxs)("span", { className: "crisp-inbox-row-body", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-inbox-row-msg", children: n.message }), n.time != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-inbox-row-time", children: n.time }))] }), n.unread && (0, jsx_runtime_1.jsx)("span", { className: "crisp-inbox-row-unread" })] }, n.id))) }))] }));
|
|
72
80
|
}
|
|
73
81
|
Inbox.displayName = "Inbox";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.recordpageRecipe = exports.recordPageLabels = exports.RecordPage = exports.RecordDetails = exports.useEditableField = exports.editableInputType = exports.tagTone = void 0;
|
|
3
|
+
exports.recordpageRecipe = exports.recordPageLabels = exports.RecordPageMain = exports.RecordPage = exports.RecordDetails = exports.useEditableField = exports.editableInputType = exports.tagTone = void 0;
|
|
4
4
|
var column_1 = require("./column");
|
|
5
5
|
Object.defineProperty(exports, "tagTone", { enumerable: true, get: function () { return column_1.tagTone; } });
|
|
6
6
|
var editable_field_1 = require("./editable-field");
|
|
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "useEditableField", { enumerable: true, get: func
|
|
|
9
9
|
var recordpage_1 = require("./recordpage");
|
|
10
10
|
Object.defineProperty(exports, "RecordDetails", { enumerable: true, get: function () { return recordpage_1.RecordDetails; } });
|
|
11
11
|
Object.defineProperty(exports, "RecordPage", { enumerable: true, get: function () { return recordpage_1.RecordPage; } });
|
|
12
|
+
Object.defineProperty(exports, "RecordPageMain", { enumerable: true, get: function () { return recordpage_1.RecordPageMain; } });
|
|
12
13
|
Object.defineProperty(exports, "recordPageLabels", { enumerable: true, get: function () { return recordpage_1.recordPageLabels; } });
|
|
13
14
|
var recordpage_recipe_1 = require("./recordpage.recipe");
|
|
14
15
|
Object.defineProperty(exports, "recordpageRecipe", { enumerable: true, get: function () { return recordpage_recipe_1.recordpageRecipe; } });
|
|
@@ -35,11 +35,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.recordPageLabels = void 0;
|
|
37
37
|
exports.RecordDetails = RecordDetails;
|
|
38
|
+
exports.RecordPageMain = RecordPageMain;
|
|
38
39
|
exports.RecordPage = RecordPage;
|
|
39
40
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
40
41
|
const lucide_react_1 = require("lucide-react");
|
|
41
42
|
const React = __importStar(require("react"));
|
|
42
43
|
const avatar_1 = require("../../components/avatar");
|
|
44
|
+
const button_1 = require("../../components/button");
|
|
43
45
|
const checkbox_1 = require("../../components/checkbox");
|
|
44
46
|
const drawer_1 = require("../../components/drawer");
|
|
45
47
|
const menu_1 = require("../../components/menu");
|
|
@@ -100,6 +102,22 @@ exports.recordPageLabels = {
|
|
|
100
102
|
share: "Share",
|
|
101
103
|
comments: "Comments",
|
|
102
104
|
noLists: "This record has not been added to any lists.",
|
|
105
|
+
filters: "Filters",
|
|
106
|
+
addMeeting: "Add meeting",
|
|
107
|
+
noFiles: "No files",
|
|
108
|
+
filesHint: "Drag a file here or choose one from your computer.",
|
|
109
|
+
newFolder: "New folder",
|
|
110
|
+
uploadFile: "Upload file",
|
|
111
|
+
fileMoreActions: "More file actions",
|
|
112
|
+
noDealsFound: "No Deals found",
|
|
113
|
+
noDealsHint: "There are no related records for this relationship.",
|
|
114
|
+
newDeal: "New Deal",
|
|
115
|
+
noEmails: "No emails",
|
|
116
|
+
noEmailsHint: "This record doesn't have any emails, or they may be hidden due to permissions.",
|
|
117
|
+
configureMailbox: "Configure mailbox",
|
|
118
|
+
upgradeToViewCalls: "Upgrade to view calls",
|
|
119
|
+
callsUpgradeHint: "No call recordings yet! Upgrade to Pro to start recording calls.",
|
|
120
|
+
upgrade: "Upgrade",
|
|
103
121
|
highlights: "Highlights",
|
|
104
122
|
recordDetails: "Record Details",
|
|
105
123
|
searchFields: "Search fields",
|
|
@@ -515,6 +533,28 @@ function RecordDetails({ record, columns, sections, onEdit, labels, className, r
|
|
|
515
533
|
} }))] }));
|
|
516
534
|
}
|
|
517
535
|
RecordDetails.displayName = "RecordDetails";
|
|
536
|
+
/**
|
|
537
|
+
* The main/tab-content column's own box, on its own (#750) — the same
|
|
538
|
+
* "standalone piece" `RecordDetails` already is for the Details column,
|
|
539
|
+
* mirrored to the other side of the layout. `RecordPage` composes this
|
|
540
|
+
* internally for its own tabbed body (below) — this exact box, not a second
|
|
541
|
+
* copy — so a consumer building a custom record page (its own tab set, its
|
|
542
|
+
* own menu, `RecordDetails` for the left column, but not the full
|
|
543
|
+
* `RecordPage`, which hardcodes the CRM tab set) gets the same
|
|
544
|
+
* Attio-measured inset: `flex: 1; min-width: 0; display: flex;
|
|
545
|
+
* flex-direction: column; min-height: 0; overflow-y: auto; padding: 12px
|
|
546
|
+
* 20px` (`recordpage.recipe.ts`'s own `.crisp-recordpage-main`) — without
|
|
547
|
+
* reaching for that internal class name directly, the only option before
|
|
548
|
+
* this existed.
|
|
549
|
+
*
|
|
550
|
+
* Needs the same single `.crisp-recordpage`-scoped ancestor `RecordDetails`
|
|
551
|
+
* already documents — wrap both in `recordpageRecipe()`, the way
|
|
552
|
+
* `RecordPage`'s own root does.
|
|
553
|
+
*/
|
|
554
|
+
function RecordPageMain({ className, children, ...rest }) {
|
|
555
|
+
return ((0, jsx_runtime_1.jsx)("main", { className: (0, core_1.cx)("crisp-recordpage-main", className), ...rest, children: children }));
|
|
556
|
+
}
|
|
557
|
+
RecordPageMain.displayName = "RecordPageMain";
|
|
518
558
|
/**
|
|
519
559
|
* The full-page company record view — the page that opens when you click into a
|
|
520
560
|
* record (as opposed to the peek [RecordPanel]). A top breadcrumb bar with
|
|
@@ -755,13 +795,19 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
|
|
|
755
795
|
case "overview":
|
|
756
796
|
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-panel", children: [highlights !== false && ((0, jsx_runtime_1.jsxs)("section", { children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-section-title", children: t.highlights }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-hl-card", children: (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-hl-grid", children: highlights.map((h) => ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-hl-cell", children: [h.icon && ((0, jsx_runtime_1.jsx)(h.icon, { className: "crisp-recordpage-hl-icon", size: 15, strokeWidth: 1.75 })), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-hl-label", children: h.label }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-hl-value", children: h.value ?? ((0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-hl-value--empty", children: h.empty })) })] }, h.label))) }) })] })), activityFeed, (0, jsx_runtime_1.jsxs)("section", { children: [(0, jsx_runtime_1.jsx)(SectionHead, { title: t.tabs.emails, count: 0 }), emailsEmpty] }), (0, jsx_runtime_1.jsx)(NotesSection, { notes: notes, onAdd: addNote, labels: t }), (0, jsx_runtime_1.jsx)(TasksSection, { tasks: tasks, onAdd: addTask, onToggle: toggleTask, labels: t })] }));
|
|
757
797
|
case "activity":
|
|
758
|
-
return (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-panel", children:
|
|
798
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-panel", children: (0, jsx_runtime_1.jsxs)("section", { children: [(0, jsx_runtime_1.jsx)(SectionHead, { title: t.tabs.activity, action: (0, jsx_runtime_1.jsxs)("span", { className: "crisp-recordpage-tabtoolbar", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-action", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ListFilter, { size: 14, strokeWidth: 1.75 }), t.filters] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-action", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 14, strokeWidth: 1.75 }), t.addMeeting] })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-feed", children: activity.map((a) => ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-feed-item", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-feed-dot", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Activity, { size: 13, strokeWidth: 2 }) }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-feed-body", children: [a.text, (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-feed-time", children: a.time })] })] }, a.id))) })] }) }));
|
|
799
|
+
case "files":
|
|
800
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-panel crisp-recordpage-panel--center", children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-illustrated", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-illustrated-icon", "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Folder, { size: 40, strokeWidth: 1.25 }) }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-illustrated-title", children: t.noFiles }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-illustrated-desc", children: t.filesHint }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-illustrated-actions", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-action", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 14, strokeWidth: 1.75 }), t.newFolder] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-action", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 14, strokeWidth: 1.75 }), t.uploadFile] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", "aria-label": t.fileMoreActions, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 16, strokeWidth: 1.75 }) })] })] }) }));
|
|
801
|
+
case "deals":
|
|
802
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-panel crisp-recordpage-panel--center", children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-illustrated", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-illustrated-icon", "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Merge, { size: 40, strokeWidth: 1.25 }) }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-illustrated-title", children: t.noDealsFound }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-illustrated-desc", children: t.noDealsHint }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-illustrated-actions", children: (0, jsx_runtime_1.jsxs)(button_1.Button, { intent: "primary", size: "md", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), t.newDeal] }) })] }) }));
|
|
759
803
|
case "notes":
|
|
760
804
|
return ((0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-panel", children: (0, jsx_runtime_1.jsx)(NotesSection, { notes: notes, onAdd: addNote, labels: t }) }));
|
|
761
805
|
case "tasks":
|
|
762
806
|
return ((0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-panel", children: (0, jsx_runtime_1.jsx)(TasksSection, { tasks: tasks, onAdd: addTask, onToggle: toggleTask, labels: t }) }));
|
|
763
807
|
case "emails":
|
|
764
|
-
return (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-panel", children:
|
|
808
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-panel", children: [(0, jsx_runtime_1.jsx)(SectionHead, { title: t.tabs.emails }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-panel crisp-recordpage-panel--center", children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-illustrated", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-illustrated-icon", "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Mail, { size: 40, strokeWidth: 1.25 }) }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-illustrated-title", children: t.noEmails }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-illustrated-desc", children: t.noEmailsHint }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-illustrated-actions", children: (0, jsx_runtime_1.jsx)(button_1.Button, { intent: "primary", size: "md", children: t.configureMailbox }) })] }) })] }));
|
|
809
|
+
case "calls":
|
|
810
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-panel crisp-recordpage-panel--center", children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-illustrated", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-illustrated-icon", "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Phone, { size: 40, strokeWidth: 1.25 }) }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-illustrated-title crisp-recordpage-illustrated-title--lg", children: t.upgradeToViewCalls }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-illustrated-desc", children: t.callsUpgradeHint }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-illustrated-actions", children: (0, jsx_runtime_1.jsxs)(button_1.Button, { intent: "primary", size: "md", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Zap, { size: 15, strokeWidth: 1.75 }), t.upgrade] }) })] }) }));
|
|
765
811
|
default:
|
|
766
812
|
return ((0, jsx_runtime_1.jsx)("div", { className: "crisp-recordpage-panel", children: (0, jsx_runtime_1.jsx)(EmptyCard, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Folder, { size: 22, strokeWidth: 1.5 }), children: t.nothingHereYet }) }));
|
|
767
813
|
}
|
|
@@ -791,6 +837,6 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
|
|
|
791
837
|
setDetailsW((w) => grow
|
|
792
838
|
? Math.min(DETAILS_MAX_W, w + 16)
|
|
793
839
|
: Math.max(DETAILS_MIN_W, w - 16));
|
|
794
|
-
} }), (0, jsx_runtime_1.jsx)(
|
|
840
|
+
} }), (0, jsx_runtime_1.jsx)(RecordPageMain, { children: (0, jsx_runtime_1.jsx)(tabs_1.Tabs, { tabs: effectiveTabs, defaultValue: tabsProp ? defaultTab : "overview", "aria-label": `${name} record`, children: effectivePanel }) })] }), (0, jsx_runtime_1.jsx)(drawer_1.Drawer, { open: commentsOpen, onOpenChange: setCommentsOpen, "aria-label": t.comments, style: { width: 320 }, children: (0, jsx_runtime_1.jsx)(comments_1.CommentsPanel, { threads: comments, currentUserId: me.id, onPost: postComment, onClose: () => setCommentsOpen(false), role: undefined }) })] }));
|
|
795
841
|
}
|
|
796
842
|
RecordPage.displayName = "RecordPage";
|
|
@@ -229,6 +229,31 @@ const config = (0, core_1.defineRecipe)({
|
|
|
229
229
|
& .crisp-recordpage-emptycard { display: flex; flex-direction: column; align-items: center; gap: ${tokens_1.contract.space["1.5"]}; padding: ${tokens_1.contract.space["6"]} ${tokens_1.contract.space["3"]}; border: 1px dashed ${tokens_1.contract.borderDefault}; border-radius: ${tokens_1.contract.radius.xl}; color: ${tokens_1.contract.fgMuted}; font-size: ${tokens_1.contract.text.md}; text-align: center; }
|
|
230
230
|
& .crisp-recordpage-emptycard svg { color: ${tokens_1.contract.fgMuted}; opacity: 0.7; }
|
|
231
231
|
|
|
232
|
+
/* #622: the Activity tab's own toolbar -- "Filters" + "+ Add meeting",
|
|
233
|
+
both the same measured button chrome as the topbar's own action
|
|
234
|
+
buttons (28px, e.field shadow, 14/500 label). */
|
|
235
|
+
& .crisp-recordpage-tabtoolbar { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["1.5"]}; }
|
|
236
|
+
|
|
237
|
+
/* #622: Files tab's empty state -- measured live on Attio's own record
|
|
238
|
+
page (a company with no files uploaded). The 138x138 dotted-grid
|
|
239
|
+
backdrop is the same measured tile EmptyState's records variant uses
|
|
240
|
+
(crisp/#603), duplicated here rather than composed through that
|
|
241
|
+
component because this surface's title (16/600) and body (14/500,
|
|
242
|
+
63%-mixed fgPrimary, not the flat fgMuted EmptyState uses) measure to
|
|
243
|
+
their own values, matching neither of that component's existing
|
|
244
|
+
variants. */
|
|
245
|
+
& .crisp-recordpage-panel--center { flex: 1; align-items: center; justify-content: center; }
|
|
246
|
+
& .crisp-recordpage-illustrated { display: flex; flex-direction: column; align-items: center; gap: ${tokens_1.contract.space["1.5"]}; text-align: center; }
|
|
247
|
+
& .crisp-recordpage-illustrated-icon { display: flex; align-items: center; justify-content: center; width: 138px; height: 138px; margin-bottom: ${tokens_1.contract.space["2"]}; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 22%, transparent); background: radial-gradient(circle, color-mix(in srgb, ${tokens_1.contract.fgPrimary} 12%, transparent) 1.2px, transparent 1.2px) 0 0 / 16px 16px; }
|
|
248
|
+
& .crisp-recordpage-illustrated-title { font-size: ${tokens_1.contract.text.xl}; font-weight: ${tokens_1.contract.weight.semibold}; color: ${tokens_1.contract.fgPrimary}; }
|
|
249
|
+
/* #988: the Calls tab's own gated state measures a larger 20/24 title
|
|
250
|
+
than Files/Deals' 16/600 -- Attio's own "empty view" pattern (the
|
|
251
|
+
same 20/600 EmptyState's page/records variants already carry, not a
|
|
252
|
+
one-off). */
|
|
253
|
+
& .crisp-recordpage-illustrated-title--lg { font-size: 20px; line-height: 24px; }
|
|
254
|
+
& .crisp-recordpage-illustrated-desc { max-width: 320px; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 63%, transparent); }
|
|
255
|
+
& .crisp-recordpage-illustrated-actions { display: inline-flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: ${tokens_1.contract.space["1.5"]}; margin-top: ${tokens_1.contract.space["2"]}; }
|
|
256
|
+
|
|
232
257
|
& .crisp-recordpage-action[data-active] svg { color: #f5b83d; fill: #f5b83d; }
|
|
233
258
|
|
|
234
259
|
& .crisp-recordstable-tag { display: inline-flex; align-items: center; height: ${tokens_1.contract.size.chip}; padding: 0 ${tokens_1.contract.space["1"]}; border-radius: ${tokens_1.contract.radius.chip}; font-size: ${tokens_1.contract.text.lg}; line-height: 20px; font-weight: ${tokens_1.contract.weight.medium}; white-space: nowrap; background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 6%, transparent); color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 82%, transparent); }
|
package/dist/datatable.css
CHANGED
|
@@ -98,7 +98,13 @@
|
|
|
98
98
|
& .crisp-datatable-group-count { margin-inline-start: var(--crisp-space-1-5); color: var(--crisp-fg-muted); font-variant-numeric: tabular-nums; }
|
|
99
99
|
& .crisp-datatable-th-rename { width: 100%; min-width: 0; height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-1); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); outline: none; }
|
|
100
100
|
& .crisp-datatable-th-rename:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
|
|
101
|
-
|
|
101
|
+
/* #719: overflow: clip, not hidden, on every truncation rule below --
|
|
102
|
+
hidden still honours a programmatic scrollLeft assignment (found via
|
|
103
|
+
a screenshot tool that pans anything with scrollWidth > clientWidth),
|
|
104
|
+
blanking or fragmenting the visible text even though real user
|
|
105
|
+
scrolling was already blocked. clip renders the ellipsis identically
|
|
106
|
+
and leaves no scroll position to move. */
|
|
107
|
+
& .crisp-datatable-th-label { overflow: clip; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
102
108
|
/* #465: 12px per the comment above (and Attio's measured header cell
|
|
103
109
|
padding: 0 12px) -- was still c.space["2"] (8px), drifted from its
|
|
104
110
|
own documented value. */
|
|
@@ -185,7 +191,7 @@
|
|
|
185
191
|
/* reference picker: a leading 16px avatar per option, plus a trailing
|
|
186
192
|
"Create X" row when the consumer supplies one (#449) -- same measured
|
|
187
193
|
values as RecordsTable's own .crisp-recordstable-refoption*. */
|
|
188
|
-
& .crisp-datatable-refoption { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow:
|
|
194
|
+
& .crisp-datatable-refoption { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow: clip; text-overflow: ellipsis; }
|
|
189
195
|
& .crisp-datatable-refoption-avatar { flex-shrink: 0; }
|
|
190
196
|
& .crisp-datatable-refoption--create { color: var(--crisp-fg-muted); }
|
|
191
197
|
& .crisp-datatable-empty { padding: var(--crisp-space-8) var(--crisp-space-3); text-align: center; color: var(--crisp-fg-muted); font-size: var(--crisp-text-lg); }
|
|
@@ -261,7 +267,7 @@
|
|
|
261
267
|
(#833/#835/#848) that happened to apply the same 500 to every
|
|
262
268
|
primary-column body cell regardless of type. Made explicit here so
|
|
263
269
|
fixing that leak doesn't also (wrongly) drop entity names to 400. */
|
|
264
|
-
& .crisp-datatable-entity-name { overflow:
|
|
270
|
+
& .crisp-datatable-entity-name { overflow: clip; text-overflow: ellipsis; white-space: nowrap; font-weight: var(--crisp-weight-medium); text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); }
|
|
265
271
|
/* #893: shared by the button (onEntityClick, a different record) and
|
|
266
272
|
the anchor (href, the row's own record) -- was button-only, which
|
|
267
273
|
silently left a real <a> unstyled the day #893 added one. */
|
|
@@ -312,7 +318,7 @@
|
|
|
312
318
|
own .crisp-recordstable-link, which this mirrors: no underline at
|
|
313
319
|
rest, underline on hover, own-colour text rather than a browser-blue
|
|
314
320
|
link. */
|
|
315
|
-
& .crisp-datatable-link { color: var(--crisp-fg-primary); text-decoration: none; overflow:
|
|
321
|
+
& .crisp-datatable-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: clip; text-overflow: ellipsis; }
|
|
316
322
|
& .crisp-datatable-link:hover { text-decoration: underline; }
|
|
317
323
|
/* phone cell type/editor (#765) -- measured live: the dial-code prefix
|
|
318
324
|
is a muted rgba(0,0,0,0.63) at 14px, matching every other muted-text
|
|
@@ -99,7 +99,13 @@
|
|
|
99
99
|
& .crisp-datatable-group-count { margin-inline-start: var(--crisp-space-1-5); color: var(--crisp-fg-muted); font-variant-numeric: tabular-nums; }
|
|
100
100
|
& .crisp-datatable-th-rename { width: 100%; min-width: 0; height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-1); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); outline: none; }
|
|
101
101
|
& .crisp-datatable-th-rename:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
|
|
102
|
-
|
|
102
|
+
/* #719: overflow: clip, not hidden, on every truncation rule below --
|
|
103
|
+
hidden still honours a programmatic scrollLeft assignment (found via
|
|
104
|
+
a screenshot tool that pans anything with scrollWidth > clientWidth),
|
|
105
|
+
blanking or fragmenting the visible text even though real user
|
|
106
|
+
scrolling was already blocked. clip renders the ellipsis identically
|
|
107
|
+
and leaves no scroll position to move. */
|
|
108
|
+
& .crisp-datatable-th-label { overflow: clip; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
103
109
|
/* #465: 12px per the comment above (and Attio's measured header cell
|
|
104
110
|
padding: 0 12px) -- was still c.space["2"] (8px), drifted from its
|
|
105
111
|
own documented value. */
|
|
@@ -186,7 +192,7 @@
|
|
|
186
192
|
/* reference picker: a leading 16px avatar per option, plus a trailing
|
|
187
193
|
"Create X" row when the consumer supplies one (#449) -- same measured
|
|
188
194
|
values as RecordsTable's own .crisp-recordstable-refoption*. */
|
|
189
|
-
& .crisp-datatable-refoption { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow:
|
|
195
|
+
& .crisp-datatable-refoption { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow: clip; text-overflow: ellipsis; }
|
|
190
196
|
& .crisp-datatable-refoption-avatar { flex-shrink: 0; }
|
|
191
197
|
& .crisp-datatable-refoption--create { color: var(--crisp-fg-muted); }
|
|
192
198
|
& .crisp-datatable-empty { padding: var(--crisp-space-8) var(--crisp-space-3); text-align: center; color: var(--crisp-fg-muted); font-size: var(--crisp-text-lg); }
|
|
@@ -262,7 +268,7 @@
|
|
|
262
268
|
(#833/#835/#848) that happened to apply the same 500 to every
|
|
263
269
|
primary-column body cell regardless of type. Made explicit here so
|
|
264
270
|
fixing that leak doesn't also (wrongly) drop entity names to 400. */
|
|
265
|
-
& .crisp-datatable-entity-name { overflow:
|
|
271
|
+
& .crisp-datatable-entity-name { overflow: clip; text-overflow: ellipsis; white-space: nowrap; font-weight: var(--crisp-weight-medium); text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); }
|
|
266
272
|
/* #893: shared by the button (onEntityClick, a different record) and
|
|
267
273
|
the anchor (href, the row's own record) -- was button-only, which
|
|
268
274
|
silently left a real <a> unstyled the day #893 added one. */
|
|
@@ -313,7 +319,7 @@
|
|
|
313
319
|
own .crisp-recordstable-link, which this mirrors: no underline at
|
|
314
320
|
rest, underline on hover, own-colour text rather than a browser-blue
|
|
315
321
|
link. */
|
|
316
|
-
& .crisp-datatable-link { color: var(--crisp-fg-primary); text-decoration: none; overflow:
|
|
322
|
+
& .crisp-datatable-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: clip; text-overflow: ellipsis; }
|
|
317
323
|
& .crisp-datatable-link:hover { text-decoration: underline; }
|
|
318
324
|
/* phone cell type/editor (#765) -- measured live: the dial-code prefix
|
|
319
325
|
is a muted rgba(0,0,0,0.63) at 14px, matching every other muted-text
|