@crisp-ui-kit/crisp 0.10.1 → 0.11.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.
- package/dist/cjs/components/quantitypicker/index.js +18 -0
- package/dist/cjs/components/quantitypicker/quantitypicker.js +62 -0
- package/dist/cjs/components/quantitypicker/quantitypicker.recipe.js +114 -0
- package/dist/cjs/index.js +1 -0
- package/dist/esm/components/quantitypicker/index.js +2 -0
- package/dist/esm/components/quantitypicker/quantitypicker.js +59 -0
- package/dist/esm/components/quantitypicker/quantitypicker.recipe.js +111 -0
- package/dist/esm/index.js +1 -0
- package/dist/quantitypicker.css +78 -0
- package/dist/quantitypicker.d.ts +8 -0
- package/dist/styles.css +79 -0
- package/dist/types/components/quantitypicker/index.d.ts +2 -0
- package/dist/types/components/quantitypicker/quantitypicker.d.ts +43 -0
- package/dist/types/components/quantitypicker/quantitypicker.recipe.d.ts +38 -0
- package/dist/types/index.d.ts +1 -0
- package/package.json +8 -1
|
@@ -0,0 +1,18 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./quantitypicker"), exports);
|
|
18
|
+
__exportStar(require("./quantitypicker.recipe"), exports);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.QuantityPicker = QuantityPicker;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
const react_quantity_picker_1 = require("@seed-design/react-quantity-picker");
|
|
7
|
+
const lucide_react_1 = require("lucide-react");
|
|
8
|
+
const spinner_1 = require("../spinner");
|
|
9
|
+
const quantitypicker_recipe_1 = require("./quantitypicker.recipe");
|
|
10
|
+
/** seed's icon box per size, read from their stylesheet. */
|
|
11
|
+
const ICON = { small: 16, medium: 18, large: 22 };
|
|
12
|
+
/**
|
|
13
|
+
* The value, as a grid with a hidden copy of the LARGEST value stacked
|
|
14
|
+
* underneath it. Without that the control gets wider at 10 and wider again at
|
|
15
|
+
* 100, and a row of them stops lining up. `max` is the right placeholder
|
|
16
|
+
* because it is the widest the value can ever be, and it is run through
|
|
17
|
+
* `getValueText` so a formatted value reserves the width of its formatting.
|
|
18
|
+
*/
|
|
19
|
+
function Value() {
|
|
20
|
+
// `QuantityPickerValueDisplay` renders the value as its own children and its
|
|
21
|
+
// props type OMITS `children`, so the two-cell grid cannot go through it.
|
|
22
|
+
// Taking `valueDisplayProps` directly keeps every attribute the engine puts
|
|
23
|
+
// there — aria-live, aria-atomic, the data-state flags — and keeps the
|
|
24
|
+
// rendered text the engine's own rather than a second derivation of it.
|
|
25
|
+
const { max, getValueText, valueDisplayProps } = (0, react_quantity_picker_1.useQuantityPickerContext)();
|
|
26
|
+
const { children: valueText, ...displayProps } = valueDisplayProps;
|
|
27
|
+
return ((0, jsx_runtime_1.jsxs)("span", { ...displayProps, className: "quantitypicker-value", children: [(0, jsx_runtime_1.jsx)("span", { className: "quantitypicker-placeholder", "aria-hidden": "true", children: getValueText(String(max), max) }), (0, jsx_runtime_1.jsx)("span", { className: "quantitypicker-text", children: valueText })] }));
|
|
28
|
+
}
|
|
29
|
+
function DecrementIcon({ size }) {
|
|
30
|
+
const { isRemoveButton, decrementLoading } = (0, react_quantity_picker_1.useQuantityPickerContext)();
|
|
31
|
+
if (decrementLoading)
|
|
32
|
+
return (0, jsx_runtime_1.jsx)(spinner_1.Spinner, { size: ICON[size] });
|
|
33
|
+
const Icon = isRemoveButton ? lucide_react_1.Trash2 : lucide_react_1.Minus;
|
|
34
|
+
return (0, jsx_runtime_1.jsx)(Icon, { size: ICON[size], strokeWidth: 2 });
|
|
35
|
+
}
|
|
36
|
+
function IncrementIcon({ size }) {
|
|
37
|
+
const { incrementLoading } = (0, react_quantity_picker_1.useQuantityPickerContext)();
|
|
38
|
+
if (incrementLoading)
|
|
39
|
+
return (0, jsx_runtime_1.jsx)(spinner_1.Spinner, { size: ICON[size] });
|
|
40
|
+
return (0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: ICON[size], strokeWidth: 2 });
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A quantity stepper, driven by seed-design's headless engine so the clamping,
|
|
44
|
+
* the step arithmetic, the per-button loading state and the `removable` mode —
|
|
45
|
+
* where decrementing at the minimum becomes "remove this line" rather than a
|
|
46
|
+
* dead button — are theirs by construction.
|
|
47
|
+
*
|
|
48
|
+
* Two things are crisp's:
|
|
49
|
+
*
|
|
50
|
+
* The buttons carry accessible names. The engine labels the minus button only
|
|
51
|
+
* in remove mode, so a plain stepper would ship two buttons whose entire
|
|
52
|
+
* content is an SVG and whose accessible name is empty.
|
|
53
|
+
*
|
|
54
|
+
* `dir` is worth passing explicitly. The engine defaults it to `"ltr"` and
|
|
55
|
+
* stamps that on the root, which overrides an inherited RTL — the same trap
|
|
56
|
+
* Slider documents. Here it also decides child ORDER, since the engine
|
|
57
|
+
* reverses its visible children rather than relying on CSS.
|
|
58
|
+
*/
|
|
59
|
+
function QuantityPicker({ size = "medium", fill = false, className, name, decrementLabel = "Decrease quantity", incrementLabel = "Increase quantity", ...props }) {
|
|
60
|
+
return ((0, jsx_runtime_1.jsxs)(react_quantity_picker_1.QuantityPickerRoot, { ...props, className: (0, quantitypicker_recipe_1.quantitypickerRecipe)({ size, fill, className }), children: [(0, jsx_runtime_1.jsx)(react_quantity_picker_1.QuantityPickerDecrementButton, { className: "quantitypicker-button", "aria-label": decrementLabel, children: (0, jsx_runtime_1.jsx)(DecrementIcon, { size: size }) }), (0, jsx_runtime_1.jsx)("span", { className: "quantitypicker-divider" }), (0, jsx_runtime_1.jsx)(Value, {}), (0, jsx_runtime_1.jsx)("span", { className: "quantitypicker-divider" }), (0, jsx_runtime_1.jsx)(react_quantity_picker_1.QuantityPickerIncrementButton, { className: "quantitypicker-button", "aria-label": incrementLabel, children: (0, jsx_runtime_1.jsx)(IncrementIcon, { size: size }) }), name ? (0, jsx_runtime_1.jsx)(react_quantity_picker_1.QuantityPickerHiddenInput, { name: name }) : null] }));
|
|
61
|
+
}
|
|
62
|
+
QuantityPicker.displayName = "QuantityPicker";
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.quantitypickerRecipe = void 0;
|
|
4
|
+
const core_1 = require("../../core");
|
|
5
|
+
const tokens_1 = require("../../tokens");
|
|
6
|
+
// seed-design's quantity-picker, shape for shape, in crisp's colours.
|
|
7
|
+
//
|
|
8
|
+
// Attio has no stepper — its quantity-ish fields are plain number inputs — so
|
|
9
|
+
// as with Slider there is nothing of Attio's to measure and the GEOMETRY is
|
|
10
|
+
// seed's own, read from `@seed-design/css/recipes/quantity-picker.css`:
|
|
11
|
+
//
|
|
12
|
+
// size root height / button square radius icon text
|
|
13
|
+
// small 36px (x9) 8px (r2) 16px 16/22 (t5)
|
|
14
|
+
// medium 40px (x10) 8px (r2) 18px 16/22 (t5)
|
|
15
|
+
// large 52px (x13) 12px (r3) 22px 18/24 (t6)
|
|
16
|
+
//
|
|
17
|
+
// plus an 8px (x2) inline padding on the value, a 1x16 (x4) divider, a 1px
|
|
18
|
+
// inset ring that thickens to 2px when invalid, and a 2px focus ring offset
|
|
19
|
+
// 2px. 16px is crisp's `text.xl`; 18/24 has no crisp token and is written as a
|
|
20
|
+
// literal, the way EmptyState's 20px title is.
|
|
21
|
+
//
|
|
22
|
+
// Only the COLOUR roles are re-pointed at crisp's tokens:
|
|
23
|
+
// seed bg-layer-default -> bgSurface
|
|
24
|
+
// seed stroke-neutral-weak -> borderDefault (the 1px ring)
|
|
25
|
+
// seed stroke-critical-solid -> bgDanger (as Input/Textarea use when invalid)
|
|
26
|
+
// seed stroke-neutral-subtle -> borderSubtle (the divider)
|
|
27
|
+
// seed bg-transparent-pressed -> bgRaised (as Button's ghost hover)
|
|
28
|
+
// seed fg-neutral / fg-disabled-> fgPrimary / fgDisabled
|
|
29
|
+
//
|
|
30
|
+
// Two things in seed's stylesheet are deliberately NOT copied. Their pressed
|
|
31
|
+
// `scale: var(--seed-feedback-scale)` resolves to 1 in the shipped CSS, so it
|
|
32
|
+
// is a no-op to transliterate. And their hover is split across
|
|
33
|
+
// `@media (hover: hover) and (pointer: fine)` / `@media not all and (...)`; no
|
|
34
|
+
// crisp recipe contains an `@media` at all, and neither `build.mjs`'s
|
|
35
|
+
// flattener nor `emit-lynx.mjs` has ever been run against one, so this uses
|
|
36
|
+
// the plain `:hover` every other crisp component uses.
|
|
37
|
+
//
|
|
38
|
+
// The value is a GRID with a hidden placeholder stacked on the text in the
|
|
39
|
+
// same cell. That is what keeps the control from resizing as the number goes
|
|
40
|
+
// 9 -> 10, and it is the detail a hand-built stepper always misses.
|
|
41
|
+
const config = (0, core_1.defineRecipe)({
|
|
42
|
+
name: "quantitypicker",
|
|
43
|
+
base: `
|
|
44
|
+
display: inline-flex; box-sizing: border-box; width: fit-content;
|
|
45
|
+
align-items: center; font-family: ${tokens_1.contract.font.sans};
|
|
46
|
+
background: ${tokens_1.contract.bgSurface}; box-shadow: inset 0 0 0 1px ${tokens_1.contract.borderDefault};
|
|
47
|
+
|
|
48
|
+
&[data-invalid] { box-shadow: inset 0 0 0 2px ${tokens_1.contract.bgDanger}; }
|
|
49
|
+
|
|
50
|
+
& .quantitypicker-button {
|
|
51
|
+
display: inline-flex; flex-shrink: 0; box-sizing: border-box;
|
|
52
|
+
align-items: center; justify-content: center;
|
|
53
|
+
border: none; padding: 0; cursor: pointer;
|
|
54
|
+
background: transparent; color: ${tokens_1.contract.fgPrimary};
|
|
55
|
+
transition: background-color .15s ease, outline-color .15s ease;
|
|
56
|
+
outline: 2px solid transparent; outline-offset: 2px;
|
|
57
|
+
}
|
|
58
|
+
& .quantitypicker-button:focus-visible { outline-color: ${tokens_1.contract.strokeFocusRing}; }
|
|
59
|
+
& .quantitypicker-button:not([data-disabled]):hover { background: ${tokens_1.contract.bgRaised}; }
|
|
60
|
+
& .quantitypicker-button[data-loading] { background: ${tokens_1.contract.bgRaised}; }
|
|
61
|
+
& .quantitypicker-button[data-disabled] { cursor: not-allowed; color: ${tokens_1.contract.fgDisabled}; }
|
|
62
|
+
|
|
63
|
+
& .quantitypicker-value {
|
|
64
|
+
display: grid; flex-grow: 1; flex-shrink: 0; box-sizing: border-box;
|
|
65
|
+
align-items: center; justify-content: center;
|
|
66
|
+
padding-inline: ${tokens_1.contract.space["2"]};
|
|
67
|
+
font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgPrimary};
|
|
68
|
+
}
|
|
69
|
+
& .quantitypicker-value[data-disabled] { color: ${tokens_1.contract.fgDisabled}; }
|
|
70
|
+
& .quantitypicker-placeholder {
|
|
71
|
+
grid-column: 1; grid-row: 1; visibility: hidden;
|
|
72
|
+
font-variant-numeric: tabular-nums;
|
|
73
|
+
}
|
|
74
|
+
& .quantitypicker-text {
|
|
75
|
+
grid-column: 1; grid-row: 1; text-align: center;
|
|
76
|
+
font-variant-numeric: tabular-nums;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
& .quantitypicker-divider {
|
|
80
|
+
flex-shrink: 0; width: 1px; height: ${tokens_1.contract.space["4"]};
|
|
81
|
+
background: ${tokens_1.contract.borderSubtle};
|
|
82
|
+
}
|
|
83
|
+
`,
|
|
84
|
+
variants: {
|
|
85
|
+
size: {
|
|
86
|
+
small: `
|
|
87
|
+
height: 36px; border-radius: ${tokens_1.contract.radius.md};
|
|
88
|
+
& .quantitypicker-button { width: 36px; height: 36px; border-radius: ${tokens_1.contract.radius.md}; }
|
|
89
|
+
& .quantitypicker-value { font-size: ${tokens_1.contract.text.xl}; line-height: 22px; }
|
|
90
|
+
`,
|
|
91
|
+
medium: `
|
|
92
|
+
height: 40px; border-radius: ${tokens_1.contract.radius.md};
|
|
93
|
+
& .quantitypicker-button { width: 40px; height: 40px; border-radius: ${tokens_1.contract.radius.md}; }
|
|
94
|
+
& .quantitypicker-value { font-size: ${tokens_1.contract.text.xl}; line-height: 22px; }
|
|
95
|
+
`,
|
|
96
|
+
large: `
|
|
97
|
+
height: 52px; border-radius: ${tokens_1.contract.radius.xl};
|
|
98
|
+
& .quantitypicker-button { width: 52px; height: 52px; border-radius: ${tokens_1.contract.radius.xl}; }
|
|
99
|
+
& .quantitypicker-value { font-size: 18px; line-height: 24px; }
|
|
100
|
+
`,
|
|
101
|
+
},
|
|
102
|
+
/** `fill` stretches to the row it sits in; seed's `layout_fill`. */
|
|
103
|
+
fill: {
|
|
104
|
+
true: `width: auto; flex-grow: 1;`,
|
|
105
|
+
false: ``,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
defaultVariants: { size: "medium", fill: false },
|
|
109
|
+
compoundVariants: [],
|
|
110
|
+
});
|
|
111
|
+
exports.quantitypickerRecipe = Object.assign((0, core_1.resolveRecipe)(config), {
|
|
112
|
+
config,
|
|
113
|
+
});
|
|
114
|
+
exports.default = config;
|
package/dist/cjs/index.js
CHANGED
|
@@ -49,6 +49,7 @@ __exportStar(require("./components/middletruncate"), exports);
|
|
|
49
49
|
__exportStar(require("./components/multiselect"), exports);
|
|
50
50
|
__exportStar(require("./components/popover"), exports);
|
|
51
51
|
__exportStar(require("./components/progress"), exports);
|
|
52
|
+
__exportStar(require("./components/quantitypicker"), exports);
|
|
52
53
|
__exportStar(require("./components/radiogroup"), exports);
|
|
53
54
|
__exportStar(require("./components/resizablepanel"), exports);
|
|
54
55
|
__exportStar(require("./components/segmented"), exports);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { QuantityPickerDecrementButton, QuantityPickerHiddenInput, QuantityPickerIncrementButton, QuantityPickerRoot, useQuantityPickerContext, } from "@seed-design/react-quantity-picker";
|
|
4
|
+
import { Minus, Plus, Trash2 } from "lucide-react";
|
|
5
|
+
import { Spinner } from "../spinner/index.js";
|
|
6
|
+
import { quantitypickerRecipe } from "./quantitypicker.recipe.js";
|
|
7
|
+
/** seed's icon box per size, read from their stylesheet. */
|
|
8
|
+
const ICON = { small: 16, medium: 18, large: 22 };
|
|
9
|
+
/**
|
|
10
|
+
* The value, as a grid with a hidden copy of the LARGEST value stacked
|
|
11
|
+
* underneath it. Without that the control gets wider at 10 and wider again at
|
|
12
|
+
* 100, and a row of them stops lining up. `max` is the right placeholder
|
|
13
|
+
* because it is the widest the value can ever be, and it is run through
|
|
14
|
+
* `getValueText` so a formatted value reserves the width of its formatting.
|
|
15
|
+
*/
|
|
16
|
+
function Value() {
|
|
17
|
+
// `QuantityPickerValueDisplay` renders the value as its own children and its
|
|
18
|
+
// props type OMITS `children`, so the two-cell grid cannot go through it.
|
|
19
|
+
// Taking `valueDisplayProps` directly keeps every attribute the engine puts
|
|
20
|
+
// there — aria-live, aria-atomic, the data-state flags — and keeps the
|
|
21
|
+
// rendered text the engine's own rather than a second derivation of it.
|
|
22
|
+
const { max, getValueText, valueDisplayProps } = useQuantityPickerContext();
|
|
23
|
+
const { children: valueText, ...displayProps } = valueDisplayProps;
|
|
24
|
+
return (_jsxs("span", { ...displayProps, className: "quantitypicker-value", children: [_jsx("span", { className: "quantitypicker-placeholder", "aria-hidden": "true", children: getValueText(String(max), max) }), _jsx("span", { className: "quantitypicker-text", children: valueText })] }));
|
|
25
|
+
}
|
|
26
|
+
function DecrementIcon({ size }) {
|
|
27
|
+
const { isRemoveButton, decrementLoading } = useQuantityPickerContext();
|
|
28
|
+
if (decrementLoading)
|
|
29
|
+
return _jsx(Spinner, { size: ICON[size] });
|
|
30
|
+
const Icon = isRemoveButton ? Trash2 : Minus;
|
|
31
|
+
return _jsx(Icon, { size: ICON[size], strokeWidth: 2 });
|
|
32
|
+
}
|
|
33
|
+
function IncrementIcon({ size }) {
|
|
34
|
+
const { incrementLoading } = useQuantityPickerContext();
|
|
35
|
+
if (incrementLoading)
|
|
36
|
+
return _jsx(Spinner, { size: ICON[size] });
|
|
37
|
+
return _jsx(Plus, { size: ICON[size], strokeWidth: 2 });
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A quantity stepper, driven by seed-design's headless engine so the clamping,
|
|
41
|
+
* the step arithmetic, the per-button loading state and the `removable` mode —
|
|
42
|
+
* where decrementing at the minimum becomes "remove this line" rather than a
|
|
43
|
+
* dead button — are theirs by construction.
|
|
44
|
+
*
|
|
45
|
+
* Two things are crisp's:
|
|
46
|
+
*
|
|
47
|
+
* The buttons carry accessible names. The engine labels the minus button only
|
|
48
|
+
* in remove mode, so a plain stepper would ship two buttons whose entire
|
|
49
|
+
* content is an SVG and whose accessible name is empty.
|
|
50
|
+
*
|
|
51
|
+
* `dir` is worth passing explicitly. The engine defaults it to `"ltr"` and
|
|
52
|
+
* stamps that on the root, which overrides an inherited RTL — the same trap
|
|
53
|
+
* Slider documents. Here it also decides child ORDER, since the engine
|
|
54
|
+
* reverses its visible children rather than relying on CSS.
|
|
55
|
+
*/
|
|
56
|
+
export function QuantityPicker({ size = "medium", fill = false, className, name, decrementLabel = "Decrease quantity", incrementLabel = "Increase quantity", ...props }) {
|
|
57
|
+
return (_jsxs(QuantityPickerRoot, { ...props, className: quantitypickerRecipe({ size, fill, className }), children: [_jsx(QuantityPickerDecrementButton, { className: "quantitypicker-button", "aria-label": decrementLabel, children: _jsx(DecrementIcon, { size: size }) }), _jsx("span", { className: "quantitypicker-divider" }), _jsx(Value, {}), _jsx("span", { className: "quantitypicker-divider" }), _jsx(QuantityPickerIncrementButton, { className: "quantitypicker-button", "aria-label": incrementLabel, children: _jsx(IncrementIcon, { size: size }) }), name ? _jsx(QuantityPickerHiddenInput, { name: name }) : null] }));
|
|
58
|
+
}
|
|
59
|
+
QuantityPicker.displayName = "QuantityPicker";
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { defineRecipe, resolveRecipe } from "../../core/index.js";
|
|
2
|
+
import { contract as c } from "../../tokens/index.js";
|
|
3
|
+
// seed-design's quantity-picker, shape for shape, in crisp's colours.
|
|
4
|
+
//
|
|
5
|
+
// Attio has no stepper — its quantity-ish fields are plain number inputs — so
|
|
6
|
+
// as with Slider there is nothing of Attio's to measure and the GEOMETRY is
|
|
7
|
+
// seed's own, read from `@seed-design/css/recipes/quantity-picker.css`:
|
|
8
|
+
//
|
|
9
|
+
// size root height / button square radius icon text
|
|
10
|
+
// small 36px (x9) 8px (r2) 16px 16/22 (t5)
|
|
11
|
+
// medium 40px (x10) 8px (r2) 18px 16/22 (t5)
|
|
12
|
+
// large 52px (x13) 12px (r3) 22px 18/24 (t6)
|
|
13
|
+
//
|
|
14
|
+
// plus an 8px (x2) inline padding on the value, a 1x16 (x4) divider, a 1px
|
|
15
|
+
// inset ring that thickens to 2px when invalid, and a 2px focus ring offset
|
|
16
|
+
// 2px. 16px is crisp's `text.xl`; 18/24 has no crisp token and is written as a
|
|
17
|
+
// literal, the way EmptyState's 20px title is.
|
|
18
|
+
//
|
|
19
|
+
// Only the COLOUR roles are re-pointed at crisp's tokens:
|
|
20
|
+
// seed bg-layer-default -> bgSurface
|
|
21
|
+
// seed stroke-neutral-weak -> borderDefault (the 1px ring)
|
|
22
|
+
// seed stroke-critical-solid -> bgDanger (as Input/Textarea use when invalid)
|
|
23
|
+
// seed stroke-neutral-subtle -> borderSubtle (the divider)
|
|
24
|
+
// seed bg-transparent-pressed -> bgRaised (as Button's ghost hover)
|
|
25
|
+
// seed fg-neutral / fg-disabled-> fgPrimary / fgDisabled
|
|
26
|
+
//
|
|
27
|
+
// Two things in seed's stylesheet are deliberately NOT copied. Their pressed
|
|
28
|
+
// `scale: var(--seed-feedback-scale)` resolves to 1 in the shipped CSS, so it
|
|
29
|
+
// is a no-op to transliterate. And their hover is split across
|
|
30
|
+
// `@media (hover: hover) and (pointer: fine)` / `@media not all and (...)`; no
|
|
31
|
+
// crisp recipe contains an `@media` at all, and neither `build.mjs`'s
|
|
32
|
+
// flattener nor `emit-lynx.mjs` has ever been run against one, so this uses
|
|
33
|
+
// the plain `:hover` every other crisp component uses.
|
|
34
|
+
//
|
|
35
|
+
// The value is a GRID with a hidden placeholder stacked on the text in the
|
|
36
|
+
// same cell. That is what keeps the control from resizing as the number goes
|
|
37
|
+
// 9 -> 10, and it is the detail a hand-built stepper always misses.
|
|
38
|
+
const config = defineRecipe({
|
|
39
|
+
name: "quantitypicker",
|
|
40
|
+
base: `
|
|
41
|
+
display: inline-flex; box-sizing: border-box; width: fit-content;
|
|
42
|
+
align-items: center; font-family: ${c.font.sans};
|
|
43
|
+
background: ${c.bgSurface}; box-shadow: inset 0 0 0 1px ${c.borderDefault};
|
|
44
|
+
|
|
45
|
+
&[data-invalid] { box-shadow: inset 0 0 0 2px ${c.bgDanger}; }
|
|
46
|
+
|
|
47
|
+
& .quantitypicker-button {
|
|
48
|
+
display: inline-flex; flex-shrink: 0; box-sizing: border-box;
|
|
49
|
+
align-items: center; justify-content: center;
|
|
50
|
+
border: none; padding: 0; cursor: pointer;
|
|
51
|
+
background: transparent; color: ${c.fgPrimary};
|
|
52
|
+
transition: background-color .15s ease, outline-color .15s ease;
|
|
53
|
+
outline: 2px solid transparent; outline-offset: 2px;
|
|
54
|
+
}
|
|
55
|
+
& .quantitypicker-button:focus-visible { outline-color: ${c.strokeFocusRing}; }
|
|
56
|
+
& .quantitypicker-button:not([data-disabled]):hover { background: ${c.bgRaised}; }
|
|
57
|
+
& .quantitypicker-button[data-loading] { background: ${c.bgRaised}; }
|
|
58
|
+
& .quantitypicker-button[data-disabled] { cursor: not-allowed; color: ${c.fgDisabled}; }
|
|
59
|
+
|
|
60
|
+
& .quantitypicker-value {
|
|
61
|
+
display: grid; flex-grow: 1; flex-shrink: 0; box-sizing: border-box;
|
|
62
|
+
align-items: center; justify-content: center;
|
|
63
|
+
padding-inline: ${c.space["2"]};
|
|
64
|
+
font-weight: ${c.weight.medium}; color: ${c.fgPrimary};
|
|
65
|
+
}
|
|
66
|
+
& .quantitypicker-value[data-disabled] { color: ${c.fgDisabled}; }
|
|
67
|
+
& .quantitypicker-placeholder {
|
|
68
|
+
grid-column: 1; grid-row: 1; visibility: hidden;
|
|
69
|
+
font-variant-numeric: tabular-nums;
|
|
70
|
+
}
|
|
71
|
+
& .quantitypicker-text {
|
|
72
|
+
grid-column: 1; grid-row: 1; text-align: center;
|
|
73
|
+
font-variant-numeric: tabular-nums;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
& .quantitypicker-divider {
|
|
77
|
+
flex-shrink: 0; width: 1px; height: ${c.space["4"]};
|
|
78
|
+
background: ${c.borderSubtle};
|
|
79
|
+
}
|
|
80
|
+
`,
|
|
81
|
+
variants: {
|
|
82
|
+
size: {
|
|
83
|
+
small: `
|
|
84
|
+
height: 36px; border-radius: ${c.radius.md};
|
|
85
|
+
& .quantitypicker-button { width: 36px; height: 36px; border-radius: ${c.radius.md}; }
|
|
86
|
+
& .quantitypicker-value { font-size: ${c.text.xl}; line-height: 22px; }
|
|
87
|
+
`,
|
|
88
|
+
medium: `
|
|
89
|
+
height: 40px; border-radius: ${c.radius.md};
|
|
90
|
+
& .quantitypicker-button { width: 40px; height: 40px; border-radius: ${c.radius.md}; }
|
|
91
|
+
& .quantitypicker-value { font-size: ${c.text.xl}; line-height: 22px; }
|
|
92
|
+
`,
|
|
93
|
+
large: `
|
|
94
|
+
height: 52px; border-radius: ${c.radius.xl};
|
|
95
|
+
& .quantitypicker-button { width: 52px; height: 52px; border-radius: ${c.radius.xl}; }
|
|
96
|
+
& .quantitypicker-value { font-size: 18px; line-height: 24px; }
|
|
97
|
+
`,
|
|
98
|
+
},
|
|
99
|
+
/** `fill` stretches to the row it sits in; seed's `layout_fill`. */
|
|
100
|
+
fill: {
|
|
101
|
+
true: `width: auto; flex-grow: 1;`,
|
|
102
|
+
false: ``,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
defaultVariants: { size: "medium", fill: false },
|
|
106
|
+
compoundVariants: [],
|
|
107
|
+
});
|
|
108
|
+
export const quantitypickerRecipe = Object.assign(resolveRecipe(config), {
|
|
109
|
+
config,
|
|
110
|
+
});
|
|
111
|
+
export default config;
|
package/dist/esm/index.js
CHANGED
|
@@ -33,6 +33,7 @@ export * from "./components/middletruncate/index.js";
|
|
|
33
33
|
export * from "./components/multiselect/index.js";
|
|
34
34
|
export * from "./components/popover/index.js";
|
|
35
35
|
export * from "./components/progress/index.js";
|
|
36
|
+
export * from "./components/quantitypicker/index.js";
|
|
36
37
|
export * from "./components/radiogroup/index.js";
|
|
37
38
|
export * from "./components/resizablepanel/index.js";
|
|
38
39
|
export * from "./components/segmented/index.js";
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
.quantitypicker {
|
|
2
|
+
|
|
3
|
+
display: inline-flex; box-sizing: border-box; width: fit-content;
|
|
4
|
+
align-items: center; font-family: var(--font-sans);
|
|
5
|
+
background: var(--bg-surface); box-shadow: inset 0 0 0 1px var(--border-default);
|
|
6
|
+
|
|
7
|
+
&[data-invalid] { box-shadow: inset 0 0 0 2px var(--bg-danger); }
|
|
8
|
+
|
|
9
|
+
& .quantitypicker-button {
|
|
10
|
+
display: inline-flex; flex-shrink: 0; box-sizing: border-box;
|
|
11
|
+
align-items: center; justify-content: center;
|
|
12
|
+
border: none; padding: 0; cursor: pointer;
|
|
13
|
+
background: transparent; color: var(--fg-primary);
|
|
14
|
+
transition: background-color .15s ease, outline-color .15s ease;
|
|
15
|
+
outline: 2px solid transparent; outline-offset: 2px;
|
|
16
|
+
}
|
|
17
|
+
& .quantitypicker-button:focus-visible { outline-color: var(--stroke-focus-ring); }
|
|
18
|
+
& .quantitypicker-button:not([data-disabled]):hover { background: var(--bg-raised); }
|
|
19
|
+
& .quantitypicker-button[data-loading] { background: var(--bg-raised); }
|
|
20
|
+
& .quantitypicker-button[data-disabled] { cursor: not-allowed; color: var(--fg-disabled); }
|
|
21
|
+
|
|
22
|
+
& .quantitypicker-value {
|
|
23
|
+
display: grid; flex-grow: 1; flex-shrink: 0; box-sizing: border-box;
|
|
24
|
+
align-items: center; justify-content: center;
|
|
25
|
+
padding-inline: var(--space-2);
|
|
26
|
+
font-weight: var(--weight-medium); color: var(--fg-primary);
|
|
27
|
+
}
|
|
28
|
+
& .quantitypicker-value[data-disabled] { color: var(--fg-disabled); }
|
|
29
|
+
& .quantitypicker-placeholder {
|
|
30
|
+
grid-column: 1; grid-row: 1; visibility: hidden;
|
|
31
|
+
font-variant-numeric: tabular-nums;
|
|
32
|
+
}
|
|
33
|
+
& .quantitypicker-text {
|
|
34
|
+
grid-column: 1; grid-row: 1; text-align: center;
|
|
35
|
+
font-variant-numeric: tabular-nums;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
& .quantitypicker-divider {
|
|
39
|
+
flex-shrink: 0; width: 1px; height: var(--space-4);
|
|
40
|
+
background: var(--border-subtle);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
.quantitypicker--size_small {
|
|
45
|
+
|
|
46
|
+
height: 36px; border-radius: var(--radius-md);
|
|
47
|
+
& .quantitypicker-button { width: 36px; height: 36px; border-radius: var(--radius-md); }
|
|
48
|
+
& .quantitypicker-value { font-size: var(--text-xl); line-height: 22px; }
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
.quantitypicker--size_medium {
|
|
52
|
+
|
|
53
|
+
height: 40px; border-radius: var(--radius-md);
|
|
54
|
+
& .quantitypicker-button { width: 40px; height: 40px; border-radius: var(--radius-md); }
|
|
55
|
+
& .quantitypicker-value { font-size: var(--text-xl); line-height: 22px; }
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
.quantitypicker--size_large {
|
|
59
|
+
|
|
60
|
+
height: 52px; border-radius: var(--radius-xl);
|
|
61
|
+
& .quantitypicker-button { width: 52px; height: 52px; border-radius: var(--radius-xl); }
|
|
62
|
+
& .quantitypicker-value { font-size: 18px; line-height: 24px; }
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
.quantitypicker--fill_true {
|
|
66
|
+
width: auto; flex-grow: 1;
|
|
67
|
+
}
|
|
68
|
+
.quantitypicker--fill_false {
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@media (prefers-reduced-motion: reduce) {
|
|
73
|
+
.quantitypicker, .quantitypicker * {
|
|
74
|
+
animation-duration: 0.01ms !important;
|
|
75
|
+
animation-iteration-count: 1 !important;
|
|
76
|
+
transition-duration: 0.01ms !important;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RecipeFn } from "../core/types";
|
|
2
|
+
|
|
3
|
+
export interface QuantitypickerVariants {
|
|
4
|
+
size?: "small" | "medium" | "large";
|
|
5
|
+
fill?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export declare const quantitypickerRecipe: RecipeFn<{ size: { small: string; medium: string; large: string }; fill: { true: string; false: string } }>;
|
package/dist/styles.css
CHANGED
|
@@ -1175,6 +1175,85 @@
|
|
|
1175
1175
|
}
|
|
1176
1176
|
}
|
|
1177
1177
|
|
|
1178
|
+
.quantitypicker {
|
|
1179
|
+
|
|
1180
|
+
display: inline-flex; box-sizing: border-box; width: fit-content;
|
|
1181
|
+
align-items: center; font-family: var(--font-sans);
|
|
1182
|
+
background: var(--bg-surface); box-shadow: inset 0 0 0 1px var(--border-default);
|
|
1183
|
+
|
|
1184
|
+
&[data-invalid] { box-shadow: inset 0 0 0 2px var(--bg-danger); }
|
|
1185
|
+
|
|
1186
|
+
& .quantitypicker-button {
|
|
1187
|
+
display: inline-flex; flex-shrink: 0; box-sizing: border-box;
|
|
1188
|
+
align-items: center; justify-content: center;
|
|
1189
|
+
border: none; padding: 0; cursor: pointer;
|
|
1190
|
+
background: transparent; color: var(--fg-primary);
|
|
1191
|
+
transition: background-color .15s ease, outline-color .15s ease;
|
|
1192
|
+
outline: 2px solid transparent; outline-offset: 2px;
|
|
1193
|
+
}
|
|
1194
|
+
& .quantitypicker-button:focus-visible { outline-color: var(--stroke-focus-ring); }
|
|
1195
|
+
& .quantitypicker-button:not([data-disabled]):hover { background: var(--bg-raised); }
|
|
1196
|
+
& .quantitypicker-button[data-loading] { background: var(--bg-raised); }
|
|
1197
|
+
& .quantitypicker-button[data-disabled] { cursor: not-allowed; color: var(--fg-disabled); }
|
|
1198
|
+
|
|
1199
|
+
& .quantitypicker-value {
|
|
1200
|
+
display: grid; flex-grow: 1; flex-shrink: 0; box-sizing: border-box;
|
|
1201
|
+
align-items: center; justify-content: center;
|
|
1202
|
+
padding-inline: var(--space-2);
|
|
1203
|
+
font-weight: var(--weight-medium); color: var(--fg-primary);
|
|
1204
|
+
}
|
|
1205
|
+
& .quantitypicker-value[data-disabled] { color: var(--fg-disabled); }
|
|
1206
|
+
& .quantitypicker-placeholder {
|
|
1207
|
+
grid-column: 1; grid-row: 1; visibility: hidden;
|
|
1208
|
+
font-variant-numeric: tabular-nums;
|
|
1209
|
+
}
|
|
1210
|
+
& .quantitypicker-text {
|
|
1211
|
+
grid-column: 1; grid-row: 1; text-align: center;
|
|
1212
|
+
font-variant-numeric: tabular-nums;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
& .quantitypicker-divider {
|
|
1216
|
+
flex-shrink: 0; width: 1px; height: var(--space-4);
|
|
1217
|
+
background: var(--border-subtle);
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
}
|
|
1221
|
+
.quantitypicker--size_small {
|
|
1222
|
+
|
|
1223
|
+
height: 36px; border-radius: var(--radius-md);
|
|
1224
|
+
& .quantitypicker-button { width: 36px; height: 36px; border-radius: var(--radius-md); }
|
|
1225
|
+
& .quantitypicker-value { font-size: var(--text-xl); line-height: 22px; }
|
|
1226
|
+
|
|
1227
|
+
}
|
|
1228
|
+
.quantitypicker--size_medium {
|
|
1229
|
+
|
|
1230
|
+
height: 40px; border-radius: var(--radius-md);
|
|
1231
|
+
& .quantitypicker-button { width: 40px; height: 40px; border-radius: var(--radius-md); }
|
|
1232
|
+
& .quantitypicker-value { font-size: var(--text-xl); line-height: 22px; }
|
|
1233
|
+
|
|
1234
|
+
}
|
|
1235
|
+
.quantitypicker--size_large {
|
|
1236
|
+
|
|
1237
|
+
height: 52px; border-radius: var(--radius-xl);
|
|
1238
|
+
& .quantitypicker-button { width: 52px; height: 52px; border-radius: var(--radius-xl); }
|
|
1239
|
+
& .quantitypicker-value { font-size: 18px; line-height: 24px; }
|
|
1240
|
+
|
|
1241
|
+
}
|
|
1242
|
+
.quantitypicker--fill_true {
|
|
1243
|
+
width: auto; flex-grow: 1;
|
|
1244
|
+
}
|
|
1245
|
+
.quantitypicker--fill_false {
|
|
1246
|
+
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1250
|
+
.quantitypicker, .quantitypicker * {
|
|
1251
|
+
animation-duration: 0.01ms !important;
|
|
1252
|
+
animation-iteration-count: 1 !important;
|
|
1253
|
+
transition-duration: 0.01ms !important;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1178
1257
|
.radiogroup {
|
|
1179
1258
|
|
|
1180
1259
|
display: flex; flex-direction: column; gap: var(--space-1); font-family: var(--font-sans);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type QuantityPickerRootProps } from "@seed-design/react-quantity-picker";
|
|
2
|
+
/** seed's icon box per size, read from their stylesheet. */
|
|
3
|
+
declare const ICON: {
|
|
4
|
+
readonly small: 16;
|
|
5
|
+
readonly medium: 18;
|
|
6
|
+
readonly large: 22;
|
|
7
|
+
};
|
|
8
|
+
export type QuantityPickerSize = keyof typeof ICON;
|
|
9
|
+
export type QuantityPickerProps = QuantityPickerRootProps & {
|
|
10
|
+
/** 36 / 40 / 52px tall. Default `medium`. */
|
|
11
|
+
size?: QuantityPickerSize;
|
|
12
|
+
/** Stretch to the row rather than sizing to content. */
|
|
13
|
+
fill?: boolean;
|
|
14
|
+
/** Renders a hidden input under this name, so the value submits with a form. */
|
|
15
|
+
name?: string;
|
|
16
|
+
/** Accessible name for the minus button. Not used in remove mode — the
|
|
17
|
+
* engine substitutes `removeAriaLabel` there. */
|
|
18
|
+
decrementLabel?: string;
|
|
19
|
+
/** Accessible name for the plus button. */
|
|
20
|
+
incrementLabel?: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* A quantity stepper, driven by seed-design's headless engine so the clamping,
|
|
24
|
+
* the step arithmetic, the per-button loading state and the `removable` mode —
|
|
25
|
+
* where decrementing at the minimum becomes "remove this line" rather than a
|
|
26
|
+
* dead button — are theirs by construction.
|
|
27
|
+
*
|
|
28
|
+
* Two things are crisp's:
|
|
29
|
+
*
|
|
30
|
+
* The buttons carry accessible names. The engine labels the minus button only
|
|
31
|
+
* in remove mode, so a plain stepper would ship two buttons whose entire
|
|
32
|
+
* content is an SVG and whose accessible name is empty.
|
|
33
|
+
*
|
|
34
|
+
* `dir` is worth passing explicitly. The engine defaults it to `"ltr"` and
|
|
35
|
+
* stamps that on the root, which overrides an inherited RTL — the same trap
|
|
36
|
+
* Slider documents. Here it also decides child ORDER, since the engine
|
|
37
|
+
* reverses its visible children rather than relying on CSS.
|
|
38
|
+
*/
|
|
39
|
+
export declare function QuantityPicker({ size, fill, className, name, decrementLabel, incrementLabel, ...props }: QuantityPickerProps): import("react").JSX.Element;
|
|
40
|
+
export declare namespace QuantityPicker {
|
|
41
|
+
var displayName: string;
|
|
42
|
+
}
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
declare const config: import("../..").RecipeConfig<{
|
|
2
|
+
size: {
|
|
3
|
+
small: string;
|
|
4
|
+
medium: string;
|
|
5
|
+
large: string;
|
|
6
|
+
};
|
|
7
|
+
/** `fill` stretches to the row it sits in; seed's `layout_fill`. */
|
|
8
|
+
fill: {
|
|
9
|
+
true: string;
|
|
10
|
+
false: string;
|
|
11
|
+
};
|
|
12
|
+
}>;
|
|
13
|
+
export declare const quantitypickerRecipe: import("../..").RecipeFn<{
|
|
14
|
+
size: {
|
|
15
|
+
small: string;
|
|
16
|
+
medium: string;
|
|
17
|
+
large: string;
|
|
18
|
+
};
|
|
19
|
+
/** `fill` stretches to the row it sits in; seed's `layout_fill`. */
|
|
20
|
+
fill: {
|
|
21
|
+
true: string;
|
|
22
|
+
false: string;
|
|
23
|
+
};
|
|
24
|
+
}> & {
|
|
25
|
+
config: import("../..").RecipeConfig<{
|
|
26
|
+
size: {
|
|
27
|
+
small: string;
|
|
28
|
+
medium: string;
|
|
29
|
+
large: string;
|
|
30
|
+
};
|
|
31
|
+
/** `fill` stretches to the row it sits in; seed's `layout_fill`. */
|
|
32
|
+
fill: {
|
|
33
|
+
true: string;
|
|
34
|
+
false: string;
|
|
35
|
+
};
|
|
36
|
+
}>;
|
|
37
|
+
};
|
|
38
|
+
export default config;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export * from "./components/middletruncate";
|
|
|
29
29
|
export * from "./components/multiselect";
|
|
30
30
|
export * from "./components/popover";
|
|
31
31
|
export * from "./components/progress";
|
|
32
|
+
export * from "./components/quantitypicker";
|
|
32
33
|
export * from "./components/radiogroup";
|
|
33
34
|
export * from "./components/resizablepanel";
|
|
34
35
|
export * from "./components/segmented";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crisp-ui-kit/crisp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "A lean React design system, measured pixel-for-pixel from Attio.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -187,6 +187,11 @@
|
|
|
187
187
|
"import": "./dist/esm/components/progress/index.js",
|
|
188
188
|
"require": "./dist/cjs/components/progress/index.js"
|
|
189
189
|
},
|
|
190
|
+
"./quantitypicker": {
|
|
191
|
+
"types": "./dist/types/components/quantitypicker/index.d.ts",
|
|
192
|
+
"import": "./dist/esm/components/quantitypicker/index.js",
|
|
193
|
+
"require": "./dist/cjs/components/quantitypicker/index.js"
|
|
194
|
+
},
|
|
190
195
|
"./radiogroup": {
|
|
191
196
|
"types": "./dist/types/components/radiogroup/index.d.ts",
|
|
192
197
|
"import": "./dist/esm/components/radiogroup/index.js",
|
|
@@ -412,6 +417,7 @@
|
|
|
412
417
|
"test": "bun test",
|
|
413
418
|
"spec": "bun build/spec-check.mjs",
|
|
414
419
|
"lynx": "bun build/lynx-lint.mjs",
|
|
420
|
+
"lynx:emit": "bun build/emit-lynx.mjs",
|
|
415
421
|
"ssr": "bun scripts/ssr-check.tsx",
|
|
416
422
|
"contrast": "bun scripts/contrast-page.tsx",
|
|
417
423
|
"build:esm": "tsc -p tsconfig.esm.json && bun scripts/fix-esm-extensions.mjs",
|
|
@@ -429,6 +435,7 @@
|
|
|
429
435
|
"@seed-design/react-date-picker": "1.2.0",
|
|
430
436
|
"@seed-design/react-file-upload": "1.0.1",
|
|
431
437
|
"@seed-design/react-middle-truncate": "1.0.1",
|
|
438
|
+
"@seed-design/react-quantity-picker": "1.0.0",
|
|
432
439
|
"@seed-design/react-slider": "2.0.1",
|
|
433
440
|
"@seed-design/react-time-picker": "1.0.0",
|
|
434
441
|
"@zag-js/combobox": "^1.43.3",
|