@crisp-ui-kit/crisp 0.33.3 → 0.33.5

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 (44) 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/datatable/datatable.recipe.js +1 -1
  7. package/dist/cjs/components/datepicker/datepicker.js +7 -1
  8. package/dist/cjs/components/kbd/kbd.recipe.js +22 -9
  9. package/dist/cjs/components/linechart/linechart.js +6 -1
  10. package/dist/cjs/components/skeleton/skeleton.recipe.js +14 -8
  11. package/dist/cjs/components/table/table.js +6 -2
  12. package/dist/cjs/components/textarea/textarea.js +9 -2
  13. package/dist/cjs/components/textarea/textarea.recipe.js +14 -5
  14. package/dist/cjs/components/tooltip/tooltip.recipe.js +27 -6
  15. package/dist/command.css +1 -1
  16. package/dist/command.layered.css +1 -1
  17. package/dist/datatable.css +2 -2
  18. package/dist/datatable.layered.css +2 -2
  19. package/dist/esm/components/avatargroup/avatargroup.recipe.js +21 -8
  20. package/dist/esm/components/barchart/barchart.js +6 -1
  21. package/dist/esm/components/checkbox/checkbox.recipe.js +50 -17
  22. package/dist/esm/components/datatable/datatable.recipe.js +1 -1
  23. package/dist/esm/components/datepicker/datepicker.js +7 -1
  24. package/dist/esm/components/kbd/kbd.recipe.js +22 -9
  25. package/dist/esm/components/linechart/linechart.js +6 -1
  26. package/dist/esm/components/skeleton/skeleton.recipe.js +14 -8
  27. package/dist/esm/components/table/table.js +6 -2
  28. package/dist/esm/components/textarea/textarea.js +9 -2
  29. package/dist/esm/components/textarea/textarea.recipe.js +14 -5
  30. package/dist/esm/components/tooltip/tooltip.recipe.js +27 -6
  31. package/dist/navitem.css +1 -1
  32. package/dist/navitem.layered.css +1 -1
  33. package/dist/sidebar.css +1 -1
  34. package/dist/sidebar.layered.css +1 -1
  35. package/dist/styles.css +8 -8
  36. package/dist/styles.layered.css +8 -8
  37. package/dist/textarea.css +2 -2
  38. package/dist/textarea.layered.css +2 -2
  39. package/dist/types/components/avatargroup/avatargroup.recipe.d.ts +25 -2
  40. package/dist/types/components/checkbox/checkbox.recipe.d.ts +51 -3
  41. package/dist/types/components/kbd/kbd.recipe.d.ts +24 -3
  42. package/dist/types/components/skeleton/skeleton.recipe.d.ts +19 -2
  43. package/dist/types/components/tooltip/tooltip.recipe.d.ts +30 -2
  44. package/package.json +5 -2
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;
@@ -17,12 +17,12 @@ const config = (0, core_1.defineRecipe)({
17
17
  & .crisp-datatable-search input::placeholder { color: ${tokens_1.contract.fgMuted}; }
18
18
  & .crisp-datatable-search input:focus { box-shadow: inset 0 0 0 1px ${tokens_1.contract.bgBrandSolid}; }
19
19
  & .crisp-datatable-count { margin-inline-start: auto; font-size: ${tokens_1.contract.text.md}; color: ${tokens_1.contract.fgMuted}; }
20
- /* The header cell has no padding of its own -- the button owns the
21
20
  /* announced, never drawn */
22
21
  & .crisp-datatable-live {
23
22
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
24
23
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
25
24
  }
25
+ /* The header cell has no padding of its own -- the button owns the
26
26
  full cell so the whole header is clickable (its own inset is 12px).
27
27
  Mirrors the summary-row fix. */
28
28
  & th { padding: 0; }
@@ -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;
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); }
@@ -7,9 +7,9 @@
7
7
  & .crisp-datatable-search input::placeholder { color: var(--crisp-fg-muted); }
8
8
  & .crisp-datatable-search input:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
9
9
  & .crisp-datatable-count { margin-inline-start: auto; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
10
- /* The header cell has no padding of its own -- the button owns the /* announced, never drawn */
10
+ /* announced, never drawn */
11
11
  & .crisp-datatable-live { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
12
- full cell so the whole header is clickable (its own inset is 12px). Mirrors the summary-row fix. */
12
+ /* The header cell has no padding of its own -- the button owns the full cell so the whole header is clickable (its own inset is 12px). Mirrors the summary-row fix. */
13
13
  & th { padding: 0; }
14
14
  /* Same wrapper trap as AUTHORING.md Lesson 40 (ColumnCalc's summary trigger):
15
15
  Menu wraps its trigger in a flex div that sizes to its content, so a
@@ -8,9 +8,9 @@
8
8
  & .crisp-datatable-search input::placeholder { color: var(--crisp-fg-muted); }
9
9
  & .crisp-datatable-search input:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
10
10
  & .crisp-datatable-count { margin-inline-start: auto; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
11
- /* The header cell has no padding of its own -- the button owns the /* announced, never drawn */
11
+ /* announced, never drawn */
12
12
  & .crisp-datatable-live { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
13
- full cell so the whole header is clickable (its own inset is 12px). Mirrors the summary-row fix. */
13
+ /* The header cell has no padding of its own -- the button owns the full cell so the whole header is clickable (its own inset is 12px). Mirrors the summary-row fix. */
14
14
  & th { padding: 0; }
15
15
  /* Same wrapper trap as AUTHORING.md Lesson 40 (ColumnCalc's summary trigger):
16
16
  Menu wraps its trigger in a flex div that sizes to its content, so a
@@ -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;
@@ -14,12 +14,12 @@ const config = defineRecipe({
14
14
  & .crisp-datatable-search input::placeholder { color: ${c.fgMuted}; }
15
15
  & .crisp-datatable-search input:focus { box-shadow: inset 0 0 0 1px ${c.bgBrandSolid}; }
16
16
  & .crisp-datatable-count { margin-inline-start: auto; font-size: ${c.text.md}; color: ${c.fgMuted}; }
17
- /* The header cell has no padding of its own -- the button owns the
18
17
  /* announced, never drawn */
19
18
  & .crisp-datatable-live {
20
19
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
21
20
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
22
21
  }
22
+ /* The header cell has no padding of its own -- the button owns the
23
23
  full cell so the whole header is clickable (its own inset is 12px).
24
24
  Mirrors the summary-row fix. */
25
25
  & th { padding: 0; }