@crisp-ui-kit/crisp 0.57.0 → 0.57.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/accentpicker.css +5 -0
- package/dist/accentpicker.d.ts +7 -0
- package/dist/accentpicker.layered.css +7 -0
- package/dist/avatar.css +1 -1
- package/dist/avatar.layered.css +1 -1
- package/dist/badge.css +2 -2
- package/dist/badge.layered.css +2 -2
- package/dist/button.css +3 -3
- package/dist/button.layered.css +3 -3
- package/dist/cjs/components/accentpicker/accentpicker.js +66 -0
- package/dist/cjs/components/accentpicker/accentpicker.recipe.js +25 -0
- package/dist/cjs/components/accentpicker/index.js +7 -0
- package/dist/cjs/components/avatar/avatar.recipe.js +4 -1
- package/dist/cjs/components/badge/badge.recipe.js +16 -2
- package/dist/cjs/components/button/button.recipe.js +16 -6
- package/dist/cjs/components/input/input.js +2 -2
- package/dist/cjs/components/input/input.recipe.js +8 -0
- package/dist/cjs/components/multiselect/multiselect.js +1 -1
- package/dist/cjs/components/multiselect/multiselect.recipe.js +12 -3
- package/dist/cjs/components/progress/progress.js +21 -4
- package/dist/cjs/components/progress/progress.recipe.js +30 -0
- package/dist/cjs/components/select/select.js +2 -2
- package/dist/cjs/components/select/select.recipe.js +32 -5
- package/dist/cjs/components/text/text.recipe.js +8 -7
- package/dist/cjs/components/textarea/textarea.js +3 -2
- package/dist/cjs/components/textarea/textarea.recipe.js +2 -1
- package/dist/cjs/components/textlink/textlink.recipe.js +6 -3
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/product/recordpage/recordpage.js +46 -5
- package/dist/cjs/tokens/palette.js +79 -25
- package/dist/cjs/tokens/scale.js +1 -0
- package/dist/cjs/tokens/semantic.js +24 -0
- package/dist/esm/components/accentpicker/accentpicker.js +30 -0
- package/dist/esm/components/accentpicker/accentpicker.recipe.js +22 -0
- package/dist/esm/components/accentpicker/index.js +2 -0
- package/dist/esm/components/avatar/avatar.recipe.js +4 -1
- package/dist/esm/components/badge/badge.recipe.js +16 -2
- package/dist/esm/components/button/button.recipe.js +16 -6
- package/dist/esm/components/input/input.js +3 -3
- package/dist/esm/components/input/input.recipe.js +8 -0
- package/dist/esm/components/multiselect/multiselect.js +1 -1
- package/dist/esm/components/multiselect/multiselect.recipe.js +12 -3
- package/dist/esm/components/progress/progress.js +22 -5
- package/dist/esm/components/progress/progress.recipe.js +30 -0
- package/dist/esm/components/select/select.js +2 -2
- package/dist/esm/components/select/select.recipe.js +32 -5
- package/dist/esm/components/text/text.recipe.js +8 -7
- package/dist/esm/components/textarea/textarea.js +3 -2
- package/dist/esm/components/textarea/textarea.recipe.js +2 -1
- package/dist/esm/components/textlink/textlink.recipe.js +6 -3
- package/dist/esm/index.js +1 -0
- package/dist/esm/product/recordpage/recordpage.js +47 -6
- package/dist/esm/tokens/palette.js +79 -25
- package/dist/esm/tokens/scale.js +1 -0
- package/dist/esm/tokens/semantic.js +24 -0
- package/dist/input.css +7 -0
- package/dist/input.layered.css +7 -0
- package/dist/multiselect.css +4 -0
- package/dist/multiselect.d.ts +2 -2
- package/dist/multiselect.layered.css +4 -0
- package/dist/native/tokens.d.ts +1 -1
- package/dist/native/tokens.js +33 -22
- package/dist/progress.css +3 -0
- package/dist/progress.layered.css +3 -0
- package/dist/select.css +22 -2
- package/dist/select.d.ts +2 -2
- package/dist/select.layered.css +22 -2
- package/dist/styles.css +71 -23
- package/dist/styles.layered.css +71 -23
- package/dist/text.css +1 -1
- package/dist/text.layered.css +1 -1
- package/dist/textarea.css +2 -1
- package/dist/textarea.layered.css +2 -1
- package/dist/textlink.css +2 -2
- package/dist/textlink.layered.css +2 -2
- package/dist/tokens.css +18 -11
- package/dist/tokens.json +33 -22
- package/dist/types/components/accentpicker/accentpicker.d.ts +23 -0
- package/dist/types/components/accentpicker/accentpicker.recipe.d.ts +3 -0
- package/dist/types/components/accentpicker/index.d.ts +2 -0
- package/dist/types/components/button/button.recipe.d.ts +2 -2
- package/dist/types/components/input/input.d.ts +12 -0
- package/dist/types/components/multiselect/multiselect.d.ts +4 -3
- package/dist/types/components/multiselect/multiselect.recipe.d.ts +3 -0
- package/dist/types/components/progress/progress.d.ts +16 -4
- package/dist/types/components/progress/progress.recipe.d.ts +22 -0
- package/dist/types/components/select/select.d.ts +6 -5
- package/dist/types/components/select/select.recipe.d.ts +3 -0
- package/dist/types/components/textarea/textarea.d.ts +16 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/product/recordpage/recordpage.d.ts +37 -1
- package/dist/types/tokens/contract.d.ts +4 -1
- package/dist/types/tokens/palette.d.ts +19 -11
- package/dist/types/tokens/scale.d.ts +1 -0
- package/dist/types/tokens/semantic.d.ts +15 -0
- package/package.json +6 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
.crisp-accentpicker {
|
|
2
|
+
display: flex; gap: var(--crisp-space-2);
|
|
3
|
+
& .crisp-accentpicker-swatch { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; padding: 0; margin: 0; border-radius: var(--crisp-radius-full); border: 1px solid transparent; background: none; font-family: inherit; cursor: pointer; }
|
|
4
|
+
& .crisp-accentpicker-swatch:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 2px; }
|
|
5
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
@layer crisp-components {
|
|
2
|
+
.crisp-accentpicker {
|
|
3
|
+
display: flex; gap: var(--crisp-space-2);
|
|
4
|
+
& .crisp-accentpicker-swatch { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; padding: 0; margin: 0; border-radius: var(--crisp-radius-full); border: 1px solid transparent; background: none; font-family: inherit; cursor: pointer; }
|
|
5
|
+
& .crisp-accentpicker-swatch:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 2px; }
|
|
6
|
+
}
|
|
7
|
+
}
|
package/dist/avatar.css
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
background: var(--crisp-bg-raised); color: var(--crisp-fg-neutral-muted); box-shadow: inset 0 0 0 1px var(--crisp-border-default);
|
|
25
25
|
}
|
|
26
26
|
.crisp-avatar--tone_brand {
|
|
27
|
-
background: var(--crisp-bg-brand-
|
|
27
|
+
background: var(--crisp-bg-brand-contrast); color: #fff;
|
|
28
28
|
}
|
|
29
29
|
.crisp-avatar--tone_orange {
|
|
30
30
|
background: var(--crisp-bg-orange); color: #fff;
|
package/dist/avatar.layered.css
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
background: var(--crisp-bg-raised); color: var(--crisp-fg-neutral-muted); box-shadow: inset 0 0 0 1px var(--crisp-border-default);
|
|
26
26
|
}
|
|
27
27
|
.crisp-avatar--tone_brand {
|
|
28
|
-
background: var(--crisp-bg-brand-
|
|
28
|
+
background: var(--crisp-bg-brand-contrast); color: #fff;
|
|
29
29
|
}
|
|
30
30
|
.crisp-avatar--tone_orange {
|
|
31
31
|
background: var(--crisp-bg-orange); color: #fff;
|
package/dist/badge.css
CHANGED
|
@@ -36,13 +36,13 @@
|
|
|
36
36
|
|
|
37
37
|
}
|
|
38
38
|
.crisp-badge--compound_0 {
|
|
39
|
-
background: var(--crisp-bg-brand-
|
|
39
|
+
background: var(--crisp-bg-brand-contrast); color: var(--crisp-fg-on-brand); box-shadow: none;
|
|
40
40
|
}
|
|
41
41
|
.crisp-badge--compound_1 {
|
|
42
42
|
background: var(--crisp-bg-success); color: var(--crisp-fg-on-tone); box-shadow: none;
|
|
43
43
|
}
|
|
44
44
|
.crisp-badge--compound_2 {
|
|
45
|
-
background: var(--crisp-bg-warning); color: var(--crisp-fg-on-tone); box-shadow: none;
|
|
45
|
+
background: var(--crisp-bg-warning-solid); color: var(--crisp-fg-on-tone); box-shadow: none;
|
|
46
46
|
}
|
|
47
47
|
.crisp-badge--compound_3 {
|
|
48
48
|
background: var(--crisp-bg-danger); color: var(--crisp-fg-on-tone); box-shadow: none;
|
package/dist/badge.layered.css
CHANGED
|
@@ -37,13 +37,13 @@
|
|
|
37
37
|
|
|
38
38
|
}
|
|
39
39
|
.crisp-badge--compound_0 {
|
|
40
|
-
background: var(--crisp-bg-brand-
|
|
40
|
+
background: var(--crisp-bg-brand-contrast); color: var(--crisp-fg-on-brand); box-shadow: none;
|
|
41
41
|
}
|
|
42
42
|
.crisp-badge--compound_1 {
|
|
43
43
|
background: var(--crisp-bg-success); color: var(--crisp-fg-on-tone); box-shadow: none;
|
|
44
44
|
}
|
|
45
45
|
.crisp-badge--compound_2 {
|
|
46
|
-
background: var(--crisp-bg-warning); color: var(--crisp-fg-on-tone); box-shadow: none;
|
|
46
|
+
background: var(--crisp-bg-warning-solid); color: var(--crisp-fg-on-tone); box-shadow: none;
|
|
47
47
|
}
|
|
48
48
|
.crisp-badge--compound_3 {
|
|
49
49
|
background: var(--crisp-bg-danger); color: var(--crisp-fg-on-tone); box-shadow: none;
|
package/dist/button.css
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
&:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
4
4
|
}
|
|
5
5
|
.crisp-button--intent_primary {
|
|
6
|
-
background: var(--crisp-bg-brand-
|
|
7
|
-
&:hover {
|
|
8
|
-
&:active {
|
|
6
|
+
background: var(--crisp-bg-brand-contrast); color: var(--crisp-fg-on-brand); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08);
|
|
7
|
+
&:hover { filter: brightness(0.96); }
|
|
8
|
+
&:active { filter: brightness(0.92); }
|
|
9
9
|
}
|
|
10
10
|
.crisp-button--intent_neutral {
|
|
11
11
|
background: var(--crisp-bg-surface); color: var(--crisp-fg-primary); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04);
|
package/dist/button.layered.css
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
&:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
5
5
|
}
|
|
6
6
|
.crisp-button--intent_primary {
|
|
7
|
-
background: var(--crisp-bg-brand-
|
|
8
|
-
&:hover {
|
|
9
|
-
&:active {
|
|
7
|
+
background: var(--crisp-bg-brand-contrast); color: var(--crisp-fg-on-brand); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08);
|
|
8
|
+
&:hover { filter: brightness(0.96); }
|
|
9
|
+
&:active { filter: brightness(0.92); }
|
|
10
10
|
}
|
|
11
11
|
.crisp-button--intent_neutral {
|
|
12
12
|
background: var(--crisp-bg-surface); color: var(--crisp-fg-primary); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AccentPicker = AccentPicker;
|
|
37
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
38
|
+
const React = __importStar(require("react"));
|
|
39
|
+
const radiogroup_1 = require("../radiogroup");
|
|
40
|
+
const accentpicker_recipe_1 = require("./accentpicker.recipe");
|
|
41
|
+
/**
|
|
42
|
+
* A row of selectable colour swatches — Attio's own accent-colour picker
|
|
43
|
+
* (Settings > Appearance). Not a fixed palette: pass the colours (and the
|
|
44
|
+
* accent isn't crisp's concern to theme the app with — this only picks one).
|
|
45
|
+
* Same roving-focus/arrow-key model every other crisp radio-like control
|
|
46
|
+
* shares (`useRadioGroupKeyboard`, also used by `RadioGroup` and
|
|
47
|
+
* `Segmented`).
|
|
48
|
+
*/
|
|
49
|
+
function AccentPicker({ colors, value, defaultValue, onValueChange, className, onKeyDown: onKeyDownProp, ...rest }) {
|
|
50
|
+
const [internal, setInternal] = React.useState(defaultValue);
|
|
51
|
+
const selected = value ?? internal;
|
|
52
|
+
const select = (v) => {
|
|
53
|
+
if (value === undefined)
|
|
54
|
+
setInternal(v);
|
|
55
|
+
onValueChange?.(v);
|
|
56
|
+
};
|
|
57
|
+
const { onKeyDown, groupRef } = (0, radiogroup_1.useRadioGroupKeyboard)({ select });
|
|
58
|
+
return ((0, jsx_runtime_1.jsx)("div", { ref: groupRef, role: "radiogroup", className: (0, accentpicker_recipe_1.accentpickerRecipe)({ className }), ...rest, onKeyDown: (e) => {
|
|
59
|
+
onKeyDown(e);
|
|
60
|
+
onKeyDownProp?.(e);
|
|
61
|
+
}, children: colors.map((color) => {
|
|
62
|
+
const checked = selected === color;
|
|
63
|
+
return ((0, jsx_runtime_1.jsx)("button", { type: "button", role: "radio", "aria-checked": checked, "aria-label": color, "data-value": color, tabIndex: checked ? 0 : -1, className: "crisp-accentpicker-swatch", style: { borderColor: checked ? color : "transparent" }, onClick: () => select(color), children: (0, jsx_runtime_1.jsx)("svg", { width: "22", height: "22", viewBox: "0 0 22 22", fill: "none", "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)("circle", { cx: "11", cy: "11", r: "9", fill: color }) }) }, color));
|
|
64
|
+
}) }));
|
|
65
|
+
}
|
|
66
|
+
AccentPicker.displayName = "AccentPicker";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.accentpickerRecipe = void 0;
|
|
4
|
+
const core_1 = require("../../core");
|
|
5
|
+
const tokens_1 = require("../../tokens");
|
|
6
|
+
// A row of selectable colour swatches — measured live on Attio's Appearance
|
|
7
|
+
// settings page (crisp#635): `role="radiogroup"`, `display: flex; gap: 8px`.
|
|
8
|
+
// Each swatch is a 24x24 circular button, `border: 1px solid transparent`
|
|
9
|
+
// at rest; the CHECKED swatch's border switches to its own colour (not a
|
|
10
|
+
// fixed brand ring) — measured directly on the DOM (`border: 1px solid
|
|
11
|
+
// rgb(38,109,240)` on the checked blue swatch, `1px solid rgba(0,0,0,0)` on
|
|
12
|
+
// every unchecked one). That per-swatch colour is data, not a class, so it's
|
|
13
|
+
// set inline by the component, not here. Inside sits a 22x22 SVG with one
|
|
14
|
+
// solid r=9 (18px) circle -- no separate ring/dot layer, contrary to an
|
|
15
|
+
// earlier, less precise read of this same control.
|
|
16
|
+
const config = (0, core_1.defineRecipe)({
|
|
17
|
+
name: "accentpicker",
|
|
18
|
+
base: `display: flex; gap: ${tokens_1.contract.space["2"]};
|
|
19
|
+
& .crisp-accentpicker-swatch { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center;
|
|
20
|
+
width: 24px; height: 24px; padding: 0; margin: 0; border-radius: ${tokens_1.contract.radius.full}; border: 1px solid transparent;
|
|
21
|
+
background: none; font-family: inherit; cursor: pointer; }
|
|
22
|
+
& .crisp-accentpicker-swatch:focus-visible { outline: 2px solid ${tokens_1.contract.bgBrandSolid}; outline-offset: 2px; }`,
|
|
23
|
+
});
|
|
24
|
+
exports.accentpickerRecipe = (0, core_1.resolveRecipe)(config);
|
|
25
|
+
exports.default = config;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.accentpickerRecipe = exports.AccentPicker = void 0;
|
|
4
|
+
var accentpicker_1 = require("./accentpicker");
|
|
5
|
+
Object.defineProperty(exports, "AccentPicker", { enumerable: true, get: function () { return accentpicker_1.AccentPicker; } });
|
|
6
|
+
var accentpicker_recipe_1 = require("./accentpicker.recipe");
|
|
7
|
+
Object.defineProperty(exports, "accentpickerRecipe", { enumerable: true, get: function () { return accentpicker_recipe_1.accentpickerRecipe; } });
|
|
@@ -39,7 +39,10 @@ const spec = (0, core_1.defineSpec)({
|
|
|
39
39
|
color: tokens_1.contract.fgNeutralMuted,
|
|
40
40
|
boxShadow: `inset 0 0 0 1px ${tokens_1.contract.borderDefault}`,
|
|
41
41
|
},
|
|
42
|
-
|
|
42
|
+
// your-moon/crisp#897: `bgBrandContrast`, the darker AA-safe sibling
|
|
43
|
+
// of the accent blue (`bgBrandSolid`, #0298d9, only 3.23:1 with
|
|
44
|
+
// white) -- keeps white text, matching every other tone here.
|
|
45
|
+
brand: { background: tokens_1.contract.bgBrandContrast, color: "#fff" },
|
|
43
46
|
orange: { background: tokens_1.contract.bgOrange, color: "#fff" },
|
|
44
47
|
cyan: { background: tokens_1.contract.bgCyan, color: "#fff" },
|
|
45
48
|
purple: { background: tokens_1.contract.bgPurple, color: "#fff" },
|
|
@@ -129,8 +129,14 @@ const spec = (0, core_1.defineSpec)({
|
|
|
129
129
|
{
|
|
130
130
|
variant: "solid",
|
|
131
131
|
tone: "brand",
|
|
132
|
+
// your-moon/crisp#897: `bgBrandContrast`, not `bgBrandSolid` — white
|
|
133
|
+
// text on the exact accent blue (#0298d9) only clears 3.23:1;
|
|
134
|
+
// `bgBrandContrast` is the darker sibling built to clear 4.5:1+ with
|
|
135
|
+
// white, so `fgOnBrand` (white) stays right rather than `fgOnTone`
|
|
136
|
+
// (ink read off-brand next to solid `success`/`warning`/`danger`,
|
|
137
|
+
// which really do need it).
|
|
132
138
|
decls: {
|
|
133
|
-
background: tokens_1.contract.
|
|
139
|
+
background: tokens_1.contract.bgBrandContrast,
|
|
134
140
|
color: tokens_1.contract.fgOnBrand,
|
|
135
141
|
boxShadow: "none",
|
|
136
142
|
},
|
|
@@ -143,7 +149,15 @@ const spec = (0, core_1.defineSpec)({
|
|
|
143
149
|
{
|
|
144
150
|
variant: "solid",
|
|
145
151
|
tone: "warning",
|
|
146
|
-
|
|
152
|
+
// seed-identity migration (your-moon/crisp#890): `bgWarningSolid`, not
|
|
153
|
+
// `bgWarning` — seed's own solid warning fill is a distinct, paler
|
|
154
|
+
// yellow from the accent every other tone's solid fill reuses
|
|
155
|
+
// directly (see tokens/palette.ts's `amberSolid` comment).
|
|
156
|
+
decls: {
|
|
157
|
+
background: tokens_1.contract.bgWarningSolid,
|
|
158
|
+
color: tokens_1.contract.fgOnTone,
|
|
159
|
+
boxShadow: "none",
|
|
160
|
+
},
|
|
147
161
|
},
|
|
148
162
|
{
|
|
149
163
|
variant: "solid",
|
|
@@ -30,7 +30,13 @@ const spec = (0, core_1.defineSpec)({
|
|
|
30
30
|
intent: {
|
|
31
31
|
primary: {
|
|
32
32
|
decls: {
|
|
33
|
-
|
|
33
|
+
// your-moon/crisp#897: `bgBrandContrast`, not `bgBrandSolid` —
|
|
34
|
+
// white text on the exact accent blue (#0298d9) only clears
|
|
35
|
+
// 3.23:1; `bgBrandContrast` is the darker sibling built to clear
|
|
36
|
+
// white at 4.5:1+, keeping `fgOnBrand` (white) rather than
|
|
37
|
+
// reaching for `fgOnTone` (ink read off-brand — seed's own solid
|
|
38
|
+
// buttons use white).
|
|
39
|
+
background: tokens_1.contract.bgBrandContrast,
|
|
34
40
|
color: tokens_1.contract.fgOnBrand,
|
|
35
41
|
// crisp#494: the inset ring is a black tint, not white -- live-
|
|
36
42
|
// measured on Attio's own "New Company" button as
|
|
@@ -39,12 +45,16 @@ const spec = (0, core_1.defineSpec)({
|
|
|
39
45
|
// exactly.
|
|
40
46
|
boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08)",
|
|
41
47
|
},
|
|
48
|
+
// your-moon/crisp#897: a brightness filter, not a second background
|
|
49
|
+
// swap — matches `danger`'s own pattern below. `bgBrandSolidPressed`
|
|
50
|
+
// remains the accent's own hover-darken for the many OTHER
|
|
51
|
+
// consumers that pair `bgBrandSolid` with white already (out of
|
|
52
|
+
// scope for this fix; tracked separately), so reusing it here would
|
|
53
|
+
// have made primary's rest-to-hover jump inconsistent with its own
|
|
54
|
+
// rest fill's new, darker starting point.
|
|
42
55
|
parts: [
|
|
43
|
-
{ selector: "&:hover", decls: {
|
|
44
|
-
{
|
|
45
|
-
selector: "&:active",
|
|
46
|
-
decls: { background: tokens_1.contract.bgBrandSolidPressed },
|
|
47
|
-
},
|
|
56
|
+
{ selector: "&:hover", decls: { filter: "brightness(0.96)" } },
|
|
57
|
+
{ selector: "&:active", decls: { filter: "brightness(0.92)" } },
|
|
48
58
|
],
|
|
49
59
|
},
|
|
50
60
|
neutral: {
|
|
@@ -37,11 +37,11 @@ exports.Input = void 0;
|
|
|
37
37
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
38
38
|
const React = __importStar(require("react"));
|
|
39
39
|
const input_recipe_1 = require("./input.recipe");
|
|
40
|
-
exports.Input = React.forwardRef(({ size, invalid, className, style, ...props }, ref) => {
|
|
40
|
+
exports.Input = React.forwardRef(({ size, invalid, className, style, suffix, ...props }, ref) => {
|
|
41
41
|
// The wrapper exists only to host the ::after focus ring — an <input> is a
|
|
42
42
|
// replaced element and cannot have one. Everything the consumer passes
|
|
43
43
|
// still lands on the <input> itself, so `id`, `name`, `onChange`, form
|
|
44
44
|
// association and every native attribute behave exactly as before.
|
|
45
|
-
return ((0, jsx_runtime_1.
|
|
45
|
+
return ((0, jsx_runtime_1.jsxs)("span", { className: (0, input_recipe_1.inputRecipe)({ size, invalid, className }), style: style, "data-readonly": props.readOnly ? "" : undefined, children: [(0, jsx_runtime_1.jsx)("input", { ref: ref, className: "crisp-input-value", "aria-invalid": invalid === true || undefined, ...props }), suffix != null && (0, jsx_runtime_1.jsx)("span", { className: "crisp-input-suffix", children: suffix })] }));
|
|
46
46
|
});
|
|
47
47
|
exports.Input.displayName = "Input";
|
|
@@ -41,6 +41,14 @@ const config = (0, core_1.defineRecipe)({
|
|
|
41
41
|
& .crisp-input-value::placeholder { color: ${tokens_1.contract.fgMuted}; }
|
|
42
42
|
& .crisp-input-value:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
43
43
|
&:has(.crisp-input-value:disabled) { opacity: 1; }
|
|
44
|
+
|
|
45
|
+
/* crisp#634: measured live on Attio's own Settings > Profile read-only
|
|
46
|
+
primary-email field -- a tinted field, not just muted text, driven off
|
|
47
|
+
the native \`readOnly\` attribute. \`ringField\` (rgba(0,0,0,.06)) is an
|
|
48
|
+
exact match for the measured ring; the 2%-fgPrimary tint reuses the
|
|
49
|
+
same color-mix idiom every other soft crisp surface already uses. */
|
|
50
|
+
&[data-readonly] { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 2%, transparent); box-shadow: inset 0 0 0 1px ${tokens_1.contract.ringField}; }
|
|
51
|
+
& .crisp-input-suffix { flex-shrink: 0; margin-inline-start: ${tokens_1.contract.space["1"]}; }
|
|
44
52
|
`,
|
|
45
53
|
variants: {
|
|
46
54
|
size: {
|
|
@@ -71,7 +71,7 @@ function MultiSelect({ options, value: valueProp, defaultValue, onValueChange, n
|
|
|
71
71
|
// The id belongs on the trigger, not the wrapper: `<label for>` only names a
|
|
72
72
|
// LABELABLE element, so an id left on the root div meant a FormField label
|
|
73
73
|
// above this control named nothing (#247). Select already did this.
|
|
74
|
-
id, placeholder: placeholderProp, searchable = true, searchPlaceholder: searchPlaceholderProp, emptyLabel: emptyLabelProp, disabled, readOnly, invalid, size = "
|
|
74
|
+
id, placeholder: placeholderProp, searchable = true, searchPlaceholder: searchPlaceholderProp, emptyLabel: emptyLabelProp, disabled, readOnly, invalid, size = "small", variant = "default", className, "aria-label": ariaLabel, ...rest }) {
|
|
75
75
|
// crisp/#471: same resolution order as Select's own.
|
|
76
76
|
const placeholder = (0, core_1.useCrispMessage)(placeholderProp, "Select…");
|
|
77
77
|
const searchPlaceholder = (0, core_1.useCrispMessage)(searchPlaceholderProp, "Search…");
|
|
@@ -99,9 +99,18 @@ const config = (0, core_1.defineRecipe)({
|
|
|
99
99
|
.crisp-recordstable-tag[data-tone="orange"] { background: color-mix(in srgb, ${tokens_1.contract.bgOrange} 16%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgOrange} 82%, ${tokens_1.contract.fgPrimary}); box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.bgOrange} 30%, transparent); }
|
|
100
100
|
`,
|
|
101
101
|
variants: {
|
|
102
|
-
//
|
|
103
|
-
//
|
|
102
|
+
// Owner's follow-up after #867/#879/#882 (crisp/#889): SIZE is Attio's
|
|
103
|
+
// again, LOOK stays seed's -- see Select's own recipe comment for the
|
|
104
|
+
// full reasoning (measured live in GRC's "New employee" dialog: a 40px
|
|
105
|
+
// trigger next to a 32px `Input` visibly mismatched). `small` is the new
|
|
106
|
+
// default: `c.size.menuRow` (32px) and the original, pre-#882
|
|
107
|
+
// Attio-measured asymmetric padding for a chip-wrapping trigger.
|
|
108
|
+
// `medium`/`large` (seed's own 40/52px) stay, opt-in.
|
|
104
109
|
size: {
|
|
110
|
+
small: `
|
|
111
|
+
& .crisp-multiselect-trigger { min-height: ${tokens_1.contract.size.menuRow}; border-radius: ${tokens_1.contract.radius.md}; gap: ${tokens_1.contract.space["1"]}; padding: ${tokens_1.contract.space["0.5"]} ${tokens_1.contract.space["1.5"]} ${tokens_1.contract.space["0.5"]} ${tokens_1.contract.space["2"]}; }
|
|
112
|
+
& .crisp-multiselect-chevron { width: ${tokens_1.contract.size.icon}; height: ${tokens_1.contract.size.icon}; }
|
|
113
|
+
`,
|
|
105
114
|
medium: `
|
|
106
115
|
& .crisp-multiselect-trigger { min-height: ${tokens_1.contract.size.selectTriggerMd}; border-radius: ${tokens_1.contract.radius.md}; gap: ${tokens_1.contract.space["2"]}; padding: ${tokens_1.contract.space["1"]} ${tokens_1.contract.space["3.5"]}; }
|
|
107
116
|
& .crisp-multiselect-chevron { width: ${tokens_1.contract.size.icon}; height: ${tokens_1.contract.size.icon}; }
|
|
@@ -134,7 +143,7 @@ const config = (0, core_1.defineRecipe)({
|
|
|
134
143
|
`,
|
|
135
144
|
},
|
|
136
145
|
},
|
|
137
|
-
defaultVariants: { size: "
|
|
146
|
+
defaultVariants: { size: "small", invalid: false, variant: "default" },
|
|
138
147
|
compoundVariants: [],
|
|
139
148
|
});
|
|
140
149
|
exports.multiselectRecipe = Object.assign((0, core_1.resolveRecipe)(config), {
|
|
@@ -4,11 +4,12 @@ exports.Progress = Progress;
|
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
const progress_recipe_1 = require("./progress.recipe");
|
|
6
6
|
/**
|
|
7
|
-
* A progress bar — a continuous brand fill,
|
|
8
|
-
* `segments` is set (the "Getting started 3/6" pattern)
|
|
9
|
-
* `
|
|
7
|
+
* A progress bar — a continuous brand fill, a segmented row of pills when
|
|
8
|
+
* `segments` is set (the "Getting started 3/6" pattern), or a small ring
|
|
9
|
+
* (`variant="circular"`, a character-counter's own progress). Reports its
|
|
10
|
+
* state via `role="progressbar"` and the ARIA value attributes.
|
|
10
11
|
*/
|
|
11
|
-
function Progress({ value, max = 100, segments, "aria-label": ariaLabel, className, ...rest }) {
|
|
12
|
+
function Progress({ value, max = 100, segments, variant = "linear", size = 18, "aria-label": ariaLabel, className, ...rest }) {
|
|
12
13
|
const pct = Math.max(0, Math.min(100, (value / max) * 100));
|
|
13
14
|
const aria = {
|
|
14
15
|
role: "progressbar",
|
|
@@ -17,6 +18,22 @@ function Progress({ value, max = 100, segments, "aria-label": ariaLabel, classNa
|
|
|
17
18
|
"aria-valuemax": max,
|
|
18
19
|
"aria-label": ariaLabel,
|
|
19
20
|
};
|
|
21
|
+
if (variant === "circular") {
|
|
22
|
+
// Measured live: an 18x18 SVG, r=7.4, stroke-width 1.6 (a 0.6px margin on
|
|
23
|
+
// each side) — a two-circle ring (a soft-tinted track + a brand arc),
|
|
24
|
+
// the same track tint Badge/Callout's own `soft(bgBrandSolid, 14%)`
|
|
25
|
+
// formula already gives.
|
|
26
|
+
const strokeWidth = size * (1.6 / 18);
|
|
27
|
+
const r = size / 2 - strokeWidth;
|
|
28
|
+
const circumference = 2 * Math.PI * r;
|
|
29
|
+
const offset = circumference * (1 - pct / 100);
|
|
30
|
+
// The measured sample was at value=0 (a full, invisible offset either
|
|
31
|
+
// way), so the arc's start point couldn't be read off that one DOM
|
|
32
|
+
// snapshot -- 12 o'clock is the conventional start for this shape and
|
|
33
|
+
// isn't itself in tension with anything measured, but isn't independently
|
|
34
|
+
// confirmed at a nonzero value either.
|
|
35
|
+
return ((0, jsx_runtime_1.jsx)("div", { ...aria, className: (0, progress_recipe_1.progressRecipe)({ className }), ...rest, "data-variant": "circular", children: (0, jsx_runtime_1.jsxs)("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("circle", { className: "crisp-progress-circle-track", r: r, cx: size / 2, cy: size / 2, strokeWidth: strokeWidth, fill: "transparent" }), (0, jsx_runtime_1.jsx)("circle", { className: "crisp-progress-circle-fill", r: r, cx: size / 2, cy: size / 2, strokeWidth: strokeWidth, fill: "transparent", strokeDasharray: circumference, strokeDashoffset: offset, strokeLinecap: "round", transform: `rotate(-90 ${size / 2} ${size / 2})` })] }) }));
|
|
36
|
+
}
|
|
20
37
|
if (segments && segments > 0) {
|
|
21
38
|
const filled = Math.round((pct / 100) * segments);
|
|
22
39
|
return ((0, jsx_runtime_1.jsx)("div", { ...aria, className: (0, progress_recipe_1.progressRecipe)({ className }), ...rest, "data-segmented": "", children: Array.from({ length: segments }, (_, i) => ((0, jsx_runtime_1.jsx)("span", { className: "crisp-progress-seg", "data-filled": i < filled ? "" : undefined }, i))) }));
|
|
@@ -52,6 +52,36 @@ const spec = (0, core_1.defineSpec)({
|
|
|
52
52
|
selector: "&[data-segmented] .crisp-progress-seg[data-filled]",
|
|
53
53
|
decls: { background: tokens_1.contract.bgBrandSolid },
|
|
54
54
|
},
|
|
55
|
+
// crisp#634: measured live on Attio's own Settings > Profile
|
|
56
|
+
// character-counter ring — the track tint is the same
|
|
57
|
+
// `soft(bgBrandSolid, 14%)` formula Badge/Callout already use (measured
|
|
58
|
+
// rgb(229,238,255), within the same noise this codebase already treats
|
|
59
|
+
// as equivalent), the arc is the plain brand fill.
|
|
60
|
+
{
|
|
61
|
+
selector: '&[data-variant="circular"]',
|
|
62
|
+
decls: {
|
|
63
|
+
display: "inline-flex",
|
|
64
|
+
height: "auto",
|
|
65
|
+
borderRadius: "0",
|
|
66
|
+
background: "transparent",
|
|
67
|
+
overflow: "visible",
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
slot: "circleTrack",
|
|
72
|
+
selector: "& .crisp-progress-circle-track",
|
|
73
|
+
decls: {
|
|
74
|
+
stroke: `color-mix(in srgb, ${tokens_1.contract.bgBrandSolid} 14%, transparent)`,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
slot: "circleFill",
|
|
79
|
+
selector: "& .crisp-progress-circle-fill",
|
|
80
|
+
decls: {
|
|
81
|
+
stroke: tokens_1.contract.bgBrandSolid,
|
|
82
|
+
transition: "stroke-dashoffset 0.25s ease",
|
|
83
|
+
},
|
|
84
|
+
},
|
|
55
85
|
],
|
|
56
86
|
});
|
|
57
87
|
exports.spec = spec;
|
|
@@ -55,7 +55,7 @@ function optionText(o) {
|
|
|
55
55
|
* move, Enter/Space pick, Esc close, Home/End jump, opens upward when tight.
|
|
56
56
|
* Controlled or uncontrolled.
|
|
57
57
|
*/
|
|
58
|
-
function Select({ options, value, defaultValue, onValueChange, name, required, placeholder: placeholderProp, variant = "default", width = "full", size = "
|
|
58
|
+
function Select({ options, value, defaultValue, onValueChange, name, required, placeholder: placeholderProp, variant = "default", width = "full", size = "small", searchable, searchPlaceholder: searchPlaceholderProp, onSearchChange, emptyLabel: emptyLabelProp, disabled, readOnly, invalid, className, "aria-label": ariaLabel, id, ...rest }) {
|
|
59
59
|
// crisp/#471: an explicit prop always wins; missing, it reads the nearest
|
|
60
60
|
// CrispMessagesProvider's translation of the English default, or that
|
|
61
61
|
// default itself with no provider — unchanged from before this existed.
|
|
@@ -217,7 +217,7 @@ function Select({ options, value, defaultValue, onValueChange, name, required, p
|
|
|
217
217
|
return ((0, jsx_runtime_1.jsxs)("div", { ref: rootRef, className: (0, select_recipe_1.selectRecipe)({ variant, width, size, invalid, className }), ...rest, children: [name && (selected || required) && ((0, jsx_runtime_1.jsx)("input", { className: "crisp-formvalue", style: core_1.formValueStyle, name: name, value: selected ?? "", required: required, tabIndex: -1, "aria-hidden": "true",
|
|
218
218
|
// controlled by the listbox above; `readOnly` would bar it from
|
|
219
219
|
// validation, which is the only reason this input is not hidden
|
|
220
|
-
onChange: () => { } })), (0, jsx_runtime_1.jsxs)("button", { ref: triggerRef, type: "button", id: id, role: "combobox", "aria-haspopup": "listbox", "aria-expanded": open, "aria-controls": open ? listboxId : undefined, "aria-activedescendant": searchable ? undefined : activeId, "aria-label": ariaLabel, "aria-invalid": invalid === true || undefined, "aria-readonly": readOnly ? "true" : undefined, disabled: disabled, "data-open": open ? "" : undefined, className: "crisp-select-trigger", onClick: () => (open ? setOpen(false) : openMenu()), onKeyDown: onTriggerKeyDown, children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-select-value", children: selectedOption ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [selectedOption.color && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-select-option-dot", style: { background: selectedOption.color } })), selectedOption.triggerLabel ?? selectedOption.label] })) : ((0, jsx_runtime_1.jsx)("span", { className: "crisp-select-placeholder", children: placeholder })) }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: "crisp-select-chevron", size: 16, strokeWidth: 1.75 })] }), open && ((0, jsx_runtime_1.jsx)(core_1.FloatingPortal, { sourceRef: rootRef, children: (0, jsx_runtime_1.jsxs)("div", { ref: listboxRef, id: listboxId, className: "crisp-select-listbox", role: "listbox", "aria-label": ariaLabel, "data-up": up ? "" : undefined, style: pos, children: [searchable && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-select-search", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size:
|
|
220
|
+
onChange: () => { } })), (0, jsx_runtime_1.jsxs)("button", { ref: triggerRef, type: "button", id: id, role: "combobox", "aria-haspopup": "listbox", "aria-expanded": open, "aria-controls": open ? listboxId : undefined, "aria-activedescendant": searchable ? undefined : activeId, "aria-label": ariaLabel, "aria-invalid": invalid === true || undefined, "aria-readonly": readOnly ? "true" : undefined, disabled: disabled, "data-open": open ? "" : undefined, className: "crisp-select-trigger", onClick: () => (open ? setOpen(false) : openMenu()), onKeyDown: onTriggerKeyDown, children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-select-value", children: selectedOption ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [selectedOption.color && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-select-option-dot", style: { background: selectedOption.color } })), selectedOption.triggerLabel ?? selectedOption.label] })) : ((0, jsx_runtime_1.jsx)("span", { className: "crisp-select-placeholder", children: placeholder })) }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: "crisp-select-chevron", size: 16, strokeWidth: 1.75 })] }), open && ((0, jsx_runtime_1.jsx)(core_1.FloatingPortal, { sourceRef: rootRef, children: (0, jsx_runtime_1.jsxs)("div", { ref: listboxRef, id: listboxId, className: "crisp-select-listbox", role: "listbox", "aria-label": ariaLabel, "data-up": up ? "" : undefined, style: pos, children: [searchable && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-select-search", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 16, strokeWidth: 1.75 }), (0, jsx_runtime_1.jsx)("input", { ref: searchRef, type: "text", placeholder: searchPlaceholder, "aria-label": searchPlaceholder, "aria-controls": listboxId, "aria-activedescendant": activeId, value: query, onChange: (e) => {
|
|
221
221
|
setQuery(e.target.value);
|
|
222
222
|
setHighlight(0);
|
|
223
223
|
onSearchChange?.(e.target.value);
|
|
@@ -88,9 +88,19 @@ const config = (0, core_1.defineRecipe)({
|
|
|
88
88
|
}
|
|
89
89
|
.crisp-select-listbox[data-up] { transform-origin: bottom left; }
|
|
90
90
|
.crisp-select-listbox { @starting-style { opacity: 0; transform: scale(0.95); } }
|
|
91
|
-
|
|
91
|
+
/* Composed from @seed-design/rootage's text-input.yaml
|
|
92
|
+
(variant=outline,size=medium) -- seed's Select has no search field of
|
|
93
|
+
its own to resolve this from (see the file header), so this reuses
|
|
94
|
+
the SAME tokens #867 already resolved for the trigger at this size:
|
|
95
|
+
minHeight x10 (${tokens_1.contract.size.selectTriggerMd}), paddingX x3_5
|
|
96
|
+
(${tokens_1.contract.space["3.5"]}), gap x2 (${tokens_1.contract.space["2"]}), cornerRadius r2
|
|
97
|
+
(${tokens_1.contract.radius.md}), strokeColor stroke.neutral-weak
|
|
98
|
+
(${tokens_1.contract.strokeSelectTrigger}) -- a real bordered field sitting flush
|
|
99
|
+
inside the listbox's own 8px padding, replacing the old bare
|
|
100
|
+
34px/no-border/bleeds-to-the-edge strip (crisp/#904). */
|
|
101
|
+
.crisp-select-search { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; height: ${tokens_1.contract.size.selectTriggerMd}; flex-shrink: 0; padding: 0 ${tokens_1.contract.space["3.5"]}; border: 1px solid ${tokens_1.contract.strokeSelectTrigger}; border-radius: ${tokens_1.contract.radius.md}; color: ${tokens_1.contract.fgNeutralMuted}; }
|
|
92
102
|
.crisp-select-search svg { flex-shrink: 0; }
|
|
93
|
-
.crisp-select-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: ${tokens_1.contract.text.lg}; color: ${tokens_1.contract.fgSelectNeutral}; }
|
|
103
|
+
.crisp-select-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: ${tokens_1.contract.text.lg}; line-height: 19px; color: ${tokens_1.contract.fgSelectNeutral}; }
|
|
94
104
|
.crisp-select-search input::placeholder { color: ${tokens_1.contract.fgSelectPlaceholder}; }
|
|
95
105
|
.crisp-select-empty { padding: ${tokens_1.contract.space["3"]} ${tokens_1.contract.space["4"]}; text-align: center; font-size: ${tokens_1.contract.text.md}; color: ${tokens_1.contract.fgNeutralSubtle}; }
|
|
96
106
|
/* select.yaml's groupLabel -- font-size/line-height/weight are the same
|
|
@@ -124,9 +134,26 @@ const config = (0, core_1.defineRecipe)({
|
|
|
124
134
|
full: `width: 100%;`,
|
|
125
135
|
auto: `width: auto;`,
|
|
126
136
|
},
|
|
127
|
-
//
|
|
128
|
-
//
|
|
137
|
+
// Owner's follow-up after #867/#879 (crisp/#889): the LOOK stays seed's
|
|
138
|
+
// (border, listbox, radius, shadow, items, states, all unchanged below),
|
|
139
|
+
// but the SIZE is Attio's again -- a 40px trigger next to a 32px `Input`
|
|
140
|
+
// in the same form visibly mismatched (measured live in GRC's own "New
|
|
141
|
+
// employee" dialog). `small` is the new default: Attio's original
|
|
142
|
+
// 32px `c.size.menuRow`, `Input`'s own radius/padding/line-height so the
|
|
143
|
+
// two line up exactly, not just approximately. `medium`/`large` (seed's
|
|
144
|
+
// own 40/52px) stay, opt-in. Item rows are IDENTICAL across all three --
|
|
145
|
+
// only the trigger differs; only it was ever the mismatch.
|
|
129
146
|
size: {
|
|
147
|
+
small: `
|
|
148
|
+
& .crisp-select-trigger { height: ${tokens_1.contract.size.menuRow}; border-radius: ${tokens_1.contract.radius.md}; gap: ${tokens_1.contract.space["1.5"]}; padding-inline: ${tokens_1.contract.space["2.5"]}; }
|
|
149
|
+
& .crisp-select-value, & .crisp-select-placeholder { font-size: ${tokens_1.contract.text.lg}; line-height: 20px; }
|
|
150
|
+
& .crisp-select-chevron { width: ${tokens_1.contract.size.icon}; height: ${tokens_1.contract.size.icon}; }
|
|
151
|
+
.crisp-select-option { gap: ${tokens_1.contract.space["2"]}; }
|
|
152
|
+
.crisp-select-option-label { font-size: ${tokens_1.contract.text.lg}; line-height: 19px; }
|
|
153
|
+
.crisp-select-option-description { font-size: ${tokens_1.contract.text.sm}; line-height: 16px; }
|
|
154
|
+
.crisp-select-option-check { width: ${tokens_1.contract.space["3"]}; height: ${tokens_1.contract.space["3"]}; }
|
|
155
|
+
.crisp-select-label { padding-top: ${tokens_1.contract.space["2"]}; }
|
|
156
|
+
`,
|
|
130
157
|
medium: `
|
|
131
158
|
& .crisp-select-trigger { height: ${tokens_1.contract.size.selectTriggerMd}; border-radius: ${tokens_1.contract.radius.md}; gap: ${tokens_1.contract.space["2"]}; padding-inline: ${tokens_1.contract.space["3.5"]}; }
|
|
132
159
|
& .crisp-select-value, & .crisp-select-placeholder { font-size: ${tokens_1.contract.text.lg}; line-height: 19px; }
|
|
@@ -178,7 +205,7 @@ const config = (0, core_1.defineRecipe)({
|
|
|
178
205
|
},
|
|
179
206
|
defaultVariants: {
|
|
180
207
|
width: "full",
|
|
181
|
-
size: "
|
|
208
|
+
size: "small",
|
|
182
209
|
invalid: false,
|
|
183
210
|
variant: "default",
|
|
184
211
|
},
|
|
@@ -36,13 +36,14 @@ const spec = (0, core_1.defineSpec)({
|
|
|
36
36
|
muted: { color: tokens_1.contract.fgNeutralMuted },
|
|
37
37
|
// #858: no new measurement -- both reuse tokens already measured
|
|
38
38
|
// against Attio for the same colours in a different arrangement.
|
|
39
|
-
// `brand` is `textlink`'s own reused brand-text colour
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
|
|
39
|
+
// `brand` is `textlink`'s own reused brand-text colour, now
|
|
40
|
+
// `fgBrandContrast` (your-moon/crisp#897: the plain accent,
|
|
41
|
+
// `bgBrandSolid`, only clears 3.23:1 as text on a white page).
|
|
42
|
+
// `danger` is `fgDanger` ("solid critical/destructive TEXT... an
|
|
43
|
+
// overdue due-date, a destructive menu item's label" -- see its own
|
|
44
|
+
// doc in tokens/semantic.ts), not `fgDangerSoft` (that one's a soft
|
|
45
|
+
// Badge pill's text, a different measured hex).
|
|
46
|
+
brand: { color: tokens_1.contract.fgBrandContrast },
|
|
46
47
|
danger: { color: tokens_1.contract.fgDanger },
|
|
47
48
|
},
|
|
48
49
|
},
|
|
@@ -36,13 +36,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.Textarea = void 0;
|
|
37
37
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
38
38
|
const React = __importStar(require("react"));
|
|
39
|
+
const progress_1 = require("../progress");
|
|
39
40
|
const textarea_recipe_1 = require("./textarea.recipe");
|
|
40
41
|
/**
|
|
41
42
|
* A multiline text field — measured from settings. Forwards every native
|
|
42
43
|
* `<textarea>` prop; pass `showCount` with `maxLength` to render a live
|
|
43
44
|
* character counter (which turns red if the value exceeds the limit).
|
|
44
45
|
*/
|
|
45
|
-
exports.Textarea = React.forwardRef(({ showCount, maxLength, className, wrapperClassName, value, defaultValue, onChange,
|
|
46
|
+
exports.Textarea = React.forwardRef(({ showCount, showProgress, maxLength, className, wrapperClassName, value, defaultValue, onChange,
|
|
46
47
|
// `style` is pulled out and put on the ROOT, the way Input does it.
|
|
47
48
|
// Everything else belongs on the <textarea> -- id, name, form
|
|
48
49
|
// association, aria -- but `style` sizes the visual box, and the box is
|
|
@@ -58,6 +59,6 @@ style, ...props }, ref) => {
|
|
|
58
59
|
if (value === undefined)
|
|
59
60
|
setCount(e.target.value.length);
|
|
60
61
|
onChange?.(e);
|
|
61
|
-
}, ...props }) }), counted && ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-textarea-count", "data-over": len > (maxLength ?? 0) ? "" : undefined, "aria-hidden": "true", children: [len, " / ", maxLength] }))] }));
|
|
62
|
+
}, ...props }) }), counted && ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-textarea-count", "data-over": len > (maxLength ?? 0) ? "" : undefined, "aria-hidden": "true", children: [showProgress && ((0, jsx_runtime_1.jsx)(progress_1.Progress, { variant: "circular", value: len, max: maxLength ?? 0, className: "crisp-textarea-progress" })), len, " / ", maxLength] }))] }));
|
|
62
63
|
});
|
|
63
64
|
exports.Textarea.displayName = "Textarea";
|
|
@@ -49,8 +49,9 @@ const config = (0, core_1.defineRecipe)({
|
|
|
49
49
|
&[data-invalid] .crisp-textarea-wrap::after { border-color: ${tokens_1.contract.bgDanger}; }
|
|
50
50
|
&[data-invalid] .crisp-textarea-wrap:focus-within::after { border-color: ${tokens_1.contract.bgDanger}; }
|
|
51
51
|
&[data-counted] .crisp-textarea-field { padding-bottom: ${tokens_1.contract.space["5"]}; }
|
|
52
|
-
& .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; }
|
|
52
|
+
& .crisp-textarea-count { position: absolute; inset-inline-end: 14px; bottom: ${tokens_1.contract.space["2.5"]}; display: flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; font-size: ${tokens_1.contract.text.sm}; color: ${tokens_1.contract.fgMuted}; pointer-events: none; }
|
|
53
53
|
& .crisp-textarea-count[data-over] { color: ${tokens_1.contract.bgDanger}; }
|
|
54
|
+
& .crisp-textarea-progress { flex-shrink: 0; }
|
|
54
55
|
`,
|
|
55
56
|
variants: {},
|
|
56
57
|
defaultVariants: {},
|