@crisp-ui-kit/crisp 0.57.1 → 0.57.3
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/authshell.css +9 -4
- package/dist/authshell.layered.css +9 -4
- package/dist/badge.css +3 -3
- package/dist/badge.layered.css +3 -3
- package/dist/board.css +28 -8
- package/dist/board.layered.css +28 -8
- package/dist/bottomsheet.css +26 -0
- package/dist/bottomsheet.d.ts +7 -0
- package/dist/bottomsheet.layered.css +28 -0
- package/dist/button.css +10 -6
- package/dist/button.d.ts +2 -2
- package/dist/button.layered.css +10 -6
- package/dist/checkbox.css +7 -3
- package/dist/checkbox.layered.css +7 -3
- package/dist/chip.css +16 -1
- package/dist/chip.d.ts +3 -1
- package/dist/chip.layered.css +16 -1
- package/dist/chipgroup.css +5 -0
- package/dist/chipgroup.d.ts +7 -0
- package/dist/chipgroup.layered.css +7 -0
- package/dist/cjs/components/badge/badge.recipe.js +22 -8
- package/dist/cjs/components/bottomsheet/bottomsheet.js +90 -0
- package/dist/cjs/components/bottomsheet/bottomsheet.recipe.js +77 -0
- package/dist/cjs/components/bottomsheet/index.js +9 -0
- package/dist/cjs/components/breadcrumb/breadcrumb.js +1 -1
- package/dist/cjs/components/button/button.recipe.js +67 -15
- package/dist/cjs/components/checkbox/checkbox.recipe.js +61 -9
- package/dist/cjs/components/chip/chip.js +14 -2
- package/dist/cjs/components/chip/chip.recipe.js +72 -2
- package/dist/cjs/components/chipgroup/chipgroup.js +51 -0
- package/dist/cjs/components/chipgroup/chipgroup.recipe.js +36 -0
- package/dist/cjs/components/chipgroup/index.js +7 -0
- package/dist/cjs/components/datatable/datatable.js +5 -1
- package/dist/cjs/components/datatable/datatable.recipe.js +21 -6
- package/dist/cjs/components/datepickerfield/datepickerfield.js +2 -2
- package/dist/cjs/components/datepickerfield/datepickerfield.recipe.js +16 -3
- package/dist/cjs/components/dialog/dialog.recipe.js +21 -10
- package/dist/cjs/components/emptystate/emptystate.recipe.js +7 -2
- package/dist/cjs/components/fileupload/fileupload.js +21 -4
- package/dist/cjs/components/input/index.js +2 -1
- package/dist/cjs/components/input/input.js +31 -1
- package/dist/cjs/components/input/input.recipe.js +16 -9
- package/dist/cjs/components/menu/menu.js +18 -2
- package/dist/cjs/components/menu/menu.recipe.js +21 -7
- package/dist/cjs/components/menusheet/index.js +9 -0
- package/dist/cjs/components/menusheet/menusheet.js +133 -0
- package/dist/cjs/components/menusheet/menusheet.recipe.js +103 -0
- package/dist/cjs/components/multiselect/multiselect.js +1 -1
- package/dist/cjs/components/multiselect/multiselect.recipe.js +33 -9
- package/dist/cjs/components/select/select.js +1 -1
- package/dist/cjs/components/select/select.recipe.js +38 -12
- package/dist/cjs/components/snackbar/index.js +8 -0
- package/dist/cjs/components/snackbar/snackbar.js +121 -0
- package/dist/cjs/components/snackbar/snackbar.recipe.js +71 -0
- package/dist/cjs/components/table/table.recipe.js +7 -1
- package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +7 -2
- package/dist/cjs/components/tabletoolbar/tabletoolbar.js +37 -12
- package/dist/cjs/components/tabs/tabs.js +10 -4
- package/dist/cjs/components/tabs/tabs.recipe.js +23 -6
- package/dist/cjs/components/textarea/textarea.recipe.js +11 -8
- package/dist/cjs/core/floating-portal.js +7 -4
- package/dist/cjs/index.js +7 -0
- package/dist/cjs/product/authshell/authshell.recipe.js +5 -1
- package/dist/cjs/product/command/command.recipe.js +4 -1
- package/dist/cjs/product/comments/comments.js +7 -2
- package/dist/cjs/product/comments/comments.recipe.js +25 -7
- package/dist/cjs/product/inbox/inbox.js +11 -3
- package/dist/cjs/product/memberssettings/index.js +6 -0
- package/dist/cjs/product/memberssettings/memberssettings.js +99 -0
- package/dist/cjs/product/memberssettings/memberssettings.recipe.js +83 -0
- package/dist/cjs/product/notificationssettings/index.js +5 -0
- package/dist/cjs/product/notificationssettings/notificationssettings.js +19 -0
- package/dist/cjs/product/notificationssettings/notificationssettings.recipe.js +44 -0
- package/dist/cjs/product/recordpage/index.js +2 -1
- package/dist/cjs/product/recordpage/recordpage.js +106 -9
- package/dist/cjs/product/recordpage/recordpage.recipe.js +68 -6
- package/dist/cjs/product/workspacegeneralsettings/index.js +5 -0
- package/dist/cjs/product/workspacegeneralsettings/workspacegeneralsettings.js +21 -0
- package/dist/cjs/product/workspacegeneralsettings/workspacegeneralsettings.recipe.js +51 -0
- package/dist/cjs/tokens/contract.js +1 -0
- package/dist/cjs/tokens/elevation.js +8 -5
- package/dist/cjs/tokens/palette.js +28 -8
- package/dist/cjs/tokens/scale.js +44 -2
- package/dist/cjs/tokens/semantic.js +2 -0
- package/dist/columncalc.css +15 -7
- package/dist/columncalc.layered.css +15 -7
- package/dist/command.css +23 -19
- package/dist/command.layered.css +23 -19
- package/dist/comments.css +28 -19
- package/dist/comments.d.ts +2 -2
- package/dist/comments.layered.css +28 -19
- package/dist/datatable.css +171 -71
- package/dist/datatable.layered.css +171 -71
- package/dist/datepicker.css +13 -41
- package/dist/datepicker.layered.css +13 -41
- package/dist/datepickerfield.css +12 -3
- package/dist/datepickerfield.d.ts +2 -2
- package/dist/datepickerfield.layered.css +12 -3
- package/dist/dialog.css +12 -7
- package/dist/dialog.layered.css +12 -7
- package/dist/emptystate.css +5 -3
- package/dist/emptystate.layered.css +5 -3
- package/dist/esm/components/badge/badge.recipe.js +22 -8
- package/dist/esm/components/bottomsheet/bottomsheet.js +52 -0
- package/dist/esm/components/bottomsheet/bottomsheet.recipe.js +74 -0
- package/dist/esm/components/bottomsheet/index.js +2 -0
- package/dist/esm/components/breadcrumb/breadcrumb.js +1 -1
- package/dist/esm/components/button/button.recipe.js +68 -16
- package/dist/esm/components/checkbox/checkbox.recipe.js +61 -9
- package/dist/esm/components/chip/chip.js +14 -2
- package/dist/esm/components/chip/chip.recipe.js +72 -2
- package/dist/esm/components/chipgroup/chipgroup.js +15 -0
- package/dist/esm/components/chipgroup/chipgroup.recipe.js +33 -0
- package/dist/esm/components/chipgroup/index.js +2 -0
- package/dist/esm/components/datatable/datatable.js +5 -1
- package/dist/esm/components/datatable/datatable.recipe.js +21 -6
- package/dist/esm/components/datepickerfield/datepickerfield.js +2 -2
- package/dist/esm/components/datepickerfield/datepickerfield.recipe.js +16 -3
- package/dist/esm/components/dialog/dialog.recipe.js +22 -11
- package/dist/esm/components/emptystate/emptystate.recipe.js +7 -2
- package/dist/esm/components/fileupload/fileupload.js +20 -5
- package/dist/esm/components/input/index.js +1 -1
- package/dist/esm/components/input/input.js +30 -0
- package/dist/esm/components/input/input.recipe.js +17 -10
- package/dist/esm/components/menu/menu.js +18 -2
- package/dist/esm/components/menu/menu.recipe.js +21 -7
- package/dist/esm/components/menusheet/index.js +2 -0
- package/dist/esm/components/menusheet/menusheet.js +95 -0
- package/dist/esm/components/menusheet/menusheet.recipe.js +100 -0
- package/dist/esm/components/multiselect/multiselect.js +1 -1
- package/dist/esm/components/multiselect/multiselect.recipe.js +33 -9
- package/dist/esm/components/select/select.js +1 -1
- package/dist/esm/components/select/select.recipe.js +38 -12
- package/dist/esm/components/snackbar/index.js +2 -0
- package/dist/esm/components/snackbar/snackbar.js +84 -0
- package/dist/esm/components/snackbar/snackbar.recipe.js +68 -0
- package/dist/esm/components/table/table.recipe.js +7 -1
- package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +7 -2
- package/dist/esm/components/tabletoolbar/tabletoolbar.js +37 -12
- package/dist/esm/components/tabs/tabs.js +10 -4
- package/dist/esm/components/tabs/tabs.recipe.js +23 -6
- package/dist/esm/components/textarea/textarea.recipe.js +12 -9
- package/dist/esm/core/floating-portal.js +7 -4
- package/dist/esm/index.js +7 -0
- package/dist/esm/product/authshell/authshell.recipe.js +5 -1
- package/dist/esm/product/command/command.recipe.js +4 -1
- package/dist/esm/product/comments/comments.js +7 -2
- package/dist/esm/product/comments/comments.recipe.js +25 -7
- package/dist/esm/product/inbox/inbox.js +11 -3
- package/dist/esm/product/memberssettings/index.js +1 -0
- package/dist/esm/product/memberssettings/memberssettings.js +62 -0
- package/dist/esm/product/memberssettings/memberssettings.recipe.js +80 -0
- package/dist/esm/product/notificationssettings/index.js +1 -0
- package/dist/esm/product/notificationssettings/notificationssettings.js +16 -0
- package/dist/esm/product/notificationssettings/notificationssettings.recipe.js +41 -0
- package/dist/esm/product/recordpage/index.js +1 -1
- package/dist/esm/product/recordpage/recordpage.js +106 -10
- package/dist/esm/product/recordpage/recordpage.recipe.js +68 -6
- package/dist/esm/product/workspacegeneralsettings/index.js +1 -0
- package/dist/esm/product/workspacegeneralsettings/workspacegeneralsettings.js +18 -0
- package/dist/esm/product/workspacegeneralsettings/workspacegeneralsettings.recipe.js +48 -0
- package/dist/esm/tokens/contract.js +2 -1
- package/dist/esm/tokens/elevation.js +8 -5
- package/dist/esm/tokens/palette.js +28 -8
- package/dist/esm/tokens/scale.js +43 -1
- package/dist/esm/tokens/semantic.js +2 -0
- package/dist/fileupload.css +3 -1
- package/dist/fileupload.layered.css +3 -1
- package/dist/input.css +6 -4
- package/dist/input.layered.css +6 -4
- package/dist/listpage.css +3 -1
- package/dist/listpage.layered.css +3 -1
- package/dist/memberssettings.css +67 -0
- package/dist/memberssettings.d.ts +7 -0
- package/dist/memberssettings.layered.css +69 -0
- package/dist/menu.css +2 -2
- package/dist/menu.layered.css +2 -2
- package/dist/menusheet.css +36 -0
- package/dist/menusheet.d.ts +7 -0
- package/dist/menusheet.layered.css +38 -0
- package/dist/multiselect.css +13 -6
- package/dist/multiselect.layered.css +13 -6
- package/dist/native/tokens.d.ts +2 -1
- package/dist/native/tokens.js +20 -3
- package/dist/notificationssettings.css +22 -0
- package/dist/notificationssettings.d.ts +7 -0
- package/dist/notificationssettings.layered.css +24 -0
- package/dist/objectsettings.css +4 -4
- package/dist/objectsettings.layered.css +4 -4
- package/dist/recordpage.css +90 -23
- package/dist/recordpage.layered.css +90 -23
- package/dist/recordpanel.css +35 -16
- package/dist/recordpanel.layered.css +35 -16
- package/dist/resizablepanel.css +8 -5
- package/dist/resizablepanel.layered.css +8 -5
- package/dist/richtext.css +24 -8
- package/dist/richtext.layered.css +24 -8
- package/dist/savedviews.css +9 -2
- package/dist/savedviews.layered.css +9 -2
- package/dist/select.css +25 -5
- package/dist/select.layered.css +25 -5
- package/dist/settingsshell.css +2 -3
- package/dist/settingsshell.layered.css +2 -3
- package/dist/slider.css +6 -27
- package/dist/slider.layered.css +6 -27
- package/dist/snackbar.css +24 -0
- package/dist/snackbar.d.ts +7 -0
- package/dist/snackbar.layered.css +26 -0
- package/dist/statcard.css +3 -1
- package/dist/statcard.layered.css +3 -1
- package/dist/styles.css +911 -354
- package/dist/styles.layered.css +911 -354
- package/dist/table.css +35 -12
- package/dist/table.layered.css +35 -12
- package/dist/tabletoolbar.css +35 -24
- package/dist/tabletoolbar.layered.css +35 -24
- package/dist/tabs.css +18 -4
- package/dist/tabs.layered.css +18 -4
- package/dist/textarea.css +2 -6
- package/dist/textarea.layered.css +2 -6
- package/dist/themepicker.css +2 -1
- package/dist/themepicker.layered.css +2 -1
- package/dist/tokens.css +13 -2
- package/dist/tokens.json +20 -3
- package/dist/types/components/bottomsheet/bottomsheet.d.ts +35 -0
- package/dist/types/components/bottomsheet/bottomsheet.recipe.d.ts +3 -0
- package/dist/types/components/bottomsheet/index.d.ts +2 -0
- package/dist/types/components/breadcrumb/breadcrumb.d.ts +9 -1
- package/dist/types/components/button/button.d.ts +2 -0
- package/dist/types/components/button/button.recipe.d.ts +24 -6
- package/dist/types/components/checkbox/checkbox.recipe.d.ts +28 -5
- package/dist/types/components/chip/chip.d.ts +22 -0
- package/dist/types/components/chip/chip.recipe.d.ts +49 -1
- package/dist/types/components/chipgroup/chipgroup.d.ts +14 -0
- package/dist/types/components/chipgroup/chipgroup.recipe.d.ts +5 -0
- package/dist/types/components/chipgroup/index.d.ts +2 -0
- package/dist/types/components/datatable/datatable.d.ts +19 -0
- package/dist/types/components/datepickerfield/datepickerfield.d.ts +4 -1
- package/dist/types/components/datepickerfield/datepickerfield.recipe.d.ts +18 -3
- package/dist/types/components/fileupload/fileupload.d.ts +26 -0
- package/dist/types/components/input/index.d.ts +1 -1
- package/dist/types/components/input/input.d.ts +72 -0
- package/dist/types/components/menusheet/index.d.ts +2 -0
- package/dist/types/components/menusheet/menusheet.d.ts +42 -0
- package/dist/types/components/menusheet/menusheet.recipe.d.ts +3 -0
- package/dist/types/components/snackbar/index.d.ts +2 -0
- package/dist/types/components/snackbar/snackbar.d.ts +36 -0
- package/dist/types/components/snackbar/snackbar.recipe.d.ts +5 -0
- package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +28 -1
- package/dist/types/components/tabs/tabs.d.ts +11 -1
- package/dist/types/core/floating-portal.d.ts +6 -5
- package/dist/types/index.d.ts +7 -0
- package/dist/types/product/comments/comments.d.ts +9 -3
- package/dist/types/product/comments/comments.recipe.d.ts +12 -2
- package/dist/types/product/inbox/inbox.d.ts +13 -1
- package/dist/types/product/memberssettings/index.d.ts +1 -0
- package/dist/types/product/memberssettings/memberssettings.d.ts +85 -0
- package/dist/types/product/memberssettings/memberssettings.recipe.d.ts +3 -0
- package/dist/types/product/notificationssettings/index.d.ts +1 -0
- package/dist/types/product/notificationssettings/notificationssettings.d.ts +37 -0
- package/dist/types/product/notificationssettings/notificationssettings.recipe.d.ts +3 -0
- package/dist/types/product/recordpage/index.d.ts +1 -1
- package/dist/types/product/recordpage/recordpage.d.ts +99 -2
- package/dist/types/product/workspacegeneralsettings/index.d.ts +1 -0
- package/dist/types/product/workspacegeneralsettings/workspacegeneralsettings.d.ts +48 -0
- package/dist/types/product/workspacegeneralsettings/workspacegeneralsettings.recipe.d.ts +3 -0
- package/dist/types/tokens/contract.d.ts +4 -1
- package/dist/types/tokens/elevation.d.ts +8 -5
- package/dist/types/tokens/palette.d.ts +9 -1
- package/dist/types/tokens/scale.d.ts +11 -1
- package/dist/types/tokens/semantic.d.ts +2 -0
- package/dist/wheelpicker.css +4 -2
- package/dist/wheelpicker.layered.css +4 -2
- package/dist/workspacegeneralsettings.css +30 -0
- package/dist/workspacegeneralsettings.d.ts +7 -0
- package/dist/workspacegeneralsettings.layered.css +32 -0
- package/package.json +36 -1
|
@@ -0,0 +1,133 @@
|
|
|
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.MenuSheet = MenuSheet;
|
|
37
|
+
exports.MenuSheetGroup = MenuSheetGroup;
|
|
38
|
+
exports.MenuSheetItem = MenuSheetItem;
|
|
39
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
40
|
+
const dialog = __importStar(require("@zag-js/dialog"));
|
|
41
|
+
const react_1 = require("@zag-js/react");
|
|
42
|
+
const React = __importStar(require("react"));
|
|
43
|
+
const core_1 = require("../../core");
|
|
44
|
+
const menu_keyboard_1 = require("../menu/menu-keyboard");
|
|
45
|
+
const menusheet_recipe_1 = require("./menusheet.recipe");
|
|
46
|
+
const MenuSheetContext = React.createContext(null);
|
|
47
|
+
let instanceCounter = 0;
|
|
48
|
+
function useMenuSheetInstanceId() {
|
|
49
|
+
const idRef = React.useRef(null);
|
|
50
|
+
if (idRef.current == null) {
|
|
51
|
+
instanceCounter += 1;
|
|
52
|
+
idRef.current = `crisp-menusheet-${instanceCounter}`;
|
|
53
|
+
}
|
|
54
|
+
return idRef.current;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* A mobile menu sheet -- a focus-trapped bottom sheet (same Zag dialog
|
|
58
|
+
* machine as BottomSheet) whose body is a role=menu list, with the identical
|
|
59
|
+
* keyboard model Menu already uses: arrow keys move, Home/End jump,
|
|
60
|
+
* typeahead, Enter/Space select, Escape closes. Compose items with
|
|
61
|
+
* MenuSheetItem, and group a run of them in one rounded card with
|
|
62
|
+
* MenuSheetGroup.
|
|
63
|
+
*/
|
|
64
|
+
function MenuSheet({ open, onOpenChange, title, description, footer, "aria-label": ariaLabel, children, className, "aria-describedby": describedBy, ...rest }) {
|
|
65
|
+
const id = useMenuSheetInstanceId();
|
|
66
|
+
const listRef = React.useRef(null);
|
|
67
|
+
const service = (0, react_1.useMachine)(dialog.machine, {
|
|
68
|
+
id,
|
|
69
|
+
open,
|
|
70
|
+
onOpenChange(details) {
|
|
71
|
+
onOpenChange(details.open);
|
|
72
|
+
},
|
|
73
|
+
"aria-label": typeof title === "string" ? title : ariaLabel,
|
|
74
|
+
onEscapeKeyDown(event) {
|
|
75
|
+
if ((0, core_1.hasOpenFloatingLayer)())
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
},
|
|
78
|
+
// Without this, Zag's own focus trap parks initial focus on the dialog
|
|
79
|
+
// surface itself, which wins the race against useMenuKeyboard's rAF-
|
|
80
|
+
// scheduled "focus the first item" effect below -- the two compete for
|
|
81
|
+
// the same job and Zag's own dialog-level default was landing last.
|
|
82
|
+
// Pointing it at the first menu item directly makes Zag do that job
|
|
83
|
+
// itself instead of fighting useMenuKeyboard for it.
|
|
84
|
+
initialFocusEl() {
|
|
85
|
+
return (listRef.current?.querySelector(menu_keyboard_1.MENU_ITEM_SELECTOR) ?? null);
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
const api = dialog.connect(service, react_1.normalizeProps);
|
|
89
|
+
const close = React.useCallback(() => onOpenChange(false), [onOpenChange]);
|
|
90
|
+
const { onKeyDown: menuKeyDown, items } = (0, menu_keyboard_1.useMenuKeyboard)({
|
|
91
|
+
open: api.open,
|
|
92
|
+
close,
|
|
93
|
+
contentRef: listRef,
|
|
94
|
+
});
|
|
95
|
+
// Roving tabindex: exactly one item is a real Tab stop at a time (#237 --
|
|
96
|
+
// "selected, or first when none" -- is right in every state but the one
|
|
97
|
+
// that matters, so it is enforced imperatively here rather than trusted to
|
|
98
|
+
// a per-render default). useMenuKeyboard moves FOCUS with arrow keys; this
|
|
99
|
+
// keeps the tabindex in sync with wherever that focus actually lands,
|
|
100
|
+
// including the initial auto-focus Zag's own initialFocusEl performs.
|
|
101
|
+
const onFocus = React.useCallback((e) => {
|
|
102
|
+
for (const el of items())
|
|
103
|
+
el.tabIndex = -1;
|
|
104
|
+
e.target.tabIndex = 0;
|
|
105
|
+
}, [items]);
|
|
106
|
+
React.useEffect(() => {
|
|
107
|
+
if (!api.open)
|
|
108
|
+
return;
|
|
109
|
+
const [first] = items();
|
|
110
|
+
if (first)
|
|
111
|
+
first.tabIndex = 0;
|
|
112
|
+
}, [api.open, items]);
|
|
113
|
+
const ctx = React.useMemo(() => ({ close }), [close]);
|
|
114
|
+
if (!api.open)
|
|
115
|
+
return null;
|
|
116
|
+
return ((0, jsx_runtime_1.jsx)(react_1.Portal, { children: (0, jsx_runtime_1.jsxs)("div", { className: (0, menusheet_recipe_1.menusheetRecipe)(), children: [(0, jsx_runtime_1.jsx)("div", { ...api.getBackdropProps(), className: "crisp-menusheet-backdrop" }), (0, jsx_runtime_1.jsx)("div", { ...api.getPositionerProps(), className: "crisp-menusheet-positioner", children: (0, jsx_runtime_1.jsxs)("div", { ...rest, ...(0, core_1.withDescribedBy)(api.getContentProps(), describedBy), className: className
|
|
117
|
+
? `crisp-menusheet-content ${className}`
|
|
118
|
+
: "crisp-menusheet-content", children: [(title != null || description != null) && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-menusheet-header", children: [title != null && ((0, jsx_runtime_1.jsx)("span", { ...api.getTitleProps(), className: "crisp-menusheet-title", children: title })), description != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-menusheet-description", children: description }))] })), (0, jsx_runtime_1.jsx)(MenuSheetContext.Provider, { value: ctx, children: (0, jsx_runtime_1.jsx)("div", { ref: listRef, role: "menu", className: "crisp-menusheet-list", onKeyDown: menuKeyDown, onFocus: onFocus, children: children }) }), footer != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-menusheet-footer", children: footer }))] }) })] }) }));
|
|
119
|
+
}
|
|
120
|
+
MenuSheet.displayName = "MenuSheet";
|
|
121
|
+
function MenuSheetGroup({ children }) {
|
|
122
|
+
return (0, jsx_runtime_1.jsx)("div", { className: "crisp-menusheet-group", children: children });
|
|
123
|
+
}
|
|
124
|
+
MenuSheetGroup.displayName = "MenuSheetGroup";
|
|
125
|
+
function MenuSheetItem({ children, icon, description, tone = "neutral", disabled, onSelect, closeOnSelect = true, }) {
|
|
126
|
+
const ctx = React.useContext(MenuSheetContext);
|
|
127
|
+
return ((0, jsx_runtime_1.jsxs)("button", { type: "button", role: "menuitem", tabIndex: -1, disabled: disabled, "data-disabled": disabled ? "" : undefined, "data-tone": tone, className: "crisp-menusheet-item", onClick: () => {
|
|
128
|
+
onSelect?.();
|
|
129
|
+
if (closeOnSelect)
|
|
130
|
+
ctx?.close();
|
|
131
|
+
}, children: [icon != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-menusheet-item-icon", children: icon })), (0, jsx_runtime_1.jsxs)("span", { className: "crisp-menusheet-item-content", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-menusheet-item-label", children: children }), description != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-menusheet-item-description", children: description }))] })] }));
|
|
132
|
+
}
|
|
133
|
+
MenuSheetItem.displayName = "MenuSheetItem";
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.menusheetRecipe = void 0;
|
|
4
|
+
const core_1 = require("../../core");
|
|
5
|
+
const tokens_1 = require("../../tokens");
|
|
6
|
+
// A mobile menu sheet -- daangn/seed-design components menu-sheet.yaml and
|
|
7
|
+
// menu-sheet-item.yaml (dev at e1c486b904297f7b6a8f17b8523f732c37015d60), not
|
|
8
|
+
// Attio (no such component there; crisp#911). Same @zag-js/dialog machine as
|
|
9
|
+
// BottomSheet, and the identical role=menu keyboard model Menu already uses
|
|
10
|
+
// (useMenuKeyboard, shared rather than reimplemented). Differs from
|
|
11
|
+
// BottomSheet in every slot value: r5 radius (not r6), content-level padding
|
|
12
|
+
// (not per-slot), no header alignment / close-button matrix at all -- menu
|
|
13
|
+
// sheet has neither in its own schema. Every numeric value cites its yaml
|
|
14
|
+
// line. NOTE: no apostrophes in this file -- one broke the recipe compiler
|
|
15
|
+
// silently on BottomSheet, see AUTHORING.md Lesson 64.
|
|
16
|
+
const config = (0, core_1.defineRecipe)({
|
|
17
|
+
name: "menusheet",
|
|
18
|
+
base: `display: contents; font-family: ${tokens_1.contract.font.sans};
|
|
19
|
+
& .crisp-menusheet-backdrop {
|
|
20
|
+
position: fixed; inset: 0; z-index: 60;
|
|
21
|
+
background: ${tokens_1.contract.bgOverlayStrong}; /* menu-sheet.yaml:95 backdrop.color = $color.bg.overlay */
|
|
22
|
+
opacity: 1; transition: opacity 100ms cubic-bezier(0,0,.15,1); /* menu-sheet.yaml:96-98 enterDuration/enterTimingFunction = $duration.d2/$timing-function.enter */
|
|
23
|
+
@starting-style { opacity: 0; }
|
|
24
|
+
}
|
|
25
|
+
& .crisp-menusheet-positioner {
|
|
26
|
+
position: fixed; inset: 0; z-index: 61;
|
|
27
|
+
display: flex; align-items: flex-end; justify-content: center;
|
|
28
|
+
}
|
|
29
|
+
& .crisp-menusheet-content {
|
|
30
|
+
-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
31
|
+
position: relative; box-sizing: border-box; display: flex; flex-direction: column;
|
|
32
|
+
overflow: hidden; width: 100%; max-width: 480px; /* menu-sheet.yaml:104 content.maxWidth */
|
|
33
|
+
max-height: 85vh;
|
|
34
|
+
background: ${tokens_1.contract.bgSurface}; /* menu-sheet.yaml:103 content.color = $color.bg.layer-floating */
|
|
35
|
+
padding: ${tokens_1.contract.space["6"]} ${tokens_1.contract.space["4"]} ${tokens_1.contract.space["4"]}; /* menu-sheet.yaml:105-107 content.paddingX/paddingTop/paddingBottom = global-gutter/x6/x4 (16px/24px/16px) */
|
|
36
|
+
border-radius: ${tokens_1.contract.radius["3xl"]} ${tokens_1.contract.radius["3xl"]} 0 0; /* menu-sheet.yaml:108 content.topCornerRadius = $radius.r5 (20px) */
|
|
37
|
+
transform: translateY(0);
|
|
38
|
+
transition: transform 300ms cubic-bezier(.03,.4,.1,1); /* menu-sheet.yaml:109-110 content.enterDuration/enterTimingFunction (enter-expressive) */
|
|
39
|
+
@starting-style { transform: translateY(100%); }
|
|
40
|
+
}
|
|
41
|
+
& .crisp-menusheet-header {
|
|
42
|
+
display: flex; flex-direction: column;
|
|
43
|
+
gap: ${tokens_1.contract.space["1"]}; /* menu-sheet.yaml:115 header.gap = $dimension.x1 (4px) */
|
|
44
|
+
padding-bottom: ${tokens_1.contract.space["4"]}; /* menu-sheet.yaml:116 header.paddingBottom = $dimension.x4 (16px) */
|
|
45
|
+
flex-shrink: 0;
|
|
46
|
+
}
|
|
47
|
+
& .crisp-menusheet-title {
|
|
48
|
+
color: ${tokens_1.contract.fgPrimary}; /* menu-sheet.yaml:121 title.color = $color.fg.neutral */
|
|
49
|
+
font-size: 18px; line-height: 24px; font-weight: ${tokens_1.contract.weight.bold}; /* menu-sheet.yaml:118-120 title.fontSize/lineHeight/fontWeight = $font-size.t6/$line-height.t6/bold */
|
|
50
|
+
}
|
|
51
|
+
& .crisp-menusheet-description {
|
|
52
|
+
color: ${tokens_1.contract.fgMuted}; /* menu-sheet.yaml:126 description.color = $color.fg.neutral-muted */
|
|
53
|
+
font-size: ${tokens_1.contract.text.lg}; line-height: 19px; font-weight: ${tokens_1.contract.weight.regular}; /* menu-sheet.yaml:123-125 description.fontSize/lineHeight/fontWeight = $font-size.t4/$line-height.t4/regular */
|
|
54
|
+
}
|
|
55
|
+
& .crisp-menusheet-list {
|
|
56
|
+
display: flex; flex-direction: column;
|
|
57
|
+
gap: ${tokens_1.contract.space["2.5"]}; /* menu-sheet.yaml:129 list.gap = $dimension.x2_5 (10px) */
|
|
58
|
+
overflow-y: auto; min-height: 0;
|
|
59
|
+
}
|
|
60
|
+
& .crisp-menusheet-group {
|
|
61
|
+
display: flex; flex-direction: column;
|
|
62
|
+
border-radius: ${tokens_1.contract.radius["2xl"]}; overflow: hidden; /* menu-sheet.yaml:131 group.cornerRadius = $radius.r4 (16px) */
|
|
63
|
+
}
|
|
64
|
+
& .crisp-menusheet-group > * + * { border-top: 1px solid ${tokens_1.contract.strokeGroupDivider}; } /* menu-sheet.yaml:132-134 divider.strokeBottomWidth/strokeColor = 1px/$color.stroke.neutral-muted */
|
|
65
|
+
& .crisp-menusheet-footer {
|
|
66
|
+
flex-shrink: 0;
|
|
67
|
+
padding-top: ${tokens_1.contract.space["2.5"]}; /* menu-sheet.yaml:136 footer.paddingTop = $dimension.x2_5 (10px) */
|
|
68
|
+
}
|
|
69
|
+
& .crisp-menusheet-item {
|
|
70
|
+
position: relative; box-sizing: border-box; display: flex; align-items: center;
|
|
71
|
+
width: 100%; min-height: 52px; /* menu-sheet-item.yaml:72 root.minHeight */
|
|
72
|
+
padding: ${tokens_1.contract.space["3.5"]} ${tokens_1.contract.space["4"]}; gap: ${tokens_1.contract.space["3.5"]}; /* menu-sheet-item.yaml:73-75 root.paddingX/paddingY/gap = x4/x3_5/x3_5 (16px/14px/14px) */
|
|
73
|
+
border: none; margin: 0; cursor: pointer; text-align: start;
|
|
74
|
+
background: ${tokens_1.contract.bgNeutralWeak}; /* menu-sheet-item.yaml:71 root.color = $color.bg.neutral-weak */
|
|
75
|
+
font-family: inherit;
|
|
76
|
+
}
|
|
77
|
+
& .crisp-menusheet-item:hover,
|
|
78
|
+
& .crisp-menusheet-item:active { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 6%, ${tokens_1.contract.bgNeutralWeak}); } /* menu-sheet-item.yaml:90-91 pressed.root.color = $color.bg.neutral-weak-pressed, approximated as a tint over the base fill */
|
|
79
|
+
& .crisp-menusheet-item:active .crisp-menusheet-item-content { transform: scale(0.98); } /* menu-sheet-item.yaml:92 pressed.root.scaleScope = content */
|
|
80
|
+
& .crisp-menusheet-item-icon { flex-shrink: 0; display: inline-flex; width: 22px; height: 22px; } /* menu-sheet-item.yaml:77 prefixIcon.size */
|
|
81
|
+
& .crisp-menusheet-item-content {
|
|
82
|
+
flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column;
|
|
83
|
+
gap: ${tokens_1.contract.space["0.5"]}; /* menu-sheet-item.yaml:79 content.gap = $dimension.x0_5 (2px) */
|
|
84
|
+
transition: transform 150ms cubic-bezier(0,0,.15,1);
|
|
85
|
+
}
|
|
86
|
+
& .crisp-menusheet-item-label {
|
|
87
|
+
font-size: ${tokens_1.contract.text.xl}; line-height: 22px; font-weight: ${tokens_1.contract.weight.regular}; /* menu-sheet-item.yaml:81-83 label.fontSize/lineHeight/fontWeight = $font-size.t5/$line-height.t5/regular */
|
|
88
|
+
}
|
|
89
|
+
& .crisp-menusheet-item-description {
|
|
90
|
+
color: ${tokens_1.contract.fgNeutralSubtle}; /* menu-sheet-item.yaml:88 description.color = $color.fg.neutral-subtle */
|
|
91
|
+
font-size: ${tokens_1.contract.text.md}; line-height: 18px; font-weight: ${tokens_1.contract.weight.medium}; /* menu-sheet-item.yaml:85-87 description.fontSize/lineHeight/fontWeight = $font-size.t3/$line-height.t3/medium */
|
|
92
|
+
}
|
|
93
|
+
& .crisp-menusheet-item[data-tone="neutral"] .crisp-menusheet-item-icon,
|
|
94
|
+
& .crisp-menusheet-item[data-tone="neutral"] .crisp-menusheet-item-label { color: ${tokens_1.contract.fgPrimary}; } /* menu-sheet-item.yaml:93-98 tone=neutral */
|
|
95
|
+
& .crisp-menusheet-item[data-tone="critical"] .crisp-menusheet-item-icon,
|
|
96
|
+
& .crisp-menusheet-item[data-tone="critical"] .crisp-menusheet-item-label { color: ${tokens_1.contract.fgDanger}; } /* menu-sheet-item.yaml:99-104 tone=critical */
|
|
97
|
+
`,
|
|
98
|
+
variants: {},
|
|
99
|
+
defaultVariants: {},
|
|
100
|
+
compoundVariants: [],
|
|
101
|
+
});
|
|
102
|
+
exports.menusheetRecipe = (0, core_1.resolveRecipe)(config);
|
|
103
|
+
exports.default = config;
|
|
@@ -222,7 +222,7 @@ id, placeholder: placeholderProp, searchable = true, searchPlaceholder: searchPl
|
|
|
222
222
|
}, children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-multiselect-chips", children: selectedOptions.length === 0 ? ((0, jsx_runtime_1.jsx)("span", { className: "crisp-multiselect-placeholder", children: placeholder })) : (selectedOptions.map((o) => (0, jsx_runtime_1.jsx)(Chip, { opt: o }, o.value))) }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: "crisp-multiselect-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-multiselect-listbox", role: "listbox", "aria-multiselectable": "true", "aria-label": ariaLabel, "data-up": up ? "" : undefined, style: pos, children: [searchable && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-multiselect-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) => {
|
|
223
223
|
setQuery(e.target.value);
|
|
224
224
|
setHighlight(0);
|
|
225
|
-
}, onKeyDown: onNavKeys })] })), filtered.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-multiselect-empty", children: emptyLabel })) : (filtered.map((opt, i) => {
|
|
225
|
+
}, onKeyDown: onNavKeys })] })), searchable && (0, jsx_runtime_1.jsx)("div", { className: "crisp-multiselect-divider" }), filtered.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-multiselect-empty", children: emptyLabel })) : (filtered.map((opt, i) => {
|
|
226
226
|
const sel = selectedSet.has(opt.value);
|
|
227
227
|
// #343: a header renders once per run of same-`group`
|
|
228
228
|
// options, so consecutive rows sharing a group render one
|
|
@@ -16,10 +16,14 @@ const tokens_1 = require("../../tokens");
|
|
|
16
16
|
// styling (shared with RecordsTable/DataTable elsewhere), the checkbox-style
|
|
17
17
|
// selected indicator, and the toggle-without-closing interaction.
|
|
18
18
|
//
|
|
19
|
-
// The search row
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
19
|
+
// The search row now follows seed too (owner decision, 2026-09-26: Select
|
|
20
|
+
// AND MultiSelect both follow seed-design, superseding #481's old Attio
|
|
21
|
+
// lock on this row's geometry -- crisp/#904's MultiSelect follow-up). It
|
|
22
|
+
// reads the exact same composed @seed-design/rootage `text-input.yaml`
|
|
23
|
+
// (variant=outline,size=medium) values #913 resolved for Select's own
|
|
24
|
+
// search row -- see that recipe's matching comment for the full citation.
|
|
25
|
+
// `build/spec-check.mjs`'s guard was rewritten to assert these seed values
|
|
26
|
+
// instead of the old 36px Attio measurement.
|
|
23
27
|
//
|
|
24
28
|
// The listbox portals to `document.body` (#339), so its rules live in
|
|
25
29
|
// `globalCss`, not nested under `&` -- see Select's own recipe for the full
|
|
@@ -64,13 +68,19 @@ const config = (0, core_1.defineRecipe)({
|
|
|
64
68
|
}
|
|
65
69
|
.crisp-multiselect-listbox[data-up] { transform-origin: bottom left; }
|
|
66
70
|
.crisp-multiselect-listbox { @starting-style { opacity: 0; transform: scale(0.95); } }
|
|
67
|
-
/*
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
/* Seed geometry (build/spec-check.mjs guards these four properties
|
|
72
|
+
directly), superseding #481's old Attio lock -- see this file's
|
|
73
|
+
header comment and Select's matching .crisp-select-search comment
|
|
74
|
+
for the full citation. An ordinary flex child of the listbox, so its
|
|
75
|
+
own 8px gap provides the inset/seam on every side, same as Select. */
|
|
76
|
+
.crisp-multiselect-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}; }
|
|
71
77
|
.crisp-multiselect-search svg { flex-shrink: 0; }
|
|
72
|
-
.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.fgSelectNeutral}; }
|
|
78
|
+
.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}; line-height: 19px; color: ${tokens_1.contract.fgSelectNeutral}; }
|
|
73
79
|
.crisp-multiselect-search input::placeholder { color: ${tokens_1.contract.fgSelectPlaceholder}; }
|
|
80
|
+
/* Same header/body seam as Select's own .crisp-select-divider -- see
|
|
81
|
+
that recipe's comment for the seed source (select.yaml's divider
|
|
82
|
+
slot, stroke.neutral-muted). */
|
|
83
|
+
.crisp-multiselect-divider { flex-shrink: 0; height: 1px; margin-inline: ${tokens_1.contract.space["4"]}; background: ${tokens_1.contract.strokeSelectDivider}; }
|
|
74
84
|
/* Same groupLabel treatment as Select's (13px/regular/neutral-subtle). */
|
|
75
85
|
.crisp-multiselect-label { padding: ${tokens_1.contract.space["2"]} ${tokens_1.contract.space["4"]} 0; font-size: ${tokens_1.contract.text.md}; line-height: 18px; font-weight: ${tokens_1.contract.weight.regular}; color: ${tokens_1.contract.fgNeutralSubtle}; }
|
|
76
86
|
.crisp-multiselect-option {
|
|
@@ -106,6 +116,20 @@ const config = (0, core_1.defineRecipe)({
|
|
|
106
116
|
// default: `c.size.menuRow` (32px) and the original, pre-#882
|
|
107
117
|
// Attio-measured asymmetric padding for a chip-wrapping trigger.
|
|
108
118
|
// `medium`/`large` (seed's own 40/52px) stay, opt-in.
|
|
119
|
+
//
|
|
120
|
+
// crisp/#904 (MultiSelect follow-up, briefly moved then reverted, same
|
|
121
|
+
// ruling as Select's own): `small`'s `gap` moved from `space["1"]`
|
|
122
|
+
// (4px) to `space["2"]` (8px), matching `medium`/`large`'s seed gap --
|
|
123
|
+
// team-lead's review ruling on Select's matching change reversed it:
|
|
124
|
+
// padding AND gap are SPACING, and the owner's direction is "sizes/
|
|
125
|
+
// spacing from Attio, look from seed", not seed's literal gap number.
|
|
126
|
+
// Back to `space["1"]` (4px), the original pre-#882 Attio-measured
|
|
127
|
+
// value. `padding` was never touched either way: the pre-#882,
|
|
128
|
+
// Attio-measured asymmetric padding a chip-wrapping trigger needs
|
|
129
|
+
// (chips sit close to the start edge, the chevron gets more room at
|
|
130
|
+
// the end) -- seed has no multi-value combobox of its own to source a
|
|
131
|
+
// replacement number from (see this file's header comment) even if
|
|
132
|
+
// the ruling had gone the other way.
|
|
109
133
|
size: {
|
|
110
134
|
small: `
|
|
111
135
|
& .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"]}; }
|
|
@@ -221,7 +221,7 @@ function Select({ options, value, defaultValue, onValueChange, name, required, p
|
|
|
221
221
|
setQuery(e.target.value);
|
|
222
222
|
setHighlight(0);
|
|
223
223
|
onSearchChange?.(e.target.value);
|
|
224
|
-
}, onKeyDown: onNavKeys })] })), filtered.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-select-empty", children: emptyLabel })) : (filtered.map((opt, i) => {
|
|
224
|
+
}, onKeyDown: onNavKeys })] })), searchable && (0, jsx_runtime_1.jsx)("div", { className: "crisp-select-divider" }), filtered.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-select-empty", children: emptyLabel })) : (filtered.map((opt, i) => {
|
|
225
225
|
const isSel = opt.value === selected;
|
|
226
226
|
// #343: a header renders once per run of same-`group`
|
|
227
227
|
// options, so consecutive rows sharing a group render one
|
|
@@ -29,11 +29,11 @@ const tokens_1 = require("../../tokens");
|
|
|
29
29
|
// pattern with no precedent anywhere else in the repo, for one component.
|
|
30
30
|
// The enter animation (scale 0.95→1, opacity 0→1, seed's own curve) IS
|
|
31
31
|
// reproduced below.
|
|
32
|
-
// - A
|
|
33
|
-
// crisp or GRC groups options with an explicit separator
|
|
34
|
-
// AUTHORING's rule against building what nothing consumes
|
|
35
|
-
// too.
|
|
36
|
-
//
|
|
32
|
+
// - A general-purpose `divider` slot between arbitrary option groups:
|
|
33
|
+
// nothing in crisp or GRC groups options with an explicit separator
|
|
34
|
+
// today, and AUTHORING's rule against building what nothing consumes
|
|
35
|
+
// applies to CSS too. (`select.yaml`'s resolved value IS used below, just
|
|
36
|
+
// for one specific seam — see `.crisp-select-divider`'s own comment.)
|
|
37
37
|
const config = (0, core_1.defineRecipe)({
|
|
38
38
|
name: "select",
|
|
39
39
|
base: `
|
|
@@ -102,6 +102,20 @@ const config = (0, core_1.defineRecipe)({
|
|
|
102
102
|
.crisp-select-search svg { flex-shrink: 0; }
|
|
103
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}; }
|
|
104
104
|
.crisp-select-search input::placeholder { color: ${tokens_1.contract.fgSelectPlaceholder}; }
|
|
105
|
+
/* select.yaml's own divider slot (marginX x4, height 1px, colour
|
|
106
|
+
stroke.neutral-muted -- described upstream as "the line marking a
|
|
107
|
+
boundary between semantic units: between sections, between content
|
|
108
|
+
and an action area, between a header and a body" -- exactly the
|
|
109
|
+
search-field/option-list boundary crisp#904's review flagged as
|
|
110
|
+
reading too tight). Not the general-purpose separator #867 declined
|
|
111
|
+
to build (nothing groups options with one) -- scoped to this one
|
|
112
|
+
specific header/body seam, which now exists because searchable
|
|
113
|
+
does. Sits as an ordinary flex child, so the listbox's own 8px gap
|
|
114
|
+
still applies on both sides of it -- total visual seam is
|
|
115
|
+
gap+1px+gap (17px), deliberately more than the 8px between two
|
|
116
|
+
plain option rows, so it reads as a section break, not a wider list
|
|
117
|
+
gap. */
|
|
118
|
+
.crisp-select-divider { flex-shrink: 0; height: 1px; margin-inline: ${tokens_1.contract.space["4"]}; background: ${tokens_1.contract.strokeSelectDivider}; }
|
|
105
119
|
.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}; }
|
|
106
120
|
/* select.yaml's groupLabel -- font-size/line-height/weight are the same
|
|
107
121
|
across sizes, only paddingY differs (medium 8px, large 10px). */
|
|
@@ -136,13 +150,25 @@ const config = (0, core_1.defineRecipe)({
|
|
|
136
150
|
},
|
|
137
151
|
// Owner's follow-up after #867/#879 (crisp/#889): the LOOK stays seed's
|
|
138
152
|
// (border, listbox, radius, shadow, items, states, all unchanged below),
|
|
139
|
-
// but the
|
|
140
|
-
// in the same form visibly mismatched (measured live in GRC's
|
|
141
|
-
// employee" dialog). `small` is the new default: Attio's
|
|
142
|
-
// 32px `c.size.menuRow
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
153
|
+
// but the HEIGHT is Attio's again -- a 40px trigger next to a 32px
|
|
154
|
+
// `Input` in the same form visibly mismatched (measured live in GRC's
|
|
155
|
+
// own "New employee" dialog). `small` is the new default: Attio's
|
|
156
|
+
// original 32px `c.size.menuRow`. `medium`/`large` (seed's own 40/52px)
|
|
157
|
+
// stay, opt-in. Item rows are IDENTICAL across all three -- only the
|
|
158
|
+
// trigger differs; only it was ever the mismatch.
|
|
159
|
+
//
|
|
160
|
+
// crisp/#904 (follow-up, then reverted): `small`'s gap/padding-inline
|
|
161
|
+
// briefly moved to seed's literal `select-trigger.yaml` `size=medium`
|
|
162
|
+
// values (8px/14px), on the theory that even the 32px trigger should
|
|
163
|
+
// read seed's numbers verbatim. Team-lead ruling on that PR's review:
|
|
164
|
+
// padding and gap are SPACING, and the owner's actual direction is
|
|
165
|
+
// "sizes/spacing from Attio, look from seed" -- Select and Input sit
|
|
166
|
+
// side by side in every form, so spacing follows Input's measured
|
|
167
|
+
// values here, same as `medium`/`large` below follow seed's. Reverted
|
|
168
|
+
// to `space["1.5"]`/`space["2.5"]` (6px/10px, `Input`'s own gap/padding
|
|
169
|
+
// at this row height) -- everything else `small` already got from seed
|
|
170
|
+
// (fill/stroke/border/radius/chevron/colours/states, see this file's
|
|
171
|
+
// other comments) is unaffected and stays seed's.
|
|
146
172
|
size: {
|
|
147
173
|
small: `
|
|
148
174
|
& .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"]}; }
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.snackbarRecipe = exports.useSnackbar = exports.SnackbarProvider = void 0;
|
|
4
|
+
var snackbar_1 = require("./snackbar");
|
|
5
|
+
Object.defineProperty(exports, "SnackbarProvider", { enumerable: true, get: function () { return snackbar_1.SnackbarProvider; } });
|
|
6
|
+
Object.defineProperty(exports, "useSnackbar", { enumerable: true, get: function () { return snackbar_1.useSnackbar; } });
|
|
7
|
+
var snackbar_recipe_1 = require("./snackbar.recipe");
|
|
8
|
+
Object.defineProperty(exports, "snackbarRecipe", { enumerable: true, get: function () { return snackbar_recipe_1.snackbarRecipe; } });
|
|
@@ -0,0 +1,121 @@
|
|
|
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.SnackbarProvider = SnackbarProvider;
|
|
37
|
+
exports.useSnackbar = useSnackbar;
|
|
38
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
39
|
+
const lucide_react_1 = require("lucide-react");
|
|
40
|
+
const React = __importStar(require("react"));
|
|
41
|
+
const core_1 = require("../../core");
|
|
42
|
+
const snackbar_recipe_1 = require("./snackbar.recipe");
|
|
43
|
+
const SnackbarContext = React.createContext(null);
|
|
44
|
+
const ICONS = {
|
|
45
|
+
positive: lucide_react_1.CircleCheck,
|
|
46
|
+
critical: lucide_react_1.CircleAlert,
|
|
47
|
+
};
|
|
48
|
+
function SnackbarPill({ item, onClose, }) {
|
|
49
|
+
const Icon = ICONS[item.variant ?? "default"];
|
|
50
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-snackbar", "data-variant": item.variant ?? "default", children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-snackbar-content", children: [Icon != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-snackbar-icon", children: (0, jsx_runtime_1.jsx)(Icon, { size: 24, strokeWidth: 1.75, "aria-hidden": "true" }) })), (0, jsx_runtime_1.jsx)("span", { className: "crisp-snackbar-message", children: item.message })] }), item.action && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-snackbar-action", onClick: () => {
|
|
51
|
+
item.action?.onClick();
|
|
52
|
+
onClose();
|
|
53
|
+
}, children: item.action.label }))] }));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Wrap your app in a `SnackbarProvider`, then call `useSnackbar().show({...})`
|
|
57
|
+
* from anywhere to queue a bottom-anchored snackbar. Only one shows at a time
|
|
58
|
+
* (seed-design's mobile pattern, crisp#911) -- a second call queues behind the
|
|
59
|
+
* first and shows once it dismisses. Auto-dismisses after `duration` (default
|
|
60
|
+
* 4s; 0 keeps it until `dismiss()` or its action fires -- there is no close
|
|
61
|
+
* button, matching seed's own anatomy, which has none).
|
|
62
|
+
*/
|
|
63
|
+
function SnackbarProvider({ children, duration = 4000, regionLabel: regionLabelProp, }) {
|
|
64
|
+
const regionLabel = (0, core_1.useCrispMessage)(regionLabelProp, "Notifications");
|
|
65
|
+
const [queue, setQueue] = React.useState([]);
|
|
66
|
+
const idRef = React.useRef(0);
|
|
67
|
+
const timer = React.useRef(null);
|
|
68
|
+
const regionRef = React.useRef(null);
|
|
69
|
+
const cameFrom = React.useRef(null);
|
|
70
|
+
const focusAfterDismiss = (0, core_1.useFocusAfterRemoval)(regionRef, ".crisp-snackbar-action", cameFrom);
|
|
71
|
+
React.useEffect(() => {
|
|
72
|
+
const onFocusIn = (e) => {
|
|
73
|
+
const target = e.target;
|
|
74
|
+
if (!target || regionRef.current?.contains(target))
|
|
75
|
+
return;
|
|
76
|
+
cameFrom.current = target;
|
|
77
|
+
};
|
|
78
|
+
document.addEventListener("focusin", onFocusIn);
|
|
79
|
+
return () => document.removeEventListener("focusin", onFocusIn);
|
|
80
|
+
}, []);
|
|
81
|
+
const dismiss = React.useCallback((id) => {
|
|
82
|
+
setQueue((list) => list.filter((s) => s.id !== id));
|
|
83
|
+
if (timer.current) {
|
|
84
|
+
clearTimeout(timer.current);
|
|
85
|
+
timer.current = null;
|
|
86
|
+
}
|
|
87
|
+
}, []);
|
|
88
|
+
const show = React.useCallback((o) => {
|
|
89
|
+
idRef.current += 1;
|
|
90
|
+
const id = idRef.current;
|
|
91
|
+
setQueue((list) => [...list, { ...o, id }]);
|
|
92
|
+
return id;
|
|
93
|
+
}, []);
|
|
94
|
+
// Only the FRONT of the queue is ever mounted, so its own auto-dismiss
|
|
95
|
+
// timer starts when it becomes visible, not when `show` was called.
|
|
96
|
+
const current = queue[0];
|
|
97
|
+
React.useEffect(() => {
|
|
98
|
+
if (!current)
|
|
99
|
+
return;
|
|
100
|
+
const d = current.duration ?? duration;
|
|
101
|
+
if (d <= 0)
|
|
102
|
+
return;
|
|
103
|
+
timer.current = setTimeout(() => dismiss(current.id), d);
|
|
104
|
+
return () => {
|
|
105
|
+
if (timer.current)
|
|
106
|
+
clearTimeout(timer.current);
|
|
107
|
+
};
|
|
108
|
+
}, [current, duration, dismiss]);
|
|
109
|
+
const api = React.useMemo(() => ({ show, dismiss }), [show, dismiss]);
|
|
110
|
+
return ((0, jsx_runtime_1.jsxs)(SnackbarContext.Provider, { value: api, children: [children, (0, jsx_runtime_1.jsx)("div", { className: (0, snackbar_recipe_1.snackbarRecipe)(), children: (0, jsx_runtime_1.jsx)("div", { ref: regionRef, className: "crisp-snackbar-region", role: "region", "aria-label": regionLabel, "aria-live": "polite", "aria-atomic": "false", children: current && ((0, jsx_runtime_1.jsx)(SnackbarPill, { item: current, onClose: () => {
|
|
111
|
+
focusAfterDismiss(0);
|
|
112
|
+
dismiss(current.id);
|
|
113
|
+
} }, current.id)) }) })] }));
|
|
114
|
+
}
|
|
115
|
+
SnackbarProvider.displayName = "SnackbarProvider";
|
|
116
|
+
function useSnackbar() {
|
|
117
|
+
const ctx = React.useContext(SnackbarContext);
|
|
118
|
+
if (!ctx)
|
|
119
|
+
throw new Error("useSnackbar must be used within a SnackbarProvider");
|
|
120
|
+
return ctx;
|
|
121
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.snackbarRecipe = void 0;
|
|
4
|
+
const core_1 = require("../../core");
|
|
5
|
+
const tokens_1 = require("../../tokens");
|
|
6
|
+
// A mobile-pattern snackbar — daangn/seed-design's
|
|
7
|
+
// packages/rootage/components/snackbar.yaml (dev@e1c486b904297f7b6a8f17b8523f732c37015d60),
|
|
8
|
+
// not Attio (no such component lives there; crisp#911). A single dark
|
|
9
|
+
// `bgNeutralInverted` pill, bottom-anchored, holding an optional tone-coloured
|
|
10
|
+
// prefix icon, a message, and an optional bold action. Every numeric value
|
|
11
|
+
// below cites its yaml line; sizes/radii already equal existing crisp tokens
|
|
12
|
+
// (crisp's space/radius scale was itself derived 2px-granular, same as
|
|
13
|
+
// seed's `dimension`/`radius` scales, so no new tokens were needed).
|
|
14
|
+
//
|
|
15
|
+
// Deviation from the spec's literal `actionButton.color: $color.fg.brand`
|
|
16
|
+
// (snackbar.yaml:128, seed's carrot-600 `#ff6600`): crisp's OWN brand token
|
|
17
|
+
// is TechPartners' verified blue (`bgBrandSolid`, `#0298d9`), not seed's
|
|
18
|
+
// orange (the identity migration takes seed's STRUCTURE, crisp's own colour
|
|
19
|
+
// -- `~/grc-parity/seed-identity-spec.md`). Computed contrast of
|
|
20
|
+
// `bgBrandSolid` on `bgNeutralInverted` (light theme: `#0298d9` on `#2a3038`)
|
|
21
|
+
// is 4.11:1 -- below AA's 4.5:1 for 14px text (bold 14px does not clear the
|
|
22
|
+
// large-text 3:1 threshold, which needs 18.66px). crisp has no brand step
|
|
23
|
+
// bright enough on this dark surface yet, so the action reads in
|
|
24
|
+
// `fgOnNeutralInverted` (the same token the message already uses) at bold
|
|
25
|
+
// weight instead -- distinguished by weight, not colour, until a dedicated
|
|
26
|
+
// token is measured.
|
|
27
|
+
const config = (0, core_1.defineRecipe)({
|
|
28
|
+
name: "snackbar",
|
|
29
|
+
base: `
|
|
30
|
+
& .crisp-snackbar-region {
|
|
31
|
+
position: fixed; z-index: 80; inset-inline: 0; bottom: 0;
|
|
32
|
+
display: flex; justify-content: center;
|
|
33
|
+
padding: ${tokens_1.contract.space["2"]}; /* snackbar.yaml:95-96 region.paddingX/paddingY = $dimension.x2 (8px) */
|
|
34
|
+
pointer-events: none;
|
|
35
|
+
}
|
|
36
|
+
& .crisp-snackbar {
|
|
37
|
+
pointer-events: auto; box-sizing: border-box;
|
|
38
|
+
display: flex; align-items: center; gap: ${tokens_1.contract.space["2.5"]}; /* snackbar.yaml:116 content.gap = $dimension.x2_5 (10px), reused as the icon+message-to-action gap */
|
|
39
|
+
min-height: 44px; max-width: 464px; /* snackbar.yaml:102-103 */
|
|
40
|
+
width: max-content;
|
|
41
|
+
padding: ${tokens_1.contract.space["2.5"]}; /* snackbar.yaml:104-105 root.paddingX/paddingY = $dimension.x2_5 (10px) */
|
|
42
|
+
border-radius: ${tokens_1.contract.radius.md}; /* snackbar.yaml:101 root.cornerRadius = $radius.r2 (8px) */
|
|
43
|
+
background: ${tokens_1.contract.bgNeutralInverted}; /* snackbar.yaml:100 root.color = $color.bg.neutral-inverted */
|
|
44
|
+
font-family: ${tokens_1.contract.font.sans};
|
|
45
|
+
opacity: 1; transform: scale(1);
|
|
46
|
+
transition: opacity 150ms cubic-bezier(0,0,.15,1), transform 150ms cubic-bezier(0,0,.15,1); /* snackbar.yaml:108-109 enterDuration/enterTimingFunction = $duration.d3 (150ms) / $timing-function.enter */
|
|
47
|
+
@starting-style { opacity: 0; transform: scale(0.8); } /* snackbar.yaml:106-107 enterOpacity/enterScale */
|
|
48
|
+
}
|
|
49
|
+
& .crisp-snackbar-content { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: ${tokens_1.contract.space["0.5"]}; } /* snackbar.yaml:124 prefixIcon.paddingRight = $dimension.x0_5 (2px), used as the icon-to-message gap */
|
|
50
|
+
& .crisp-snackbar-icon { flex-shrink: 0; display: inline-flex; }
|
|
51
|
+
& .crisp-snackbar[data-variant="positive"] .crisp-snackbar-icon { color: ${tokens_1.contract.bgSuccess}; } /* snackbar.yaml:138-139 variant=positive prefixIcon.color = $color.fg.positive */
|
|
52
|
+
& .crisp-snackbar[data-variant="critical"] .crisp-snackbar-icon { color: ${tokens_1.contract.bgDanger}; } /* snackbar.yaml:142-143 variant=critical prefixIcon.color = $color.fg.critical */
|
|
53
|
+
& .crisp-snackbar-message { flex: 1 1 auto; min-width: 0; color: ${tokens_1.contract.fgOnNeutralInverted}; font-size: ${tokens_1.contract.text.lg}; line-height: 19px; font-weight: ${tokens_1.contract.weight.regular}; } /* snackbar.yaml:117-121: message.color = $color.fg.neutral-inverted, fontSize/lineHeight = $font-size.t4/$line-height.t4 (14px/19px), fontWeight = regular */
|
|
54
|
+
& .crisp-snackbar-action {
|
|
55
|
+
flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center;
|
|
56
|
+
min-height: 44px; padding: 0 ${tokens_1.contract.space["2"]}; margin: -12px calc(-1 * ${tokens_1.contract.space["2"]}); /* snackbar.yaml:126-127 targetPaddingX/targetMinHeight = $dimension.x2 (8px, doubled here as horizontal padding)/44px -- a larger invisible tap target than the pill's own 44px content row, via negative margin so it doesn't grow the pill visually */
|
|
57
|
+
border: none; border-radius: ${tokens_1.contract.radius.md}; background: none;
|
|
58
|
+
color: ${tokens_1.contract.fgOnNeutralInverted}; font-family: inherit; font-size: ${tokens_1.contract.text.lg}; line-height: 19px; font-weight: ${tokens_1.contract.weight.bold}; /* fontSize/lineHeight = t4/line-height.t4; fontWeight = bold (snackbar.yaml:129-131); colour deviation documented above */
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
transition: transform 150ms cubic-bezier(0,0,.15,1);
|
|
61
|
+
}
|
|
62
|
+
& .crisp-snackbar-action:active { transform: scale(0.98); } /* snackbar.yaml:133-134 pressed.actionButton.scaleScope = self */
|
|
63
|
+
`,
|
|
64
|
+
variants: {},
|
|
65
|
+
defaultVariants: {},
|
|
66
|
+
compoundVariants: [],
|
|
67
|
+
});
|
|
68
|
+
exports.snackbarRecipe = Object.assign((0, core_1.resolveRecipe)(config), {
|
|
69
|
+
config,
|
|
70
|
+
});
|
|
71
|
+
exports.default = config;
|
|
@@ -24,7 +24,13 @@ const config = (0, core_1.defineRecipe)({
|
|
|
24
24
|
(padding: 0 12px) and independently on a body cell's text inset
|
|
25
25
|
(getBoundingClientRect delta from the cell edge). Matches
|
|
26
26
|
ColumnCalc's own already-correct 12px trigger inset. */
|
|
27
|
-
|
|
27
|
+
/* #719: overflow: clip, not hidden -- both render the ellipsis
|
|
28
|
+
identically, but hidden still honours a programmatic scrollLeft
|
|
29
|
+
assignment (a screenshot tool, a browser extension) even though it
|
|
30
|
+
already blocks user-driven wheel/scrollbar input; clip leaves no
|
|
31
|
+
scroll position for anything to move, so the truncated text can
|
|
32
|
+
never be panned out of view. */
|
|
33
|
+
& th, & td { position: relative; height: ${tokens_1.contract.size.tableRow}; padding: 0 ${tokens_1.contract.space["3"]}; border-bottom: 1px solid ${tokens_1.contract.borderSubtle}; border-inline-end: 1px solid ${tokens_1.contract.borderSubtle}; text-align: start; vertical-align: middle; white-space: nowrap; overflow: clip; text-overflow: ellipsis; }
|
|
28
34
|
/* #554: sticky on the vertical axis, mirroring RecordsTable's own
|
|
29
35
|
\`.crisp-recordstable-th\`. z-index 2 sits above a plain body cell (auto)
|
|
30
36
|
and below a sticky-column corner cell (DataTable's \`th[data-sticky]\`,
|