@crisp-ui-kit/crisp 0.33.4 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/checkbox.css +1 -1
  2. package/dist/checkbox.layered.css +1 -1
  3. package/dist/cjs/components/avatargroup/avatargroup.recipe.js +21 -8
  4. package/dist/cjs/components/barchart/barchart.js +6 -1
  5. package/dist/cjs/components/checkbox/checkbox.recipe.js +50 -17
  6. package/dist/cjs/components/datepicker/datepicker.js +7 -1
  7. package/dist/cjs/components/kbd/kbd.recipe.js +22 -9
  8. package/dist/cjs/components/linechart/linechart.js +6 -1
  9. package/dist/cjs/components/skeleton/skeleton.recipe.js +14 -8
  10. package/dist/cjs/components/table/table.js +6 -2
  11. package/dist/cjs/components/textarea/textarea.js +9 -2
  12. package/dist/cjs/components/textarea/textarea.recipe.js +14 -5
  13. package/dist/cjs/components/tooltip/tooltip.recipe.js +27 -6
  14. package/dist/cjs/product/recordpage/recordpage.js +1 -1
  15. package/dist/command.css +1 -1
  16. package/dist/command.layered.css +1 -1
  17. package/dist/esm/components/avatargroup/avatargroup.recipe.js +21 -8
  18. package/dist/esm/components/barchart/barchart.js +6 -1
  19. package/dist/esm/components/checkbox/checkbox.recipe.js +50 -17
  20. package/dist/esm/components/datepicker/datepicker.js +7 -1
  21. package/dist/esm/components/kbd/kbd.recipe.js +22 -9
  22. package/dist/esm/components/linechart/linechart.js +6 -1
  23. package/dist/esm/components/skeleton/skeleton.recipe.js +14 -8
  24. package/dist/esm/components/table/table.js +6 -2
  25. package/dist/esm/components/textarea/textarea.js +9 -2
  26. package/dist/esm/components/textarea/textarea.recipe.js +14 -5
  27. package/dist/esm/components/tooltip/tooltip.recipe.js +27 -6
  28. package/dist/esm/product/recordpage/recordpage.js +1 -1
  29. package/dist/navitem.css +1 -1
  30. package/dist/navitem.layered.css +1 -1
  31. package/dist/sidebar.css +1 -1
  32. package/dist/sidebar.layered.css +1 -1
  33. package/dist/styles.css +6 -90
  34. package/dist/styles.layered.css +6 -90
  35. package/dist/textarea.css +2 -2
  36. package/dist/textarea.layered.css +2 -2
  37. package/dist/tokens.css +0 -84
  38. package/dist/types/components/avatargroup/avatargroup.recipe.d.ts +25 -2
  39. package/dist/types/components/checkbox/checkbox.recipe.d.ts +51 -3
  40. package/dist/types/components/kbd/kbd.recipe.d.ts +24 -3
  41. package/dist/types/components/skeleton/skeleton.recipe.d.ts +19 -2
  42. package/dist/types/components/tooltip/tooltip.recipe.d.ts +30 -2
  43. package/package.json +2 -1
package/dist/checkbox.css CHANGED
@@ -1,5 +1,5 @@
1
1
  .crisp-checkbox {
2
- appearance: none; -webkit-appearance: none; box-sizing: border-box; flex-shrink: 0; margin: 0; width: 16px; height: 16px; border: none; border-radius: var(--crisp-radius-sm) ; background: var(--crisp-bg-surface) ; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); cursor: pointer; transition: background-color .12s, box-shadow .12s;
2
+ appearance: none; -webkit-appearance: none; box-sizing: border-box; flex-shrink: 0; margin: 0; width: 16px; height: 16px; border: none; border-radius: var(--crisp-radius-sm); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); cursor: pointer; transition: background-color .12s, box-shadow .12s;
3
3
  &:hover { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 30%, transparent); }
4
4
  &:checked, &:indeterminate { background: var(--crisp-bg-brand-solid) center / 10px no-repeat; box-shadow: none; }
5
5
  &:checked { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 6.5 5 9l4.5-5'/%3E%3C/svg%3E"); }
@@ -1,6 +1,6 @@
1
1
  @layer crisp-components {
2
2
  .crisp-checkbox {
3
- appearance: none; -webkit-appearance: none; box-sizing: border-box; flex-shrink: 0; margin: 0; width: 16px; height: 16px; border: none; border-radius: var(--crisp-radius-sm) ; background: var(--crisp-bg-surface) ; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); cursor: pointer; transition: background-color .12s, box-shadow .12s;
3
+ appearance: none; -webkit-appearance: none; box-sizing: border-box; flex-shrink: 0; margin: 0; width: 16px; height: 16px; border: none; border-radius: var(--crisp-radius-sm); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); cursor: pointer; transition: background-color .12s, box-shadow .12s;
4
4
  &:hover { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 30%, transparent); }
5
5
  &:checked, &:indeterminate { background: var(--crisp-bg-brand-solid) center / 10px no-repeat; box-shadow: none; }
6
6
  &:checked { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 6.5 5 9l4.5-5'/%3E%3C/svg%3E"); }
@@ -1,18 +1,31 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.avatargroupRecipe = void 0;
3
+ exports.spec = exports.avatargroupRecipe = void 0;
4
4
  const core_1 = require("../../core");
5
5
  const tokens_1 = require("../../tokens");
6
6
  // A row of overlapping avatars — each ringed in the surface colour so it reads
7
7
  // against its neighbour, with a "+N" overflow chip.
8
- const config = (0, core_1.defineRecipe)({
8
+ const spec = (0, core_1.defineSpec)({
9
9
  name: "avatargroup",
10
- base: `display: inline-flex; align-items: center;
11
- & > * { margin-inline-start: -${tokens_1.contract.space["2"]}; box-shadow: 0 0 0 2px ${tokens_1.contract.bgSurface}; position: relative; }
12
- & > *:first-child { margin-inline-start: 0; }`,
13
- variants: {},
14
- defaultVariants: {},
15
- compoundVariants: [],
10
+ base: { display: "inline-flex", alignItems: "center" },
11
+ parts: [
12
+ {
13
+ slot: "member",
14
+ selector: "& > *",
15
+ decls: {
16
+ marginInlineStart: `-${tokens_1.contract.space["2"]}`,
17
+ boxShadow: `0 0 0 2px ${tokens_1.contract.bgSurface}`,
18
+ position: "relative",
19
+ },
20
+ },
21
+ {
22
+ slot: "member",
23
+ selector: "& > *:first-child",
24
+ decls: { marginInlineStart: 0 },
25
+ },
26
+ ],
16
27
  });
28
+ exports.spec = spec;
29
+ const config = (0, core_1.specToRecipe)(spec);
17
30
  exports.avatargroupRecipe = (0, core_1.resolveRecipe)(config);
18
31
  exports.default = config;
@@ -34,9 +34,14 @@ function BarChart({ data, height = 240, max, ticks = 5, color, format = (n) => n
34
34
  : `max(34px, ${longestTickChars}ch + ${tokens_1.contract.space["4"]})`;
35
35
  const summary = ariaLabel ??
36
36
  `Bar chart: ${data.map((d) => `${d.label} ${format(d.value)}`).join(", ")}`;
37
- return ((0, jsx_runtime_1.jsxs)("div", { className: (0, barchart_recipe_1.barchartRecipe)({ className }), ...rest, style: {
37
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, barchart_recipe_1.barchartRecipe)({ className }), ...rest,
38
+ // merged, not assigned: written plainly this sits AFTER `{...rest}` and
39
+ // overwrites whatever style the consumer passed, so `style={{ maxWidth }}`
40
+ // on the chart did nothing at all. The consumer's wins on collision.
41
+ style: {
38
42
  height,
39
43
  "--crisp-barchart-yaxis-width": yAxisWidthValue,
44
+ ...rest.style,
40
45
  }, role: "img", "aria-label": summary, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-barchart-body", children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-barchart-yaxis", children: tickValues.map((t) => ((0, jsx_runtime_1.jsx)("span", { className: "crisp-barchart-ytick", style: { bottom: `${(t / top) * 100}%` }, children: format(t) }, t))) }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-barchart-plot", children: [tickValues.map((t) => ((0, jsx_runtime_1.jsx)("div", { className: "crisp-barchart-grid", style: { bottom: `${(t / top) * 100}%` } }, t))), data.map((d) => ((0, jsx_runtime_1.jsx)("div", { className: "crisp-barchart-col", children: (0, jsx_runtime_1.jsx)("div", { className: "crisp-barchart-bar", "data-empty": d.value <= 0 ? "" : undefined, style: {
41
46
  height: `${Math.max(0, (d.value / top) * 100)}%`,
42
47
  background: color,
@@ -1,28 +1,61 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.checkboxRecipe = void 0;
3
+ exports.spec = exports.checkboxRecipe = void 0;
4
4
  const core_1 = require("../../core");
5
5
  const tokens_1 = require("../../tokens");
6
6
  // A 16px square checkbox measured from a record grid: r6, a subtle 1px
7
7
  // inset ring when empty, brand fill + white check when on. The check is a
8
8
  // data-URI SVG background so it renders reliably (input pseudo-content doesn't).
9
9
  const CHECK = "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 6.5 5 9l4.5-5'/%3E%3C/svg%3E\")";
10
- const config = (0, core_1.defineRecipe)({
10
+ const spec = (0, core_1.defineSpec)({
11
11
  name: "checkbox",
12
- base: `appearance: none; -webkit-appearance: none; box-sizing: border-box; flex-shrink: 0; margin: 0; width: 16px; height: 16px; border: none; border-radius: ${tokens_1.contract.radius.sm}
13
- ; background: ${tokens_1.contract.bgSurface}
14
- ; box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.fgPrimary}
15
- 9%, transparent); cursor: pointer; transition: background-color .12s, box-shadow .12s; &:hover { box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.fgPrimary} 30%, transparent); }
16
- &:checked, &:indeterminate { background: ${tokens_1.contract.bgBrandSolid} center / 10px no-repeat; box-shadow: none; }
17
- &:checked { background-image: ${CHECK}; }
18
- &:indeterminate { background-image: linear-gradient(${tokens_1.contract.fgOnBrand}, ${tokens_1.contract.fgOnBrand}); background-size: 8px 2px; }
19
- &:focus-visible { outline: 2px solid ${tokens_1.contract.bgBrandSolid}; outline-offset: 1px; }
20
- `,
21
- variants: {},
22
- defaultVariants: {},
23
- compoundVariants: [],
24
- });
25
- exports.checkboxRecipe = Object.assign((0, core_1.resolveRecipe)(config), {
26
- config,
12
+ base: {
13
+ appearance: "none",
14
+ WebkitAppearance: "none",
15
+ boxSizing: "border-box",
16
+ flexShrink: 0,
17
+ margin: 0,
18
+ width: "16px",
19
+ height: "16px",
20
+ border: "none",
21
+ borderRadius: tokens_1.contract.radius.sm,
22
+ background: tokens_1.contract.bgSurface,
23
+ boxShadow: `inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.fgPrimary} 9%, transparent)`,
24
+ cursor: "pointer",
25
+ transition: "background-color .12s, box-shadow .12s",
26
+ },
27
+ parts: [
28
+ {
29
+ selector: "&:hover",
30
+ decls: {
31
+ boxShadow: `inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.fgPrimary} 30%, transparent)`,
32
+ },
33
+ },
34
+ {
35
+ selector: "&:checked, &:indeterminate",
36
+ decls: {
37
+ background: `${tokens_1.contract.bgBrandSolid} center / 10px no-repeat`,
38
+ boxShadow: "none",
39
+ },
40
+ },
41
+ { selector: "&:checked", decls: { backgroundImage: CHECK } },
42
+ {
43
+ selector: "&:indeterminate",
44
+ decls: {
45
+ backgroundImage: `linear-gradient(${tokens_1.contract.fgOnBrand}, ${tokens_1.contract.fgOnBrand})`,
46
+ backgroundSize: "8px 2px",
47
+ },
48
+ },
49
+ {
50
+ selector: "&:focus-visible",
51
+ decls: {
52
+ outline: `2px solid ${tokens_1.contract.bgBrandSolid}`,
53
+ outlineOffset: "1px",
54
+ },
55
+ },
56
+ ],
27
57
  });
58
+ exports.spec = spec;
59
+ const config = (0, core_1.specToRecipe)(spec);
60
+ exports.checkboxRecipe = Object.assign((0, core_1.resolveRecipe)(config), { config });
28
61
  exports.default = config;
@@ -126,7 +126,13 @@ const DatePickerImplementation = React.forwardRef((props, forwardedRef) => {
126
126
  }, ...(0, dom_utils_1.mergeProps)(api.rootProps, rootProps), "aria-label": rootAriaLabel, "aria-labelledby": ariaLabelledby, className: (0, datepicker_recipe_1.datepickerRecipe)({
127
127
  visibleRange: api.visibleRange,
128
128
  className,
129
- }), style: styleOf({ height, minHeight, maxHeight }), children: [api.visibleRange === "continuous" ? ((0, jsx_runtime_1.jsxs)("div", { ref: api.refs.continuousScroll, ...api.continuousScrollProps, "data-date-picker-continuous-scroll": "", className: "crisp-datepicker-continuous-scroll", children: [(0, jsx_runtime_1.jsx)(WeekdayRow, { api: api, semantic: false }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-datepicker-continuous-content", children: [(0, jsx_runtime_1.jsx)("div", { "aria-hidden": "true", className: "datepicker-continuous-spacer", style: { height: api.virtual.topHeight } }), api.months.map((month) => ((0, jsx_runtime_1.jsx)(MonthView, { api: api, month: month, renderDateCellContent: renderDateCellContent, renderDateCellSupplement: renderDateCellSupplement, showWeekdays: false, showMonthLabel: true, monthRef: api.refs.continuousMonth(month.key) }, month.key))), (0, jsx_runtime_1.jsx)("div", { "aria-hidden": "true", className: "datepicker-continuous-spacer", style: { height: api.virtual.bottomHeight } })] })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [api.visibleRange !== "twoMonths" && (0, jsx_runtime_1.jsx)(Header, { api: api }), (0, jsx_runtime_1.jsx)("div", { "data-wheel-open": api.isWheelOpen && api.visibleRange !== "twoMonths"
129
+ }),
130
+ // merged: this sits after the spread above, so assigning it plainly
131
+ // dropped any style the consumer passed. Theirs wins on collision.
132
+ style: {
133
+ ...styleOf({ height, minHeight, maxHeight }),
134
+ ...rootProps?.style,
135
+ }, children: [api.visibleRange === "continuous" ? ((0, jsx_runtime_1.jsxs)("div", { ref: api.refs.continuousScroll, ...api.continuousScrollProps, "data-date-picker-continuous-scroll": "", className: "crisp-datepicker-continuous-scroll", children: [(0, jsx_runtime_1.jsx)(WeekdayRow, { api: api, semantic: false }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-datepicker-continuous-content", children: [(0, jsx_runtime_1.jsx)("div", { "aria-hidden": "true", className: "datepicker-continuous-spacer", style: { height: api.virtual.topHeight } }), api.months.map((month) => ((0, jsx_runtime_1.jsx)(MonthView, { api: api, month: month, renderDateCellContent: renderDateCellContent, renderDateCellSupplement: renderDateCellSupplement, showWeekdays: false, showMonthLabel: true, monthRef: api.refs.continuousMonth(month.key) }, month.key))), (0, jsx_runtime_1.jsx)("div", { "aria-hidden": "true", className: "datepicker-continuous-spacer", style: { height: api.virtual.bottomHeight } })] })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [api.visibleRange !== "twoMonths" && (0, jsx_runtime_1.jsx)(Header, { api: api }), (0, jsx_runtime_1.jsx)("div", { "data-wheel-open": api.isWheelOpen && api.visibleRange !== "twoMonths"
130
136
  ? ""
131
137
  : undefined, "aria-hidden": api.isWheelOpen && api.visibleRange !== "twoMonths"
132
138
  ? true
@@ -1,16 +1,29 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.kbdRecipe = void 0;
3
+ exports.spec = exports.kbdRecipe = void 0;
4
4
  const core_1 = require("../../core");
5
5
  const tokens_1 = require("../../tokens");
6
- const config = (0, core_1.defineRecipe)({
6
+ const spec = (0, core_1.defineSpec)({
7
7
  name: "kbd",
8
- base: `box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; gap: ${tokens_1.contract.space["0.5"]}; min-width: 20px; height: 20px; padding-inline: ${tokens_1.contract.space["1"]}; border-radius: ${tokens_1.contract.radius.sm}; font-family: ${tokens_1.contract.font.sans}; font-size: ${tokens_1.contract.text.xs}; font-weight: ${tokens_1.contract.weight.regular}; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 50%, transparent); background: transparent; box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.fgPrimary} 6%, transparent);`,
9
- variants: {},
10
- defaultVariants: {},
11
- compoundVariants: [],
12
- });
13
- exports.kbdRecipe = Object.assign((0, core_1.resolveRecipe)(config), {
14
- config,
8
+ base: {
9
+ boxSizing: "border-box",
10
+ display: "inline-flex",
11
+ alignItems: "center",
12
+ justifyContent: "center",
13
+ gap: tokens_1.contract.space["0.5"],
14
+ minWidth: "20px",
15
+ height: "20px",
16
+ paddingInline: tokens_1.contract.space["1"],
17
+ borderRadius: tokens_1.contract.radius.sm,
18
+ fontFamily: tokens_1.contract.font.sans,
19
+ fontSize: tokens_1.contract.text.xs,
20
+ fontWeight: tokens_1.contract.weight.regular,
21
+ color: `color-mix(in srgb, ${tokens_1.contract.fgPrimary} 50%, transparent)`,
22
+ background: "transparent",
23
+ boxShadow: `inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.fgPrimary} 6%, transparent)`,
24
+ },
15
25
  });
26
+ exports.spec = spec;
27
+ const config = (0, core_1.specToRecipe)(spec);
28
+ exports.kbdRecipe = Object.assign((0, core_1.resolveRecipe)(config), { config });
16
29
  exports.default = config;
@@ -44,9 +44,14 @@ function LineChart({ data, height = 240, max, ticks = 5, color, area = true, dot
44
44
  : "";
45
45
  const summary = ariaLabel ??
46
46
  `Line chart: ${data.map((d) => `${d.label} ${format(d.value)}`).join(", ")}`;
47
- return ((0, jsx_runtime_1.jsxs)("div", { className: (0, linechart_recipe_1.linechartRecipe)({ className }), ...rest, style: {
47
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, linechart_recipe_1.linechartRecipe)({ className }), ...rest,
48
+ // merged, not assigned: written plainly this sits AFTER `{...rest}` and
49
+ // overwrites whatever style the consumer passed, so `style={{ maxWidth }}`
50
+ // on the chart did nothing at all. The consumer's wins on collision.
51
+ style: {
48
52
  height,
49
53
  "--crisp-linechart-yaxis-width": yAxisWidthValue,
54
+ ...rest.style,
50
55
  }, role: "img", "aria-label": summary, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-linechart-body", children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-linechart-yaxis", children: tickValues.map((t) => ((0, jsx_runtime_1.jsx)("span", { className: "crisp-linechart-ytick", style: { bottom: `${(t / top) * 100}%` }, children: format(t) }, t))) }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-linechart-plot", children: [tickValues.map((t) => ((0, jsx_runtime_1.jsx)("div", { className: "crisp-linechart-grid", style: { bottom: `${(t / top) * 100}%` } }, t))), pts.length > 1 && ((0, jsx_runtime_1.jsxs)("svg", { className: "crisp-linechart-svg", viewBox: "0 0 100 100", preserveAspectRatio: "none", "aria-hidden": "true", children: [area && (0, jsx_runtime_1.jsx)("path", { className: "crisp-linechart-area", d: areaPath }), (0, jsx_runtime_1.jsx)("polyline", { className: "crisp-linechart-line", points: line, style: { stroke: color } })] })), dots &&
51
56
  pts.map((p, i) => ((0, jsx_runtime_1.jsx)("span", { className: "crisp-linechart-dot-html", style: {
52
57
  left: `${p.x}%`,
@@ -1,17 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.skeletonRecipe = void 0;
3
+ exports.spec = exports.skeletonRecipe = void 0;
4
4
  const core_1 = require("../../core");
5
5
  const tokens_1 = require("../../tokens");
6
6
  // A pulsing placeholder block for loading states.
7
- const config = (0, core_1.defineRecipe)({
7
+ const spec = (0, core_1.defineSpec)({
8
8
  name: "skeleton",
9
- globalCss: `@keyframes crisp-skeleton { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }`,
10
- base: `display: block; background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 8%, transparent); border-radius: ${tokens_1.contract.radius.md}; animation: crisp-skeleton 1.4s ease-in-out infinite;
11
- &[data-circle] { border-radius: ${tokens_1.contract.radius.full}; }`,
12
- variants: {},
13
- defaultVariants: {},
14
- compoundVariants: [],
9
+ globalCss: "@keyframes crisp-skeleton { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }",
10
+ base: {
11
+ display: "block",
12
+ background: `color-mix(in srgb, ${tokens_1.contract.fgPrimary} 8%, transparent)`,
13
+ borderRadius: tokens_1.contract.radius.md,
14
+ animation: "crisp-skeleton 1.4s ease-in-out infinite",
15
+ },
16
+ parts: [
17
+ { selector: "&[data-circle]", decls: { borderRadius: tokens_1.contract.radius.full } },
18
+ ],
15
19
  });
20
+ exports.spec = spec;
21
+ const config = (0, core_1.specToRecipe)(spec);
16
22
  exports.skeletonRecipe = (0, core_1.resolveRecipe)(config);
17
23
  exports.default = config;
@@ -44,11 +44,15 @@ const table_recipe_1 = require("./table.recipe");
44
44
  * `TableHeader`, `TableBody`, `TableFooter`, `TableRow`, `TableHead`,
45
45
  * `TableCell`, and `TableCaption`.
46
46
  */
47
- exports.Table = React.forwardRef(({ interactive, surface, className, wrapperClassName, scrollProps, children, ...props }, ref) => ((0, jsx_runtime_1.jsx)("div", { className: (0, table_recipe_1.tableRecipe)({
47
+ exports.Table = React.forwardRef(({ interactive, surface, className, wrapperClassName, scrollProps, children,
48
+ // on the root for the same reason as Textarea: the wrapper is the box a
49
+ // consumer sees and the scroll container they want to size. Native
50
+ // table attributes still reach the <table> below.
51
+ style, ...props }, ref) => ((0, jsx_runtime_1.jsx)("div", { className: (0, table_recipe_1.tableRecipe)({
48
52
  interactive,
49
53
  surface,
50
54
  className: wrapperClassName,
51
- }), children: (0, jsx_runtime_1.jsx)("div", { ...scrollProps, className: `crisp-table-scroll${scrollProps?.className ? ` ${scrollProps.className}` : ""}`, children: (0, jsx_runtime_1.jsx)("table", { ref: ref, className: className, ...props, children: children }) }) })));
55
+ }), style: style, children: (0, jsx_runtime_1.jsx)("div", { ...scrollProps, className: `crisp-table-scroll${scrollProps?.className ? ` ${scrollProps.className}` : ""}`, children: (0, jsx_runtime_1.jsx)("table", { ref: ref, className: className, ...props, children: children }) }) })));
52
56
  exports.Table.displayName = "Table";
53
57
  exports.TableHeader = React.forwardRef((props, ref) => (0, jsx_runtime_1.jsx)("thead", { ref: ref, ...props }));
54
58
  exports.TableHeader.displayName = "TableHeader";
@@ -42,12 +42,19 @@ const textarea_recipe_1 = require("./textarea.recipe");
42
42
  * `<textarea>` prop; pass `showCount` with `maxLength` to render a live
43
43
  * character counter (which turns red if the value exceeds the limit).
44
44
  */
45
- exports.Textarea = React.forwardRef(({ showCount, maxLength, className, wrapperClassName, value, defaultValue, onChange, ...props }, ref) => {
45
+ exports.Textarea = React.forwardRef(({ showCount, maxLength, className, wrapperClassName, value, defaultValue, onChange,
46
+ // `style` is pulled out and put on the ROOT, the way Input does it.
47
+ // Everything else belongs on the <textarea> -- id, name, form
48
+ // association, aria -- but `style` sizes the visual box, and the box is
49
+ // the root: it carries the focus ring (an ::after on the wrap) and the
50
+ // character counter. Left on the field, `style={{ maxWidth: 480 }}` gave
51
+ // a 480px control inside a full-width ring with the counter stranded.
52
+ style, ...props }, ref) => {
46
53
  const initial = String(value ?? defaultValue ?? "").length;
47
54
  const [count, setCount] = React.useState(initial);
48
55
  const len = value !== undefined ? String(value).length : count;
49
56
  const counted = showCount && maxLength != null;
50
- return ((0, jsx_runtime_1.jsxs)("div", { className: (0, textarea_recipe_1.textareaRecipe)({ className: wrapperClassName }), "data-counted": counted ? "" : undefined, children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-textarea-wrap", children: (0, jsx_runtime_1.jsx)("textarea", { ref: ref, className: `crisp-textarea-field${className ? ` ${className}` : ""}`, maxLength: maxLength, value: value, defaultValue: defaultValue, onChange: (e) => {
57
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, textarea_recipe_1.textareaRecipe)({ className: wrapperClassName }), style: style, "data-counted": counted ? "" : undefined, children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-textarea-wrap", children: (0, jsx_runtime_1.jsx)("textarea", { ref: ref, className: `crisp-textarea-field${className ? ` ${className}` : ""}`, maxLength: maxLength, value: value, defaultValue: defaultValue, onChange: (e) => {
51
58
  if (value === undefined)
52
59
  setCount(e.target.value.length);
53
60
  onChange?.(e);
@@ -3,9 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.textareaRecipe = void 0;
4
4
  const core_1 = require("../../core");
5
5
  const tokens_1 = require("../../tokens");
6
- // A multiline text field, measured from a settings "Personal context" field: an
7
- // inset 1px ring (radius 8), 14px text, 6/10 padding, and an optional character
8
- // counter pinned bottom-right (12px, muted). Focus rings the field brand.
6
+ // A multiline text field: an inset 1px ring (radius 8), 14px text, and an
7
+ // optional character counter pinned bottom-right (12px, muted). Focus rings
8
+ // the field brand.
9
+ //
10
+ // The inset is seed-design's, not Attio's -- the second deliberate divergence
11
+ // after the focus treatment. seed's text-input sets its inline inset to
12
+ // `$dimension.x3_5`, which rootage/dimension.yaml gives as **14px**, against
13
+ // the 10px this had. seed ships no textarea, so only the horizontal inset is
14
+ // theirs to copy; the vertical is derived from it: 10px above and below a
15
+ // 20px line makes a single-line box 40px tall, which is exactly seed's
16
+ // `$dimension.x10` medium field height. The counter's own inset moves to 14px
17
+ // with it, so it lines up with the text rather than sitting 4px inside it.
9
18
  const config = (0, core_1.defineRecipe)({
10
19
  name: "textarea",
11
20
  base: `
@@ -13,7 +22,7 @@ const config = (0, core_1.defineRecipe)({
13
22
 
14
23
  & .crisp-textarea-field {
15
24
  box-sizing: border-box; display: block; width: 100%; min-height: 80px; resize: vertical;
16
- margin: 0; padding: ${tokens_1.contract.space["1.5"]} ${tokens_1.contract.space["2.5"]}; border: none; border-radius: ${tokens_1.contract.radius.md};
25
+ margin: 0; padding: ${tokens_1.contract.space["2.5"]} 14px; border: none; border-radius: ${tokens_1.contract.radius.md};
17
26
  background: ${tokens_1.contract.bgSurface}; box-shadow: ${tokens_1.elevation.field};
18
27
  font-family: inherit; font-size: ${tokens_1.contract.text.lg}; line-height: 20px; color: ${tokens_1.contract.fgPrimary};
19
28
  outline: none; transition: box-shadow .15s ease;
@@ -35,7 +44,7 @@ const config = (0, core_1.defineRecipe)({
35
44
  &[data-invalid] .crisp-textarea-wrap::after { border-color: ${tokens_1.contract.bgDanger}; }
36
45
  &[data-invalid] .crisp-textarea-wrap:focus-within::after { border-color: ${tokens_1.contract.bgDanger}; }
37
46
  &[data-counted] .crisp-textarea-field { padding-bottom: ${tokens_1.contract.space["5"]}; }
38
- & .crisp-textarea-count { position: absolute; inset-inline-end: ${tokens_1.contract.space["2.5"]}; bottom: ${tokens_1.contract.space["1.5"]}; font-size: ${tokens_1.contract.text.sm}; color: ${tokens_1.contract.fgMuted}; pointer-events: none; }
47
+ & .crisp-textarea-count { position: absolute; inset-inline-end: 14px; bottom: ${tokens_1.contract.space["2.5"]}; font-size: ${tokens_1.contract.text.sm}; color: ${tokens_1.contract.fgMuted}; pointer-events: none; }
39
48
  & .crisp-textarea-count[data-over] { color: ${tokens_1.contract.bgDanger}; }
40
49
  `,
41
50
  variants: {},
@@ -1,16 +1,37 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.tooltipRecipe = void 0;
3
+ exports.spec = exports.tooltipRecipe = void 0;
4
4
  const core_1 = require("../../core");
5
5
  const tokens_1 = require("../../tokens");
6
6
  // The floating bubble for a Tooltip — a dark hint chip. Applied to the
7
7
  // portaled bubble itself (not a wrapper), so its styles survive the portal.
8
- const config = (0, core_1.defineRecipe)({
8
+ const spec = (0, core_1.defineSpec)({
9
9
  name: "tooltip",
10
- base: `position: fixed; z-index: 70; box-sizing: border-box; pointer-events: none; max-width: 260px; padding: ${tokens_1.contract.space["1"]} ${tokens_1.contract.space["2"]}; border-radius: ${tokens_1.contract.radius.sm}; background: #20242c; color: #fff; font-family: ${tokens_1.contract.font.sans}; font-size: ${tokens_1.contract.text.sm}; font-weight: ${tokens_1.contract.weight.medium}; line-height: 1.45; box-shadow: 0 4px 14px -2px rgba(28,40,64,0.28), 0 0 0 1px rgba(255,255,255,0.04); opacity: 1; transition: opacity 0.12s ease; @starting-style { opacity: 0; }`,
11
- variants: {},
12
- defaultVariants: {},
13
- compoundVariants: [],
10
+ base: {
11
+ position: "fixed",
12
+ zIndex: 70,
13
+ boxSizing: "border-box",
14
+ pointerEvents: "none",
15
+ maxWidth: "260px",
16
+ padding: `${tokens_1.contract.space["1"]} ${tokens_1.contract.space["2"]}`,
17
+ borderRadius: tokens_1.contract.radius.sm,
18
+ background: "#20242c",
19
+ color: "#fff",
20
+ fontFamily: tokens_1.contract.font.sans,
21
+ fontSize: tokens_1.contract.text.sm,
22
+ fontWeight: tokens_1.contract.weight.medium,
23
+ lineHeight: "1.45",
24
+ boxShadow: "0 4px 14px -2px rgba(28,40,64,0.28), 0 0 0 1px rgba(255,255,255,0.04)",
25
+ opacity: 1,
26
+ transition: "opacity 0.12s ease",
27
+ },
28
+ parts: [
29
+ // An at-rule whose body is declarations rather than rules, so a part
30
+ // expresses it directly: the entry state the overlay animates from.
31
+ { selector: "@starting-style", decls: { opacity: 0 } },
32
+ ],
14
33
  });
34
+ exports.spec = spec;
35
+ const config = (0, core_1.specToRecipe)(spec);
15
36
  exports.tooltipRecipe = (0, core_1.resolveRecipe)(config);
16
37
  exports.default = config;
@@ -392,7 +392,7 @@ function RecordPage({ record, columns, collection = "Records", labels, onBack, o
392
392
  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 }) }));
393
393
  }
394
394
  };
395
- return ((0, jsx_runtime_1.jsxs)("div", { className: (0, recordpage_recipe_1.recordpageRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-topbar", children: [(0, jsx_runtime_1.jsxs)("nav", { className: "crisp-recordpage-crumbs", "aria-label": t.breadcrumb, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-crumb", onClick: onBack, "aria-label": `Back to ${collection}`, children: collection }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-crumb-sep", children: "/" }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-crumb-name", children: name })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-actions", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { size: 14, strokeWidth: 1.75, color: "var(--bg-brand-solid)" }), t.askAttio] }), onToggleFull && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: onToggleFull, "aria-label": full ? t.exitFullScreen : t.fullScreen, children: full ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Minimize2, { size: 15, strokeWidth: 1.75 })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.Maximize2, { size: 15, strokeWidth: 1.75 })) })), (0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", "aria-label": t.recordActions, trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", "aria-label": t.recordActions, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 16, strokeWidth: 1.75 }) }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }), shortcut: "\u2318C", children: t.copyRecordLink }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), children: t.addToList }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }), onSelect: () => setFavorite((f) => !f), children: favorite ? t.removeFromFavourites : t.addToFavourites }), (0, jsx_runtime_1.jsx)(menu_1.MenuSeparator, {}), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Trash2, { size: 15, strokeWidth: 1.75 }), danger: true, shortcut: "\u232B", children: t.deleteRecord })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-body", children: [(0, jsx_runtime_1.jsxs)("aside", { className: "crisp-recordpage-details", style: { width: detailsW }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-entity", children: [entity?.avatar ? ((0, jsx_runtime_1.jsx)("img", { className: "crisp-recordpage-entity-logo", src: entity.avatar, alt: "" })) : ((0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "lg", shape: "rounded", tone: "brand", children: name.charAt(0) })), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-entity-name", children: name }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-entity-iconbtn", "aria-label": t.editRecord, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Pencil, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.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: (0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-quickactions", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-compose", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Mail, { size: 16, strokeWidth: 1.75 }), t.composeEmail] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.copyRecordLink, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.logANote, children: (0, jsx_runtime_1.jsx)(lucide_react_1.FileText, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.linkRecords, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Share2, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.createATask, children: (0, jsx_runtime_1.jsx)(lucide_react_1.SquareCheck, { size: 15, strokeWidth: 1.75 }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-section", children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-detailhead", children: [searchOpen ? ((0, jsx_runtime_1.jsx)("input", {
395
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, recordpage_recipe_1.recordpageRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-topbar", children: [(0, jsx_runtime_1.jsxs)("nav", { className: "crisp-recordpage-crumbs", "aria-label": t.breadcrumb, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-crumb", onClick: onBack, "aria-label": `Back to ${collection}`, children: collection }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-crumb-sep", children: "/" }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-crumb-name", children: name })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-actions", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--ghost", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { size: 14, strokeWidth: 1.75, color: "var(--crisp-bg-brand-solid)" }), t.askAttio] }), onToggleFull && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", onClick: onToggleFull, "aria-label": full ? t.exitFullScreen : t.fullScreen, children: full ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Minimize2, { size: 15, strokeWidth: 1.75 })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.Maximize2, { size: 15, strokeWidth: 1.75 })) })), (0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", "aria-label": t.recordActions, trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-action crisp-recordpage-action--icon", "aria-label": t.recordActions, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 16, strokeWidth: 1.75 }) }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }), shortcut: "\u2318C", children: t.copyRecordLink }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), children: t.addToList }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }), onSelect: () => setFavorite((f) => !f), children: favorite ? t.removeFromFavourites : t.addToFavourites }), (0, jsx_runtime_1.jsx)(menu_1.MenuSeparator, {}), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Trash2, { size: 15, strokeWidth: 1.75 }), danger: true, shortcut: "\u232B", children: t.deleteRecord })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-body", children: [(0, jsx_runtime_1.jsxs)("aside", { className: "crisp-recordpage-details", style: { width: detailsW }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-entity", children: [entity?.avatar ? ((0, jsx_runtime_1.jsx)("img", { className: "crisp-recordpage-entity-logo", src: entity.avatar, alt: "" })) : ((0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "lg", shape: "rounded", tone: "brand", children: name.charAt(0) })), (0, jsx_runtime_1.jsx)("span", { className: "crisp-recordpage-entity-name", children: name }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-entity-iconbtn", "aria-label": t.editRecord, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Pencil, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.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: (0, jsx_runtime_1.jsx)(lucide_react_1.Star, { size: 15, strokeWidth: 1.75 }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-quickactions", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-recordpage-compose", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Mail, { size: 16, strokeWidth: 1.75 }), t.composeEmail] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.copyRecordLink, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.logANote, children: (0, jsx_runtime_1.jsx)(lucide_react_1.FileText, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.linkRecords, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Share2, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-recordpage-quickbtn", "aria-label": t.createATask, children: (0, jsx_runtime_1.jsx)(lucide_react_1.SquareCheck, { size: 15, strokeWidth: 1.75 }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-section", children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-recordpage-detailhead", children: [searchOpen ? ((0, jsx_runtime_1.jsx)("input", {
396
396
  // biome-ignore lint/a11y/noAutofocus: focusing the just-opened field search is expected
397
397
  autoFocus: true, type: "text", className: "crisp-recordpage-fieldsearch", placeholder: t.searchPlaceholder, "aria-label": t.searchFields, value: fieldQuery, onChange: (e) => setFieldQuery(e.target.value), onKeyDown: (e) => {
398
398
  if (e.key === "Escape") {
package/dist/command.css CHANGED
@@ -1,5 +1,5 @@
1
1
  .crisp-command {
2
- display: contents; font-family: var(--crisp-font-sans) ;
2
+ display: contents; font-family: var(--crisp-font-sans);
3
3
  & .crisp-command-content, & .crisp-command-card { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
4
4
  & .crisp-command-backdrop { position: fixed; inset: 0; z-index: 50; background: transparent; }
5
5
  & .crisp-command-positioner { position: fixed; inset: 0; z-index: 51; display: flex; align-items: flex-start; justify-content: center; padding: 15vh var(--crisp-space-4) var(--crisp-space-4); }
@@ -1,6 +1,6 @@
1
1
  @layer crisp-components {
2
2
  .crisp-command {
3
- display: contents; font-family: var(--crisp-font-sans) ;
3
+ display: contents; font-family: var(--crisp-font-sans);
4
4
  & .crisp-command-content, & .crisp-command-card { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
5
5
  & .crisp-command-backdrop { position: fixed; inset: 0; z-index: 50; background: transparent; }
6
6
  & .crisp-command-positioner { position: fixed; inset: 0; z-index: 51; display: flex; align-items: flex-start; justify-content: center; padding: 15vh var(--crisp-space-4) var(--crisp-space-4); }
@@ -1,15 +1,28 @@
1
- import { defineRecipe, resolveRecipe } from "../../core/index.js";
1
+ import { defineSpec, resolveRecipe, specToRecipe } from "../../core/index.js";
2
2
  import { contract as c } from "../../tokens/index.js";
3
3
  // A row of overlapping avatars — each ringed in the surface colour so it reads
4
4
  // against its neighbour, with a "+N" overflow chip.
5
- const config = defineRecipe({
5
+ const spec = defineSpec({
6
6
  name: "avatargroup",
7
- base: `display: inline-flex; align-items: center;
8
- & > * { margin-inline-start: -${c.space["2"]}; box-shadow: 0 0 0 2px ${c.bgSurface}; position: relative; }
9
- & > *:first-child { margin-inline-start: 0; }`,
10
- variants: {},
11
- defaultVariants: {},
12
- compoundVariants: [],
7
+ base: { display: "inline-flex", alignItems: "center" },
8
+ parts: [
9
+ {
10
+ slot: "member",
11
+ selector: "& > *",
12
+ decls: {
13
+ marginInlineStart: `-${c.space["2"]}`,
14
+ boxShadow: `0 0 0 2px ${c.bgSurface}`,
15
+ position: "relative",
16
+ },
17
+ },
18
+ {
19
+ slot: "member",
20
+ selector: "& > *:first-child",
21
+ decls: { marginInlineStart: 0 },
22
+ },
23
+ ],
13
24
  });
25
+ const config = specToRecipe(spec);
14
26
  export const avatargroupRecipe = resolveRecipe(config);
27
+ export { spec };
15
28
  export default config;
@@ -31,9 +31,14 @@ export function BarChart({ data, height = 240, max, ticks = 5, color, format = (
31
31
  : `max(34px, ${longestTickChars}ch + ${c.space["4"]})`;
32
32
  const summary = ariaLabel ??
33
33
  `Bar chart: ${data.map((d) => `${d.label} ${format(d.value)}`).join(", ")}`;
34
- return (_jsxs("div", { className: barchartRecipe({ className }), ...rest, style: {
34
+ return (_jsxs("div", { className: barchartRecipe({ className }), ...rest,
35
+ // merged, not assigned: written plainly this sits AFTER `{...rest}` and
36
+ // overwrites whatever style the consumer passed, so `style={{ maxWidth }}`
37
+ // on the chart did nothing at all. The consumer's wins on collision.
38
+ style: {
35
39
  height,
36
40
  "--crisp-barchart-yaxis-width": yAxisWidthValue,
41
+ ...rest.style,
37
42
  }, role: "img", "aria-label": summary, children: [_jsxs("div", { className: "crisp-barchart-body", children: [_jsx("div", { className: "crisp-barchart-yaxis", children: tickValues.map((t) => (_jsx("span", { className: "crisp-barchart-ytick", style: { bottom: `${(t / top) * 100}%` }, children: format(t) }, t))) }), _jsxs("div", { className: "crisp-barchart-plot", children: [tickValues.map((t) => (_jsx("div", { className: "crisp-barchart-grid", style: { bottom: `${(t / top) * 100}%` } }, t))), data.map((d) => (_jsx("div", { className: "crisp-barchart-col", children: _jsx("div", { className: "crisp-barchart-bar", "data-empty": d.value <= 0 ? "" : undefined, style: {
38
43
  height: `${Math.max(0, (d.value / top) * 100)}%`,
39
44
  background: color,
@@ -1,25 +1,58 @@
1
- import { defineRecipe, resolveRecipe } from "../../core/index.js";
1
+ import { defineSpec, resolveRecipe, specToRecipe } from "../../core/index.js";
2
2
  import { contract as c } from "../../tokens/index.js";
3
3
  // A 16px square checkbox measured from a record grid: r6, a subtle 1px
4
4
  // inset ring when empty, brand fill + white check when on. The check is a
5
5
  // data-URI SVG background so it renders reliably (input pseudo-content doesn't).
6
6
  const CHECK = "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 6.5 5 9l4.5-5'/%3E%3C/svg%3E\")";
7
- const config = defineRecipe({
7
+ const spec = defineSpec({
8
8
  name: "checkbox",
9
- base: `appearance: none; -webkit-appearance: none; box-sizing: border-box; flex-shrink: 0; margin: 0; width: 16px; height: 16px; border: none; border-radius: ${c.radius.sm}
10
- ; background: ${c.bgSurface}
11
- ; box-shadow: inset 0 0 0 1px color-mix(in srgb, ${c.fgPrimary}
12
- 9%, transparent); cursor: pointer; transition: background-color .12s, box-shadow .12s; &:hover { box-shadow: inset 0 0 0 1px color-mix(in srgb, ${c.fgPrimary} 30%, transparent); }
13
- &:checked, &:indeterminate { background: ${c.bgBrandSolid} center / 10px no-repeat; box-shadow: none; }
14
- &:checked { background-image: ${CHECK}; }
15
- &:indeterminate { background-image: linear-gradient(${c.fgOnBrand}, ${c.fgOnBrand}); background-size: 8px 2px; }
16
- &:focus-visible { outline: 2px solid ${c.bgBrandSolid}; outline-offset: 1px; }
17
- `,
18
- variants: {},
19
- defaultVariants: {},
20
- compoundVariants: [],
21
- });
22
- export const checkboxRecipe = Object.assign(resolveRecipe(config), {
23
- config,
9
+ base: {
10
+ appearance: "none",
11
+ WebkitAppearance: "none",
12
+ boxSizing: "border-box",
13
+ flexShrink: 0,
14
+ margin: 0,
15
+ width: "16px",
16
+ height: "16px",
17
+ border: "none",
18
+ borderRadius: c.radius.sm,
19
+ background: c.bgSurface,
20
+ boxShadow: `inset 0 0 0 1px color-mix(in srgb, ${c.fgPrimary} 9%, transparent)`,
21
+ cursor: "pointer",
22
+ transition: "background-color .12s, box-shadow .12s",
23
+ },
24
+ parts: [
25
+ {
26
+ selector: "&:hover",
27
+ decls: {
28
+ boxShadow: `inset 0 0 0 1px color-mix(in srgb, ${c.fgPrimary} 30%, transparent)`,
29
+ },
30
+ },
31
+ {
32
+ selector: "&:checked, &:indeterminate",
33
+ decls: {
34
+ background: `${c.bgBrandSolid} center / 10px no-repeat`,
35
+ boxShadow: "none",
36
+ },
37
+ },
38
+ { selector: "&:checked", decls: { backgroundImage: CHECK } },
39
+ {
40
+ selector: "&:indeterminate",
41
+ decls: {
42
+ backgroundImage: `linear-gradient(${c.fgOnBrand}, ${c.fgOnBrand})`,
43
+ backgroundSize: "8px 2px",
44
+ },
45
+ },
46
+ {
47
+ selector: "&:focus-visible",
48
+ decls: {
49
+ outline: `2px solid ${c.bgBrandSolid}`,
50
+ outlineOffset: "1px",
51
+ },
52
+ },
53
+ ],
24
54
  });
55
+ const config = specToRecipe(spec);
56
+ export const checkboxRecipe = Object.assign(resolveRecipe(config), { config });
57
+ export { spec };
25
58
  export default config;
@@ -90,7 +90,13 @@ const DatePickerImplementation = React.forwardRef((props, forwardedRef) => {
90
90
  }, ...mergeProps(api.rootProps, rootProps), "aria-label": rootAriaLabel, "aria-labelledby": ariaLabelledby, className: datepickerRecipe({
91
91
  visibleRange: api.visibleRange,
92
92
  className,
93
- }), style: styleOf({ height, minHeight, maxHeight }), children: [api.visibleRange === "continuous" ? (_jsxs("div", { ref: api.refs.continuousScroll, ...api.continuousScrollProps, "data-date-picker-continuous-scroll": "", className: "crisp-datepicker-continuous-scroll", children: [_jsx(WeekdayRow, { api: api, semantic: false }), _jsxs("div", { className: "crisp-datepicker-continuous-content", children: [_jsx("div", { "aria-hidden": "true", className: "datepicker-continuous-spacer", style: { height: api.virtual.topHeight } }), api.months.map((month) => (_jsx(MonthView, { api: api, month: month, renderDateCellContent: renderDateCellContent, renderDateCellSupplement: renderDateCellSupplement, showWeekdays: false, showMonthLabel: true, monthRef: api.refs.continuousMonth(month.key) }, month.key))), _jsx("div", { "aria-hidden": "true", className: "datepicker-continuous-spacer", style: { height: api.virtual.bottomHeight } })] })] })) : (_jsxs(_Fragment, { children: [api.visibleRange !== "twoMonths" && _jsx(Header, { api: api }), _jsx("div", { "data-wheel-open": api.isWheelOpen && api.visibleRange !== "twoMonths"
93
+ }),
94
+ // merged: this sits after the spread above, so assigning it plainly
95
+ // dropped any style the consumer passed. Theirs wins on collision.
96
+ style: {
97
+ ...styleOf({ height, minHeight, maxHeight }),
98
+ ...rootProps?.style,
99
+ }, children: [api.visibleRange === "continuous" ? (_jsxs("div", { ref: api.refs.continuousScroll, ...api.continuousScrollProps, "data-date-picker-continuous-scroll": "", className: "crisp-datepicker-continuous-scroll", children: [_jsx(WeekdayRow, { api: api, semantic: false }), _jsxs("div", { className: "crisp-datepicker-continuous-content", children: [_jsx("div", { "aria-hidden": "true", className: "datepicker-continuous-spacer", style: { height: api.virtual.topHeight } }), api.months.map((month) => (_jsx(MonthView, { api: api, month: month, renderDateCellContent: renderDateCellContent, renderDateCellSupplement: renderDateCellSupplement, showWeekdays: false, showMonthLabel: true, monthRef: api.refs.continuousMonth(month.key) }, month.key))), _jsx("div", { "aria-hidden": "true", className: "datepicker-continuous-spacer", style: { height: api.virtual.bottomHeight } })] })] })) : (_jsxs(_Fragment, { children: [api.visibleRange !== "twoMonths" && _jsx(Header, { api: api }), _jsx("div", { "data-wheel-open": api.isWheelOpen && api.visibleRange !== "twoMonths"
94
100
  ? ""
95
101
  : undefined, "aria-hidden": api.isWheelOpen && api.visibleRange !== "twoMonths"
96
102
  ? true