@crisp-ui-kit/crisp 0.42.0 → 0.43.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.
@@ -195,7 +195,12 @@ id, placeholder = "Select…", searchable = true, searchPlaceholder = "Search…
195
195
  setHighlight(0);
196
196
  }, onKeyDown: onNavKeys })] })), filtered.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-multiselect-empty", children: emptyLabel })) : (filtered.map((opt, i) => {
197
197
  const sel = selectedSet.has(opt.value);
198
- return ((0, jsx_runtime_1.jsxs)("button", { type: "button", id: optionId(i), role: "option", "aria-selected": sel, tabIndex: -1, "data-highlighted": i === highlight ? "" : undefined, className: "crisp-multiselect-option", onClick: () => toggle(opt.value), onMouseEnter: () => setHighlight(i), children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-multiselect-option-chip", children: (0, jsx_runtime_1.jsx)(Chip, { opt: opt }) }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-multiselect-check", children: sel && (0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: 12, strokeWidth: 3 }) })] }, opt.value));
198
+ // #343: a header renders once per run of same-`group`
199
+ // options, so consecutive rows sharing a group render one
200
+ // heading, not one each.
201
+ const showGroupLabel = opt.group !== undefined &&
202
+ opt.group !== filtered[i - 1]?.group;
203
+ return ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [showGroupLabel && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-multiselect-label", children: opt.group })), (0, jsx_runtime_1.jsxs)("button", { type: "button", id: optionId(i), role: "option", "aria-selected": sel, tabIndex: -1, "data-highlighted": i === highlight ? "" : undefined, className: "crisp-multiselect-option", onClick: () => toggle(opt.value), onMouseEnter: () => setHighlight(i), children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-multiselect-option-chip", children: (0, jsx_runtime_1.jsx)(Chip, { opt: opt }) }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-multiselect-check", children: sel && (0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: 12, strokeWidth: 3 }) })] })] }, opt.value));
199
204
  }))] }) }))] }));
200
205
  }
201
206
  MultiSelect.displayName = "MultiSelect";
@@ -58,6 +58,10 @@ const config = (0, core_1.defineRecipe)({
58
58
  .crisp-multiselect-search svg { flex-shrink: 0; }
59
59
  .crisp-multiselect-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.fgPrimary}; }
60
60
  .crisp-multiselect-search input::placeholder { color: ${tokens_1.contract.fgMuted}; }
61
+ /* #343: same measured values as Menu's own group heading
62
+ (crisp-menu-label) -- reused rather than re-measured, since it's the
63
+ same "small muted heading above a run of rows" treatment. */
64
+ .crisp-multiselect-label { padding: ${tokens_1.contract.space["1.5"]} ${tokens_1.contract.space["2"]} ${tokens_1.contract.space["0.5"]}; font-size: ${tokens_1.contract.text.sm}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgMuted}; }
61
65
  .crisp-multiselect-option {
62
66
  box-sizing: border-box; display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; height: ${tokens_1.contract.size.menuRow}; flex-shrink: 0;
63
67
  padding: 0 ${tokens_1.contract.space["2"]}; margin: 0; border: none; background: none; border-radius: ${tokens_1.contract.radius.md}; font-family: inherit; cursor: pointer; text-align: start;
@@ -185,7 +185,12 @@ function Select({ options, value, defaultValue, onValueChange, name, required, p
185
185
  setHighlight(0);
186
186
  }, onKeyDown: onNavKeys })] })), filtered.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-select-empty", children: emptyLabel })) : (filtered.map((opt, i) => {
187
187
  const isSel = opt.value === selected;
188
- return ((0, jsx_runtime_1.jsxs)("button", { type: "button", id: optionId(i), role: "option", "aria-selected": isSel, tabIndex: -1, disabled: opt.disabled, "data-disabled": opt.disabled ? "" : undefined, "data-highlighted": i === highlight ? "" : undefined, className: "crisp-select-option", onClick: () => pick(opt), onMouseEnter: () => setHighlight(i), children: [opt.color && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-select-option-dot", style: { background: opt.color } })), (0, jsx_runtime_1.jsx)("span", { className: "crisp-select-option-label", children: opt.label }), isSel && ((0, jsx_runtime_1.jsx)(lucide_react_1.Check, { className: "crisp-select-option-check", size: 15, strokeWidth: 2 }))] }, opt.value));
188
+ // #343: a header renders once per run of same-`group`
189
+ // options, so consecutive rows sharing a group render one
190
+ // heading, not one each.
191
+ const showGroupLabel = opt.group !== undefined &&
192
+ opt.group !== filtered[i - 1]?.group;
193
+ return ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [showGroupLabel && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-select-label", children: opt.group })), (0, jsx_runtime_1.jsxs)("button", { type: "button", id: optionId(i), role: "option", "aria-selected": isSel, tabIndex: -1, disabled: opt.disabled, "data-disabled": opt.disabled ? "" : undefined, "data-highlighted": i === highlight ? "" : undefined, className: "crisp-select-option", onClick: () => pick(opt), onMouseEnter: () => setHighlight(i), children: [opt.color && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-select-option-dot", style: { background: opt.color } })), (0, jsx_runtime_1.jsx)("span", { className: "crisp-select-option-label", children: opt.label }), isSel && ((0, jsx_runtime_1.jsx)(lucide_react_1.Check, { className: "crisp-select-option-check", size: 15, strokeWidth: 2 }))] })] }, opt.value));
189
194
  }))] }) }))] }));
190
195
  }
191
196
  Select.displayName = "Select";
@@ -59,6 +59,10 @@ const config = (0, core_1.defineRecipe)({
59
59
  .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.fgPrimary}; }
60
60
  .crisp-select-search input::placeholder { color: ${tokens_1.contract.fgMuted}; }
61
61
  .crisp-select-empty { padding: ${tokens_1.contract.space["3"]} ${tokens_1.contract.space["2"]}; text-align: center; font-size: ${tokens_1.contract.text.md}; color: ${tokens_1.contract.fgMuted}; }
62
+ /* #343: same measured values as Menu's own group heading
63
+ (crisp-menu-label) -- reused rather than re-measured, since it's the
64
+ same "small muted heading above a run of rows" treatment. */
65
+ .crisp-select-label { padding: ${tokens_1.contract.space["1.5"]} ${tokens_1.contract.space["2"]} ${tokens_1.contract.space["0.5"]}; font-size: ${tokens_1.contract.text.sm}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgMuted}; }
62
66
  .crisp-select-option {
63
67
  box-sizing: border-box; display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; height: ${tokens_1.contract.size.menuRow}; flex-shrink: 0;
64
68
  padding: 0 ${tokens_1.contract.space["2"]}; margin: 0; border: none; background: none; border-radius: ${tokens_1.contract.radius.md};
@@ -159,7 +159,12 @@ id, placeholder = "Select…", searchable = true, searchPlaceholder = "Search…
159
159
  setHighlight(0);
160
160
  }, onKeyDown: onNavKeys })] })), filtered.length === 0 ? (_jsx("div", { className: "crisp-multiselect-empty", children: emptyLabel })) : (filtered.map((opt, i) => {
161
161
  const sel = selectedSet.has(opt.value);
162
- return (_jsxs("button", { type: "button", id: optionId(i), role: "option", "aria-selected": sel, tabIndex: -1, "data-highlighted": i === highlight ? "" : undefined, className: "crisp-multiselect-option", onClick: () => toggle(opt.value), onMouseEnter: () => setHighlight(i), children: [_jsx("span", { className: "crisp-multiselect-option-chip", children: _jsx(Chip, { opt: opt }) }), _jsx("span", { className: "crisp-multiselect-check", children: sel && _jsx(Check, { size: 12, strokeWidth: 3 }) })] }, opt.value));
162
+ // #343: a header renders once per run of same-`group`
163
+ // options, so consecutive rows sharing a group render one
164
+ // heading, not one each.
165
+ const showGroupLabel = opt.group !== undefined &&
166
+ opt.group !== filtered[i - 1]?.group;
167
+ return (_jsxs(React.Fragment, { children: [showGroupLabel && (_jsx("div", { className: "crisp-multiselect-label", children: opt.group })), _jsxs("button", { type: "button", id: optionId(i), role: "option", "aria-selected": sel, tabIndex: -1, "data-highlighted": i === highlight ? "" : undefined, className: "crisp-multiselect-option", onClick: () => toggle(opt.value), onMouseEnter: () => setHighlight(i), children: [_jsx("span", { className: "crisp-multiselect-option-chip", children: _jsx(Chip, { opt: opt }) }), _jsx("span", { className: "crisp-multiselect-check", children: sel && _jsx(Check, { size: 12, strokeWidth: 3 }) })] })] }, opt.value));
163
168
  }))] }) }))] }));
164
169
  }
165
170
  MultiSelect.displayName = "MultiSelect";
@@ -55,6 +55,10 @@ const config = defineRecipe({
55
55
  .crisp-multiselect-search svg { flex-shrink: 0; }
56
56
  .crisp-multiselect-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: ${c.text.lg}; color: ${c.fgPrimary}; }
57
57
  .crisp-multiselect-search input::placeholder { color: ${c.fgMuted}; }
58
+ /* #343: same measured values as Menu's own group heading
59
+ (crisp-menu-label) -- reused rather than re-measured, since it's the
60
+ same "small muted heading above a run of rows" treatment. */
61
+ .crisp-multiselect-label { padding: ${c.space["1.5"]} ${c.space["2"]} ${c.space["0.5"]}; font-size: ${c.text.sm}; font-weight: ${c.weight.medium}; color: ${c.fgMuted}; }
58
62
  .crisp-multiselect-option {
59
63
  box-sizing: border-box; display: flex; align-items: center; gap: ${c.space["2"]}; height: ${c.size.menuRow}; flex-shrink: 0;
60
64
  padding: 0 ${c.space["2"]}; margin: 0; border: none; background: none; border-radius: ${c.radius.md}; font-family: inherit; cursor: pointer; text-align: start;
@@ -149,7 +149,12 @@ export function Select({ options, value, defaultValue, onValueChange, name, requ
149
149
  setHighlight(0);
150
150
  }, onKeyDown: onNavKeys })] })), filtered.length === 0 ? (_jsx("div", { className: "crisp-select-empty", children: emptyLabel })) : (filtered.map((opt, i) => {
151
151
  const isSel = opt.value === selected;
152
- return (_jsxs("button", { type: "button", id: optionId(i), role: "option", "aria-selected": isSel, tabIndex: -1, disabled: opt.disabled, "data-disabled": opt.disabled ? "" : undefined, "data-highlighted": i === highlight ? "" : undefined, className: "crisp-select-option", onClick: () => pick(opt), onMouseEnter: () => setHighlight(i), children: [opt.color && (_jsx("span", { className: "crisp-select-option-dot", style: { background: opt.color } })), _jsx("span", { className: "crisp-select-option-label", children: opt.label }), isSel && (_jsx(Check, { className: "crisp-select-option-check", size: 15, strokeWidth: 2 }))] }, opt.value));
152
+ // #343: a header renders once per run of same-`group`
153
+ // options, so consecutive rows sharing a group render one
154
+ // heading, not one each.
155
+ const showGroupLabel = opt.group !== undefined &&
156
+ opt.group !== filtered[i - 1]?.group;
157
+ return (_jsxs(React.Fragment, { children: [showGroupLabel && (_jsx("div", { className: "crisp-select-label", children: opt.group })), _jsxs("button", { type: "button", id: optionId(i), role: "option", "aria-selected": isSel, tabIndex: -1, disabled: opt.disabled, "data-disabled": opt.disabled ? "" : undefined, "data-highlighted": i === highlight ? "" : undefined, className: "crisp-select-option", onClick: () => pick(opt), onMouseEnter: () => setHighlight(i), children: [opt.color && (_jsx("span", { className: "crisp-select-option-dot", style: { background: opt.color } })), _jsx("span", { className: "crisp-select-option-label", children: opt.label }), isSel && (_jsx(Check, { className: "crisp-select-option-check", size: 15, strokeWidth: 2 }))] })] }, opt.value));
153
158
  }))] }) }))] }));
154
159
  }
155
160
  Select.displayName = "Select";
@@ -56,6 +56,10 @@ const config = defineRecipe({
56
56
  .crisp-select-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: ${c.text.lg}; color: ${c.fgPrimary}; }
57
57
  .crisp-select-search input::placeholder { color: ${c.fgMuted}; }
58
58
  .crisp-select-empty { padding: ${c.space["3"]} ${c.space["2"]}; text-align: center; font-size: ${c.text.md}; color: ${c.fgMuted}; }
59
+ /* #343: same measured values as Menu's own group heading
60
+ (crisp-menu-label) -- reused rather than re-measured, since it's the
61
+ same "small muted heading above a run of rows" treatment. */
62
+ .crisp-select-label { padding: ${c.space["1.5"]} ${c.space["2"]} ${c.space["0.5"]}; font-size: ${c.text.sm}; font-weight: ${c.weight.medium}; color: ${c.fgMuted}; }
59
63
  .crisp-select-option {
60
64
  box-sizing: border-box; display: flex; align-items: center; gap: ${c.space["2"]}; height: ${c.size.menuRow}; flex-shrink: 0;
61
65
  padding: 0 ${c.space["2"]}; margin: 0; border: none; background: none; border-radius: ${c.radius.md};
@@ -13,6 +13,10 @@
13
13
  .crisp-multiselect-search svg { flex-shrink: 0; }
14
14
  .crisp-multiselect-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
15
15
  .crisp-multiselect-search input::placeholder { color: var(--crisp-fg-muted); }
16
+ /* #343: same measured values as Menu's own group heading
17
+ (crisp-menu-label) -- reused rather than re-measured, since it's the
18
+ same "small muted heading above a run of rows" treatment. */
19
+ .crisp-multiselect-label { padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-0-5); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
16
20
  .crisp-multiselect-option {
17
21
  box-sizing: border-box; display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-menuRow); flex-shrink: 0;
18
22
  padding: 0 var(--crisp-space-2); margin: 0; border: none; background: none; border-radius: var(--crisp-radius-md); font-family: inherit; cursor: pointer; text-align: start;
@@ -14,6 +14,10 @@
14
14
  .crisp-multiselect-search svg { flex-shrink: 0; }
15
15
  .crisp-multiselect-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
16
16
  .crisp-multiselect-search input::placeholder { color: var(--crisp-fg-muted); }
17
+ /* #343: same measured values as Menu's own group heading
18
+ (crisp-menu-label) -- reused rather than re-measured, since it's the
19
+ same "small muted heading above a run of rows" treatment. */
20
+ .crisp-multiselect-label { padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-0-5); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
17
21
  .crisp-multiselect-option {
18
22
  box-sizing: border-box; display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-menuRow); flex-shrink: 0;
19
23
  padding: 0 var(--crisp-space-2); margin: 0; border: none; background: none; border-radius: var(--crisp-radius-md); font-family: inherit; cursor: pointer; text-align: start;
package/dist/select.css CHANGED
@@ -14,6 +14,10 @@
14
14
  .crisp-select-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
15
15
  .crisp-select-search input::placeholder { color: var(--crisp-fg-muted); }
16
16
  .crisp-select-empty { padding: var(--crisp-space-3) var(--crisp-space-2); text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
17
+ /* #343: same measured values as Menu's own group heading
18
+ (crisp-menu-label) -- reused rather than re-measured, since it's the
19
+ same "small muted heading above a run of rows" treatment. */
20
+ .crisp-select-label { padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-0-5); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
17
21
  .crisp-select-option {
18
22
  box-sizing: border-box; display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-menuRow); flex-shrink: 0;
19
23
  padding: 0 var(--crisp-space-2); margin: 0; border: none; background: none; border-radius: var(--crisp-radius-md);
@@ -15,6 +15,10 @@
15
15
  .crisp-select-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
16
16
  .crisp-select-search input::placeholder { color: var(--crisp-fg-muted); }
17
17
  .crisp-select-empty { padding: var(--crisp-space-3) var(--crisp-space-2); text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
18
+ /* #343: same measured values as Menu's own group heading
19
+ (crisp-menu-label) -- reused rather than re-measured, since it's the
20
+ same "small muted heading above a run of rows" treatment. */
21
+ .crisp-select-label { padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-0-5); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
18
22
  .crisp-select-option {
19
23
  box-sizing: border-box; display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-menuRow); flex-shrink: 0;
20
24
  padding: 0 var(--crisp-space-2); margin: 0; border: none; background: none; border-radius: var(--crisp-radius-md);
package/dist/styles.css CHANGED
@@ -1045,6 +1045,10 @@
1045
1045
  .crisp-multiselect-search svg { flex-shrink: 0; }
1046
1046
  .crisp-multiselect-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
1047
1047
  .crisp-multiselect-search input::placeholder { color: var(--crisp-fg-muted); }
1048
+ /* #343: same measured values as Menu's own group heading
1049
+ (crisp-menu-label) -- reused rather than re-measured, since it's the
1050
+ same "small muted heading above a run of rows" treatment. */
1051
+ .crisp-multiselect-label { padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-0-5); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
1048
1052
  .crisp-multiselect-option {
1049
1053
  box-sizing: border-box; display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-menuRow); flex-shrink: 0;
1050
1054
  padding: 0 var(--crisp-space-2); margin: 0; border: none; background: none; border-radius: var(--crisp-radius-md); font-family: inherit; cursor: pointer; text-align: start;
@@ -1272,6 +1276,10 @@
1272
1276
  .crisp-select-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
1273
1277
  .crisp-select-search input::placeholder { color: var(--crisp-fg-muted); }
1274
1278
  .crisp-select-empty { padding: var(--crisp-space-3) var(--crisp-space-2); text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
1279
+ /* #343: same measured values as Menu's own group heading
1280
+ (crisp-menu-label) -- reused rather than re-measured, since it's the
1281
+ same "small muted heading above a run of rows" treatment. */
1282
+ .crisp-select-label { padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-0-5); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
1275
1283
  .crisp-select-option {
1276
1284
  box-sizing: border-box; display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-menuRow); flex-shrink: 0;
1277
1285
  padding: 0 var(--crisp-space-2); margin: 0; border: none; background: none; border-radius: var(--crisp-radius-md);
@@ -1046,6 +1046,10 @@
1046
1046
  .crisp-multiselect-search svg { flex-shrink: 0; }
1047
1047
  .crisp-multiselect-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
1048
1048
  .crisp-multiselect-search input::placeholder { color: var(--crisp-fg-muted); }
1049
+ /* #343: same measured values as Menu's own group heading
1050
+ (crisp-menu-label) -- reused rather than re-measured, since it's the
1051
+ same "small muted heading above a run of rows" treatment. */
1052
+ .crisp-multiselect-label { padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-0-5); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
1049
1053
  .crisp-multiselect-option {
1050
1054
  box-sizing: border-box; display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-menuRow); flex-shrink: 0;
1051
1055
  padding: 0 var(--crisp-space-2); margin: 0; border: none; background: none; border-radius: var(--crisp-radius-md); font-family: inherit; cursor: pointer; text-align: start;
@@ -1273,6 +1277,10 @@
1273
1277
  .crisp-select-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
1274
1278
  .crisp-select-search input::placeholder { color: var(--crisp-fg-muted); }
1275
1279
  .crisp-select-empty { padding: var(--crisp-space-3) var(--crisp-space-2); text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
1280
+ /* #343: same measured values as Menu's own group heading
1281
+ (crisp-menu-label) -- reused rather than re-measured, since it's the
1282
+ same "small muted heading above a run of rows" treatment. */
1283
+ .crisp-select-label { padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-0-5); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
1276
1284
  .crisp-select-option {
1277
1285
  box-sizing: border-box; display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-menuRow); flex-shrink: 0;
1278
1286
  padding: 0 var(--crisp-space-2); margin: 0; border: none; background: none; border-radius: var(--crisp-radius-md);
@@ -4,6 +4,14 @@ export type MultiSelectOption = {
4
4
  label: string;
5
5
  /** Chip colour tone; falls back to a deterministic pastel per value. */
6
6
  tone?: string;
7
+ /**
8
+ * Group heading, rendered above this option (#343). A header renders once
9
+ * per run of consecutive options sharing the same `group` — like native
10
+ * `<optgroup>`, grouping is positional: pre-sort `options` so same-group
11
+ * rows are adjacent, or the same label renders more than once. Options
12
+ * with no `group` render exactly as before this existed.
13
+ */
14
+ group?: string;
7
15
  };
8
16
  export type MultiSelectProps = Omit<React.ComponentPropsWithoutRef<"div">, "value" | "defaultValue" | "onChange"> & {
9
17
  options: MultiSelectOption[];
@@ -7,6 +7,14 @@ export type SelectOption = {
7
7
  /** A status colour dot shown before the label (status/select fields). */
8
8
  color?: string;
9
9
  disabled?: boolean;
10
+ /**
11
+ * Group heading, rendered above this option (#343). A header renders once
12
+ * per run of consecutive options sharing the same `group` — like native
13
+ * `<optgroup>`, grouping is positional: pre-sort `options` so same-group
14
+ * rows are adjacent, or the same label renders more than once. Options
15
+ * with no `group` render exactly as before this existed.
16
+ */
17
+ group?: string;
10
18
  };
11
19
  export type SelectProps = Omit<React.ComponentPropsWithoutRef<"div">, "value" | "defaultValue" | "onChange"> & {
12
20
  options: SelectOption[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crisp-ui-kit/crisp",
3
- "version": "0.42.0",
3
+ "version": "0.43.0",
4
4
  "description": "A lean React design system, measured pixel-for-pixel from Attio.",
5
5
  "license": "MIT",
6
6
  "type": "module",