@crisp-ui-kit/crisp 0.39.0 → 0.41.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/datepicker/datepicker.recipe.js +6 -1
- package/dist/cjs/components/dialog/dialog.js +12 -0
- package/dist/cjs/components/drawer/drawer.js +7 -0
- package/dist/cjs/core/floating-stack.js +21 -0
- package/dist/cjs/core/index.js +2 -1
- package/dist/cjs/core/primitive.js +1 -1
- package/dist/cjs/product/recordpanel/recordpanel.js +13 -2
- package/dist/cjs/product/sidebar/sidebar.js +19 -5
- package/dist/datepicker.css +54 -53
- package/dist/datepicker.layered.css +54 -53
- package/dist/esm/components/datepicker/datepicker.recipe.js +6 -1
- package/dist/esm/components/dialog/dialog.js +13 -1
- package/dist/esm/components/drawer/drawer.js +8 -1
- package/dist/esm/core/floating-stack.js +20 -0
- package/dist/esm/core/index.js +1 -1
- package/dist/esm/core/primitive.js +1 -1
- package/dist/esm/product/recordpanel/recordpanel.js +13 -2
- package/dist/esm/product/sidebar/sidebar.js +19 -5
- package/dist/styles.css +54 -53
- package/dist/styles.layered.css +54 -53
- package/dist/types/core/floating-stack.d.ts +18 -0
- package/dist/types/core/index.d.ts +1 -1
- package/dist/types/core/primitive.d.ts +1 -1
- package/dist/types/product/sidebar/sidebar.d.ts +44 -1
- package/package.json +1 -1
|
@@ -59,9 +59,14 @@ const config = (0, core_1.defineRecipe)({
|
|
|
59
59
|
display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
|
|
60
60
|
position: relative; z-index: 2; background: ${tokens_1.contract.bgSurface};
|
|
61
61
|
}
|
|
62
|
+
/* fgNeutralMuted, not seed's fgNeutralSubtle (#367): 3.42:1 in light
|
|
63
|
+
against bgSurface, fails AA for this 12px text. Not an Attio
|
|
64
|
+
measurement -- accessibility wins over the seed reference.
|
|
65
|
+
neutralMuted clears it, 6.62:1 light / 14.71:1 dark (dark was
|
|
66
|
+
already fine at 9.43:1). */
|
|
62
67
|
& .crisp-datepicker-weekday {
|
|
63
68
|
display: flex; align-items: center; justify-content: center;
|
|
64
|
-
height: ${tokens_1.contract.size.calendarCell}; color: ${tokens_1.contract.
|
|
69
|
+
height: ${tokens_1.contract.size.calendarCell}; color: ${tokens_1.contract.fgNeutralMuted};
|
|
65
70
|
font-size: ${tokens_1.contract.text.sm}; line-height: 16px; font-weight: ${tokens_1.contract.weight.medium};
|
|
66
71
|
}
|
|
67
72
|
|
|
@@ -72,6 +72,18 @@ function Dialog({ open, onOpenChange, title, icon, width = 640, "aria-label": ar
|
|
|
72
72
|
// search), rather than the close button; fall back to the machine default.
|
|
73
73
|
initialFocusEl: () => document.querySelector(`[data-crisp-dialog="${id}"] .crisp-dialog-body :where(input, textarea, select, [contenteditable]):not([disabled])`),
|
|
74
74
|
"aria-label": typeof title === "string" ? title : ariaLabel,
|
|
75
|
+
// Defer to a nested Menu/Select/Popover/ContextMenu's own Escape
|
|
76
|
+
// handling (#361): none of those five are Zag machines, so this
|
|
77
|
+
// machine's own dismissable-layer stack has no visibility into them --
|
|
78
|
+
// pressing Escape to close one opened from inside this Dialog closed
|
|
79
|
+
// the Dialog too, since both listen for the same keypress independently.
|
|
80
|
+
// Zag only calls its own dismiss when the event isn't already
|
|
81
|
+
// prevented (`@zag-js/dismissable`), so this cleanly defers instead of
|
|
82
|
+
// racing it.
|
|
83
|
+
onEscapeKeyDown(event) {
|
|
84
|
+
if ((0, core_1.hasOpenFloatingLayer)())
|
|
85
|
+
event.preventDefault();
|
|
86
|
+
},
|
|
75
87
|
});
|
|
76
88
|
const api = dialog.connect(service, react_1.normalizeProps);
|
|
77
89
|
if (!api.open)
|
|
@@ -66,6 +66,13 @@ function Drawer({ open, onOpenChange, side = "right", title, icon, "aria-label":
|
|
|
66
66
|
onOpenChange(details.open);
|
|
67
67
|
},
|
|
68
68
|
"aria-label": typeof title === "string" ? title : ariaLabel,
|
|
69
|
+
// Defer to a nested Menu/Select/Popover/ContextMenu's own Escape
|
|
70
|
+
// handling (#361) -- see Dialog's identical comment; this composes the
|
|
71
|
+
// same Zag machine.
|
|
72
|
+
onEscapeKeyDown(event) {
|
|
73
|
+
if ((0, core_1.hasOpenFloatingLayer)())
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
},
|
|
69
76
|
});
|
|
70
77
|
const api = dialog.connect(service, react_1.normalizeProps);
|
|
71
78
|
if (!api.open)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerFloatingLayer = registerFloatingLayer;
|
|
4
4
|
exports.isInsideLaterFloatingLayer = isInsideLaterFloatingLayer;
|
|
5
|
+
exports.hasOpenFloatingLayer = hasOpenFloatingLayer;
|
|
5
6
|
/**
|
|
6
7
|
* Tracks every currently-open `FloatingPortal` layer, in open order, so a
|
|
7
8
|
* layer's outside-click check can tell "outside me" from "inside a layer I
|
|
@@ -52,3 +53,23 @@ function isInsideLaterFloatingLayer(target, self) {
|
|
|
52
53
|
}
|
|
53
54
|
return false;
|
|
54
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* True when at least one `Select`/`MultiSelect`/`Menu`/`Popover`/
|
|
58
|
+
* `ContextMenu` is currently open, anywhere (#361).
|
|
59
|
+
*
|
|
60
|
+
* A dismissable container that closes on Escape via its OWN raw keydown
|
|
61
|
+
* listener or a library's dismissable-layer machine (`RecordPanel`,
|
|
62
|
+
* `Dialog`, `Drawer`) has no visibility into these five: none of them are
|
|
63
|
+
* built on the same machine, so a Menu opened from inside a `Dialog`
|
|
64
|
+
* registers here, but the Dialog's own Escape handling doesn't know to
|
|
65
|
+
* check for it. Both listeners fire on the same Escape keydown, so both
|
|
66
|
+
* close — the outer container closing was never the point of that
|
|
67
|
+
* keypress, which was meant for the nested layer alone. Call this before
|
|
68
|
+
* closing on Escape (or in a dismissable-layer's `onEscapeKeyDown`, calling
|
|
69
|
+
* `event.preventDefault()` when it's true) to defer instead: the nested
|
|
70
|
+
* layer's own listener still runs and closes it, and the outer container
|
|
71
|
+
* gets its own, uncontested Escape on the next press.
|
|
72
|
+
*/
|
|
73
|
+
function hasOpenFloatingLayer() {
|
|
74
|
+
return stack.length > 0;
|
|
75
|
+
}
|
package/dist/cjs/core/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.usePrefersReducedMotion = exports.useLabelAssociation = exports.useFormReset = exports.useFocusAfterRemoval = exports.specToRecipe = exports.defineSpec = exports.resolveRecipe = exports.Primitive = exports.placeFloating = exports.formValueStyle = exports.registerFloatingLayer = exports.isInsideLaterFloatingLayer = exports.FloatingPortal = exports.isRtlElement = exports.withDescribedBy = exports.defineRecipe = exports.cx = exports.parseCellDate = exports.formatCellDate = void 0;
|
|
17
|
+
exports.usePrefersReducedMotion = exports.useLabelAssociation = exports.useFormReset = exports.useFocusAfterRemoval = exports.specToRecipe = exports.defineSpec = exports.resolveRecipe = exports.Primitive = exports.placeFloating = exports.formValueStyle = exports.registerFloatingLayer = exports.isInsideLaterFloatingLayer = exports.hasOpenFloatingLayer = exports.FloatingPortal = exports.isRtlElement = exports.withDescribedBy = exports.defineRecipe = exports.cx = exports.parseCellDate = exports.formatCellDate = void 0;
|
|
18
18
|
var cell_date_1 = require("./cell-date");
|
|
19
19
|
Object.defineProperty(exports, "formatCellDate", { enumerable: true, get: function () { return cell_date_1.formatCellDate; } });
|
|
20
20
|
Object.defineProperty(exports, "parseCellDate", { enumerable: true, get: function () { return cell_date_1.parseCellDate; } });
|
|
@@ -29,6 +29,7 @@ Object.defineProperty(exports, "isRtlElement", { enumerable: true, get: function
|
|
|
29
29
|
var floating_portal_1 = require("./floating-portal");
|
|
30
30
|
Object.defineProperty(exports, "FloatingPortal", { enumerable: true, get: function () { return floating_portal_1.FloatingPortal; } });
|
|
31
31
|
var floating_stack_1 = require("./floating-stack");
|
|
32
|
+
Object.defineProperty(exports, "hasOpenFloatingLayer", { enumerable: true, get: function () { return floating_stack_1.hasOpenFloatingLayer; } });
|
|
32
33
|
Object.defineProperty(exports, "isInsideLaterFloatingLayer", { enumerable: true, get: function () { return floating_stack_1.isInsideLaterFloatingLayer; } });
|
|
33
34
|
Object.defineProperty(exports, "registerFloatingLayer", { enumerable: true, get: function () { return floating_stack_1.registerFloatingLayer; } });
|
|
34
35
|
var form_value_1 = require("./form-value");
|
|
@@ -37,7 +37,7 @@ exports.Primitive = void 0;
|
|
|
37
37
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
38
38
|
const react_slot_1 = require("@radix-ui/react-slot");
|
|
39
39
|
const React = __importStar(require("react"));
|
|
40
|
-
const NODES = ["button", "div", "span", "a", "input", "p"];
|
|
40
|
+
const NODES = ["button", "div", "span", "a", "input", "p", "nav"];
|
|
41
41
|
exports.Primitive = NODES.reduce((acc, node) => {
|
|
42
42
|
const Component = React.forwardRef(({ asChild, ...props }, ref) => {
|
|
43
43
|
const Comp = asChild ? react_slot_1.Slot : node;
|
|
@@ -38,6 +38,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
38
38
|
const lucide_react_1 = require("lucide-react");
|
|
39
39
|
const React = __importStar(require("react"));
|
|
40
40
|
const avatar_1 = require("../../components/avatar");
|
|
41
|
+
const core_1 = require("../../core");
|
|
41
42
|
const recordstable_1 = require("../recordstable");
|
|
42
43
|
const recordpanel_recipe_1 = require("./recordpanel.recipe");
|
|
43
44
|
function FieldValue({ col, row, emptyLabel, }) {
|
|
@@ -64,11 +65,21 @@ function FieldValue({ col, row, emptyLabel, }) {
|
|
|
64
65
|
*/
|
|
65
66
|
function RecordPanel({ record, columns, onClose, className, closeLabel = "Close panel", title, detailsLabel = "Details", emptyLabel = "Empty", footer, ...rest }) {
|
|
66
67
|
const titleId = React.useId();
|
|
67
|
-
// close on Escape while open
|
|
68
|
+
// close on Escape while open -- but not when a Menu/Select/Popover/
|
|
69
|
+
// ContextMenu opened from inside the panel (e.g. a footer "More actions"
|
|
70
|
+
// menu) is itself open (#361): that Escape belongs to the nested layer,
|
|
71
|
+
// which has its own listener and will close itself. Without this check
|
|
72
|
+
// both close on the same keypress, since neither knows about the other --
|
|
73
|
+
// none of the five are built on this listener or registered anywhere it
|
|
74
|
+
// can see, only on the shared floating-stack this checks instead.
|
|
68
75
|
React.useEffect(() => {
|
|
69
76
|
if (!record)
|
|
70
77
|
return;
|
|
71
|
-
const onKey = (e) =>
|
|
78
|
+
const onKey = (e) => {
|
|
79
|
+
if (e.key !== "Escape" || (0, core_1.hasOpenFloatingLayer)())
|
|
80
|
+
return;
|
|
81
|
+
onClose();
|
|
82
|
+
};
|
|
72
83
|
document.addEventListener("keydown", onKey);
|
|
73
84
|
return () => document.removeEventListener("keydown", onKey);
|
|
74
85
|
}, [record, onClose]);
|
|
@@ -117,7 +117,7 @@ exports.ATTIO_SIDEBAR_FOOTER = {
|
|
|
117
117
|
function SectionHeader({ label, open, onToggle, }) {
|
|
118
118
|
return ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-sidebar-section-header", "data-collapsed": open ? undefined : "", "aria-expanded": open, onClick: onToggle, children: [(0, jsx_runtime_1.jsx)(IconChevronMini, {}), (0, jsx_runtime_1.jsx)("span", { children: label })] }));
|
|
119
119
|
}
|
|
120
|
-
exports.Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse, collapseLabel = "Collapse sidebar", inboxLabel = "Inbox", searchLabel = "Search", workspace = exports.ATTIO_SIDEBAR_WORKSPACE, items = exports.ATTIO_SIDEBAR_ITEMS, recordsSection = exports.ATTIO_SIDEBAR_RECORDS_SECTION, listsSection = exports.ATTIO_SIDEBAR_LISTS_SECTION, chatsSection = exports.ATTIO_SIDEBAR_CHATS_SECTION, groups, footer = exports.ATTIO_SIDEBAR_FOOTER, ...props }, ref) => {
|
|
120
|
+
exports.Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse, collapseLabel = "Collapse sidebar", inboxLabel = "Inbox", searchLabel = "Search", workspace = exports.ATTIO_SIDEBAR_WORKSPACE, items = exports.ATTIO_SIDEBAR_ITEMS, recordsSection = exports.ATTIO_SIDEBAR_RECORDS_SECTION, listsSection = exports.ATTIO_SIDEBAR_LISTS_SECTION, chatsSection = exports.ATTIO_SIDEBAR_CHATS_SECTION, groups, footer = exports.ATTIO_SIDEBAR_FOOTER, commandItems, commandOpen: commandOpenProp, defaultCommandOpen, onCommandOpenChange, commandShortcut = true, "aria-label": ariaLabel = "Main", ...props }, ref) => {
|
|
121
121
|
// Seeded once, on mount, from whatever groups start collapsed — not
|
|
122
122
|
// reactive to `groups` changing later, matching every other piece of
|
|
123
123
|
// this component's state (recordsSection/listsSection/chatsSection have
|
|
@@ -125,8 +125,22 @@ exports.Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse, coll
|
|
|
125
125
|
const [collapsedSections, setCollapsedSections] = React.useState(() => new Set(groups
|
|
126
126
|
?.filter((g) => g.defaultCollapsed)
|
|
127
127
|
.map((g) => g.key ?? g.label)));
|
|
128
|
-
|
|
128
|
+
// Controlled/uncontrolled triad (#364), same shape as Popover/Menu's own
|
|
129
|
+
// `open` -- an app driving a single ⌘K listener needs to be able to set
|
|
130
|
+
// `commandOpen` itself, not just read it.
|
|
131
|
+
const [internalCommandOpen, setInternalCommandOpen] = React.useState(defaultCommandOpen ?? false);
|
|
132
|
+
const commandOpen = commandOpenProp ?? internalCommandOpen;
|
|
133
|
+
const setCommandOpen = React.useCallback((next) => {
|
|
134
|
+
if (commandOpenProp === undefined)
|
|
135
|
+
setInternalCommandOpen(next);
|
|
136
|
+
onCommandOpenChange?.(next);
|
|
137
|
+
}, [commandOpenProp, onCommandOpenChange]);
|
|
129
138
|
React.useEffect(() => {
|
|
139
|
+
// #364: skip attaching entirely when the app owns its own ⌘K listener
|
|
140
|
+
// -- two listeners on the same key both firing opened two overlapping
|
|
141
|
+
// command palettes.
|
|
142
|
+
if (!commandShortcut)
|
|
143
|
+
return;
|
|
130
144
|
function handleKeyDown(event) {
|
|
131
145
|
if ((event.metaKey || event.ctrlKey) &&
|
|
132
146
|
event.key.toLowerCase() === "k") {
|
|
@@ -136,7 +150,7 @@ exports.Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse, coll
|
|
|
136
150
|
}
|
|
137
151
|
document.addEventListener("keydown", handleKeyDown);
|
|
138
152
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
139
|
-
}, []);
|
|
153
|
+
}, [commandShortcut, setCommandOpen]);
|
|
140
154
|
const toggleSection = React.useCallback((key) => {
|
|
141
155
|
setCollapsedSections((prev) => {
|
|
142
156
|
const next = new Set(prev);
|
|
@@ -149,13 +163,13 @@ exports.Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse, coll
|
|
|
149
163
|
return next;
|
|
150
164
|
});
|
|
151
165
|
}, []);
|
|
152
|
-
return ((0, jsx_runtime_1.jsxs)(core_1.Primitive.
|
|
166
|
+
return ((0, jsx_runtime_1.jsxs)(core_1.Primitive.nav, { ref: ref, asChild: asChild, "aria-label": ariaLabel, className: (0, sidebar_recipe_1.sidebarRecipe)({ className }), ...props, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-sidebar-ws", children: [(0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "lg", shape: "rounded", tone: workspace.avatarTone, children: workspace.avatarLabel }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-sidebar-ws-name", children: workspace.name }), (0, jsx_runtime_1.jsx)(IconChevronDown, {}), (0, jsx_runtime_1.jsx)(button_1.Button, { intent: "ghost", size: "sm", shape: "icon", className: "crisp-sidebar-ws-collapse", "aria-label": collapseLabel, onClick: onToggleCollapse, children: (0, jsx_runtime_1.jsx)(IconCollapsePanel, {}) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-sidebar-searchrow", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-sidebar-search", onClick: () => setCommandOpen(true), children: [(0, jsx_runtime_1.jsx)(IconSearch, {}), (0, jsx_runtime_1.jsx)("span", { className: "crisp-sidebar-search-label", children: searchLabel }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-sidebar-search-kbd", children: "\u2318K" })] }), (0, jsx_runtime_1.jsx)(button_1.Button, { intent: "neutral", size: "sm", shape: "icon", "aria-label": inboxLabel, children: (0, jsx_runtime_1.jsx)(IconInbox, {}) })] }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-divider" }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", children: items.map(({ label, icon: ItemIcon, active, trailing }) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: "#", active: active, icon: (0, jsx_runtime_1.jsx)(ItemIcon, {}), trailing: trailing, children: label }, label))) }), groups ? (
|
|
153
167
|
// #325: navigation as data, reusing the exact section markup/
|
|
154
168
|
// recipe above — SectionHeader + .crisp-sidebar-section — instead
|
|
155
169
|
// of the three fixed Attio sections.
|
|
156
170
|
groups.map((group) => {
|
|
157
171
|
const groupKey = group.key ?? group.label;
|
|
158
172
|
return ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [(0, jsx_runtime_1.jsx)(SectionHeader, { label: group.label, open: !collapsedSections.has(groupKey), onToggle: () => toggleSection(groupKey) }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has(groupKey) ? "" : undefined, children: group.items.map((item) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: item.href ?? "#", active: item.active, icon: item.icon ? (0, jsx_runtime_1.jsx)(item.icon, {}) : undefined, trailing: item.count, onClick: item.onSelect, children: item.label }, item.href ?? item.label))) })] }, groupKey));
|
|
159
|
-
})) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(SectionHeader, { label: recordsSection.label, open: !collapsedSections.has("records"), onToggle: () => toggleSection("records") }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("records") ? "" : undefined, children: recordsSection.items.map(({ label, tone, glyph: Glyph }) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: "#", icon: (0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "sm", shape: "rounded", tone: tone, children: (0, jsx_runtime_1.jsx)(Glyph, {}) }), children: label }, label))) }), (0, jsx_runtime_1.jsx)(SectionHeader, { label: listsSection.label, open: !collapsedSections.has("lists"), onToggle: () => toggleSection("lists") }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("lists") ? "" : undefined, children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-sidebar-newlist", children: listsSection.newListLabel }) }), (0, jsx_runtime_1.jsx)(SectionHeader, { label: chatsSection.label, open: !collapsedSections.has("chats"), onToggle: () => toggleSection("chats") }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("chats") ? "" : undefined, children: chatsSection.items.map(({ label, icon: ItemIcon }) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: "#", icon: (0, jsx_runtime_1.jsx)(ItemIcon, {}), children: label }, label))) })] })), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-sidebar-footer", children: [(0, jsx_runtime_1.jsx)(button_1.Button, { intent: "neutral", size: "sm", className: "crisp-sidebar-pill", children: footer.gettingStartedLabel }), (0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: "#", icon: (0, jsx_runtime_1.jsx)(IconInvite, {}), children: footer.inviteLabel }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-divider" }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-sidebar-trial", children: [(0, jsx_runtime_1.jsx)("span", { children: footer.trialText }), (0, jsx_runtime_1.jsx)(button_1.Button, { intent: "neutral", size: "sm", children: footer.trialActionLabel })] })] }), (0, jsx_runtime_1.jsx)(command_1.Command, { open: commandOpen, onOpenChange: setCommandOpen })] }));
|
|
173
|
+
})) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(SectionHeader, { label: recordsSection.label, open: !collapsedSections.has("records"), onToggle: () => toggleSection("records") }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("records") ? "" : undefined, children: recordsSection.items.map(({ label, tone, glyph: Glyph }) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: "#", icon: (0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "sm", shape: "rounded", tone: tone, children: (0, jsx_runtime_1.jsx)(Glyph, {}) }), children: label }, label))) }), (0, jsx_runtime_1.jsx)(SectionHeader, { label: listsSection.label, open: !collapsedSections.has("lists"), onToggle: () => toggleSection("lists") }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("lists") ? "" : undefined, children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-sidebar-newlist", children: listsSection.newListLabel }) }), (0, jsx_runtime_1.jsx)(SectionHeader, { label: chatsSection.label, open: !collapsedSections.has("chats"), onToggle: () => toggleSection("chats") }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("chats") ? "" : undefined, children: chatsSection.items.map(({ label, icon: ItemIcon }) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: "#", icon: (0, jsx_runtime_1.jsx)(ItemIcon, {}), children: label }, label))) })] })), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-sidebar-footer", children: [(0, jsx_runtime_1.jsx)(button_1.Button, { intent: "neutral", size: "sm", className: "crisp-sidebar-pill", children: footer.gettingStartedLabel }), (0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: "#", icon: (0, jsx_runtime_1.jsx)(IconInvite, {}), children: footer.inviteLabel }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-divider" }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-sidebar-trial", children: [(0, jsx_runtime_1.jsx)("span", { children: footer.trialText }), (0, jsx_runtime_1.jsx)(button_1.Button, { intent: "neutral", size: "sm", children: footer.trialActionLabel })] })] }), (0, jsx_runtime_1.jsx)(command_1.Command, { open: commandOpen, onOpenChange: setCommandOpen, items: commandItems })] }));
|
|
160
174
|
});
|
|
161
175
|
exports.Sidebar.displayName = "Sidebar";
|
package/dist/datepicker.css
CHANGED
|
@@ -11,47 +11,47 @@
|
|
|
11
11
|
& .crisp-datepicker-nav-button:focus-visible { outline-color: var(--crisp-stroke-focus-ring); }
|
|
12
12
|
& .crisp-datepicker-nav-button:disabled { opacity: .4; cursor: not-allowed; }
|
|
13
13
|
& .crisp-datepicker-weekday-row { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); position: relative; z-index: 2; background: var(--crisp-bg-surface); }
|
|
14
|
-
|
|
14
|
+
/* fgNeutralMuted, not seed's fgNeutralSubtle (#367): 3.42:1 in light
|
|
15
|
+
against bgSurface, fails AA for this 12px text. Not an Attio
|
|
16
|
+
measurement -- accessibility wins over the seed reference.
|
|
17
|
+
neutralMuted clears it, 6.62:1 light / 14.71:1 dark (dark was
|
|
18
|
+
already fine at 9.43:1). */
|
|
19
|
+
& .crisp-datepicker-weekday {
|
|
20
|
+
display: flex; align-items: center; justify-content: center;
|
|
21
|
+
height: var(--crisp-size-calendarCell); color: var(--crisp-fg-neutral-muted);
|
|
22
|
+
font-size: var(--crisp-text-sm); line-height: 16px; font-weight: var(--crisp-weight-medium);
|
|
23
|
+
}
|
|
24
|
+
|
|
15
25
|
& .crisp-datepicker-months { display: grid; grid-template-columns: minmax(0, 1fr); align-items: start; min-width: 0; }
|
|
16
26
|
& .crisp-datepicker-months[data-wheel-open] { visibility: hidden; }
|
|
17
27
|
& .crisp-datepicker-month { min-width: 0; }
|
|
18
|
-
& .crisp-datepicker-month-label {
|
|
28
|
+
& .crisp-datepicker-month-label {
|
|
29
|
+
display: flex; align-items: center; height: 24px;
|
|
30
|
+
color: var(--crisp-fg-primary); font-size: var(--crisp-text-lg); line-height: 20px;
|
|
31
|
+
font-weight: var(--crisp-weight-medium);
|
|
32
|
+
}
|
|
33
|
+
|
|
19
34
|
& .crisp-datepicker-grid { display: grid; min-width: 0; }
|
|
20
35
|
& .crisp-datepicker-week-row { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
|
|
21
|
-
& .crisp-datepicker-cell { position: relative; min-width: 0; min-height: var(--crisp-size-calendarCell); isolation: isolate; }
|
|
22
|
-
& .crisp-datepicker-empty-cell { min-height: var(--crisp-size-calendarCell); }
|
|
23
|
-
/* The bridge joining days inside a completed range. The endpoints do NOT carry data-in-range -- only the interior days do -- so these are three independent rules, not a base plus two modifiers. A modifier that sets only inset-inline-start never generates a pseudo element on an endpoint, which leaves a half-cell of white between the endpoint's pill and the run of grey beside it. Each rule declares its
|
|
24
|
-
own content. */
|
|
25
|
-
& .crisp-datepicker-cell[data-range-complete][data-in-range]::before {
|
|
26
|
-
content: ""; position: absolute; z-index: -1; inset-inline: 0;
|
|
27
|
-
top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak);
|
|
28
|
-
}
|
|
29
|
-
& .crisp-datepicker-cell[data-range-complete][data-range-start]:not([data-range-end])::before {
|
|
30
|
-
content: ""; position: absolute; z-index: -1;
|
|
31
|
-
inset-inline-start: 50%; inset-inline-end: 0;
|
|
32
|
-
top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak);
|
|
33
|
-
}
|
|
34
|
-
& .crisp-datepicker-cell[data-range-complete][data-range-end]:not([data-range-start])::before {
|
|
35
|
-
content: ""; position: absolute; z-index: -1;
|
|
36
|
-
inset-inline-start: 0; inset-inline-end: 50%;
|
|
37
|
-
top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak);
|
|
38
|
-
}
|
|
39
36
|
|
|
40
|
-
& .crisp-datepicker-
|
|
41
|
-
position: relative;
|
|
42
|
-
min-height: var(--crisp-size-calendarCell); min-width: 0;
|
|
43
|
-
align-items: flex-start; justify-content: center;
|
|
44
|
-
border: 0; padding: 0; background: transparent; cursor: pointer;
|
|
45
|
-
color: var(--crisp-fg-primary); font: inherit; outline: none;
|
|
37
|
+
& .crisp-datepicker-cell {
|
|
38
|
+
position: relative; min-width: 0; min-height: var(--crisp-size-calendarCell);
|
|
46
39
|
isolation: isolate;
|
|
47
40
|
}
|
|
41
|
+
& .crisp-datepicker-empty-cell { min-height: var(--crisp-size-calendarCell); }
|
|
42
|
+
|
|
43
|
+
/* The bridge joining days inside a completed range.
|
|
44
|
+
The endpoints do NOT carry data-in-range -- only the interior days do --
|
|
45
|
+
so these are three independent rules, not a base plus two modifiers. A
|
|
46
|
+
modifier that sets only inset-inline-start never generates a pseudo
|
|
47
|
+
element on an endpoint, which leaves a half-cell of white between the
|
|
48
|
+
endpoint's pill and the run of grey beside it. Each rule declares its own content. */
|
|
49
|
+
& .crisp-datepicker-cell[data-range-complete][data-in-range]::before { content: ""; position: absolute; z-index: -1; inset-inline: 0; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak); }
|
|
50
|
+
& .crisp-datepicker-cell[data-range-complete][data-range-start]:not([data-range-end])::before { content: ""; position: absolute; z-index: -1; inset-inline-start: 50%; inset-inline-end: 0; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak); }
|
|
51
|
+
& .crisp-datepicker-cell[data-range-complete][data-range-end]:not([data-range-start])::before { content: ""; position: absolute; z-index: -1; inset-inline-start: 0; inset-inline-end: 50%; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak); }
|
|
52
|
+
& .crisp-datepicker-date { position: relative; display: flex; width: 100%; height: 100%; min-height: var(--crisp-size-calendarCell); min-width: 0; align-items: flex-start; justify-content: center; border: 0; padding: 0; background: transparent; cursor: pointer; color: var(--crisp-fg-primary); font: inherit; outline: none; isolation: isolate; }
|
|
48
53
|
/* the round day highlight, behind the numeral */
|
|
49
|
-
& .crisp-datepicker-date::before {
|
|
50
|
-
content: ""; position: absolute; z-index: -1; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); left: 50%;
|
|
51
|
-
width: var(--crisp-size-calendarPill); height: var(--crisp-size-calendarPill);
|
|
52
|
-
border-radius: var(--crisp-radius-md);
|
|
53
|
-
background: transparent; transform: translateX(-50%);
|
|
54
|
-
}
|
|
54
|
+
& .crisp-datepicker-date::before { content: ""; position: absolute; z-index: -1; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); left: 50%; width: var(--crisp-size-calendarPill); height: var(--crisp-size-calendarPill); border-radius: var(--crisp-radius-md); background: transparent; transform: translateX(-50%); }
|
|
55
55
|
& .crisp-datepicker-date[data-today]::before { background: var(--crisp-bg-neutral-weak); }
|
|
56
56
|
& .crisp-datepicker-date[data-selected]::before { background: var(--crisp-bg-brand-solid); }
|
|
57
57
|
& .crisp-datepicker-date[data-selected] { color: var(--crisp-fg-on-brand); }
|
|
@@ -59,34 +59,35 @@
|
|
|
59
59
|
& .crisp-datepicker-date[data-in-range]::before { background: transparent; }
|
|
60
60
|
& .crisp-datepicker-date[data-unavailable] { color: var(--crisp-fg-disabled); }
|
|
61
61
|
& .crisp-datepicker-date:disabled { color: var(--crisp-fg-disabled); cursor: not-allowed; }
|
|
62
|
-
& .crisp-datepicker-date:focus-visible::after {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
& .crisp-datepicker-date:focus-visible::after { content: ""; position: absolute; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); left: 50%; width: var(--crisp-size-calendarPill); height: var(--crisp-size-calendarPill); transform: translateX(-50%); border-radius: var(--crisp-radius-md); outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: -2px; }
|
|
63
|
+
& .crisp-datepicker-date-content { position: relative; z-index: 1; display: flex; width: 100%; min-height: var(--crisp-size-calendarCell); min-width: 0; box-sizing: border-box; flex-direction: column; align-items: center; gap: 3px; padding-top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); color: inherit; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); font-variant-numeric: tabular-nums; }
|
|
64
|
+
/* The numeral is its own 42px flex box so it centres on the 42px pill. Without this the content column top-aligns the glyph and the pill reads as offset — the cell is 48px, the pill 42px inset 3px, and only a 42px-tall numeral box shares the pill's centre line. */
|
|
65
|
+
& .crisp-datepicker-date-content > [data-date-picker-day] {
|
|
66
|
+
display: flex; min-height: var(--crisp-size-calendarPill); flex-shrink: 0;
|
|
67
|
+
align-items: center; justify-content: center;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
& .crisp-datepicker-date-content {
|
|
70
|
-
position: relative; z-index: 1; display: flex; width: 100%;
|
|
71
|
-
min-height: var(--crisp-size-calendarCell); min-width: 0; box-sizing: border-box;
|
|
72
|
-
flex-direction: column; align-items: center; gap: 3px;
|
|
73
|
-
padding-top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); color: inherit;
|
|
74
|
-
font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium);
|
|
75
|
-
font-variant-numeric: tabular-nums;
|
|
76
|
-
}
|
|
77
|
-
/* The numeral is its own 42px flex box so it centres on the 42px pill.
|
|
78
|
-
Without this the content column top-aligns the glyph and the pill reads
|
|
79
|
-
as offset — the cell is 48px, the pill 42px inset 3px, and only a
|
|
80
|
-
42px-tall numeral box shares the pill's centre line. */
|
|
81
|
-
& .crisp-datepicker-date-content > [data-date-picker-day] { display: flex; min-height: var(--crisp-size-calendarPill); flex-shrink: 0; align-items: center; justify-content: center; }
|
|
82
70
|
/* the year/month wheel, laid over the grid it replaces */
|
|
83
|
-
& .crisp-datepicker-wheel-container {
|
|
71
|
+
& .crisp-datepicker-wheel-container {
|
|
72
|
+
position: absolute; z-index: 1; inset-inline: 0;
|
|
73
|
+
top: var(--crisp-size-menuRow); bottom: 0;
|
|
74
|
+
display: flex; align-items: center; overflow: hidden;
|
|
75
|
+
background: var(--crisp-bg-surface);
|
|
76
|
+
}
|
|
84
77
|
& .crisp-datepicker-wheel { width: 100%; }
|
|
85
|
-
|
|
78
|
+
|
|
79
|
+
& .crisp-datepicker-continuous-scroll {
|
|
80
|
+
height: 100%; min-height: 0; overflow-y: auto;
|
|
81
|
+
overscroll-behavior: contain; scrollbar-width: none;
|
|
82
|
+
}
|
|
86
83
|
& .crisp-datepicker-continuous-scroll::-webkit-scrollbar { display: none; }
|
|
87
84
|
& .crisp-datepicker-continuous-content { display: flex; flex-direction: column; }
|
|
85
|
+
|
|
88
86
|
/* announced, never drawn */
|
|
89
|
-
& .crisp-datepicker-live-region {
|
|
87
|
+
& .crisp-datepicker-live-region {
|
|
88
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
89
|
+
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
|
|
90
|
+
}
|
|
90
91
|
}
|
|
91
92
|
.crisp-datepicker--visibleRange_month {
|
|
92
93
|
|
|
@@ -12,47 +12,47 @@
|
|
|
12
12
|
& .crisp-datepicker-nav-button:focus-visible { outline-color: var(--crisp-stroke-focus-ring); }
|
|
13
13
|
& .crisp-datepicker-nav-button:disabled { opacity: .4; cursor: not-allowed; }
|
|
14
14
|
& .crisp-datepicker-weekday-row { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); position: relative; z-index: 2; background: var(--crisp-bg-surface); }
|
|
15
|
-
|
|
15
|
+
/* fgNeutralMuted, not seed's fgNeutralSubtle (#367): 3.42:1 in light
|
|
16
|
+
against bgSurface, fails AA for this 12px text. Not an Attio
|
|
17
|
+
measurement -- accessibility wins over the seed reference.
|
|
18
|
+
neutralMuted clears it, 6.62:1 light / 14.71:1 dark (dark was
|
|
19
|
+
already fine at 9.43:1). */
|
|
20
|
+
& .crisp-datepicker-weekday {
|
|
21
|
+
display: flex; align-items: center; justify-content: center;
|
|
22
|
+
height: var(--crisp-size-calendarCell); color: var(--crisp-fg-neutral-muted);
|
|
23
|
+
font-size: var(--crisp-text-sm); line-height: 16px; font-weight: var(--crisp-weight-medium);
|
|
24
|
+
}
|
|
25
|
+
|
|
16
26
|
& .crisp-datepicker-months { display: grid; grid-template-columns: minmax(0, 1fr); align-items: start; min-width: 0; }
|
|
17
27
|
& .crisp-datepicker-months[data-wheel-open] { visibility: hidden; }
|
|
18
28
|
& .crisp-datepicker-month { min-width: 0; }
|
|
19
|
-
& .crisp-datepicker-month-label {
|
|
29
|
+
& .crisp-datepicker-month-label {
|
|
30
|
+
display: flex; align-items: center; height: 24px;
|
|
31
|
+
color: var(--crisp-fg-primary); font-size: var(--crisp-text-lg); line-height: 20px;
|
|
32
|
+
font-weight: var(--crisp-weight-medium);
|
|
33
|
+
}
|
|
34
|
+
|
|
20
35
|
& .crisp-datepicker-grid { display: grid; min-width: 0; }
|
|
21
36
|
& .crisp-datepicker-week-row { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
|
|
22
|
-
& .crisp-datepicker-cell { position: relative; min-width: 0; min-height: var(--crisp-size-calendarCell); isolation: isolate; }
|
|
23
|
-
& .crisp-datepicker-empty-cell { min-height: var(--crisp-size-calendarCell); }
|
|
24
|
-
/* The bridge joining days inside a completed range. The endpoints do NOT carry data-in-range -- only the interior days do -- so these are three independent rules, not a base plus two modifiers. A modifier that sets only inset-inline-start never generates a pseudo element on an endpoint, which leaves a half-cell of white between the endpoint's pill and the run of grey beside it. Each rule declares its
|
|
25
|
-
own content. */
|
|
26
|
-
& .crisp-datepicker-cell[data-range-complete][data-in-range]::before {
|
|
27
|
-
content: ""; position: absolute; z-index: -1; inset-inline: 0;
|
|
28
|
-
top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak);
|
|
29
|
-
}
|
|
30
|
-
& .crisp-datepicker-cell[data-range-complete][data-range-start]:not([data-range-end])::before {
|
|
31
|
-
content: ""; position: absolute; z-index: -1;
|
|
32
|
-
inset-inline-start: 50%; inset-inline-end: 0;
|
|
33
|
-
top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak);
|
|
34
|
-
}
|
|
35
|
-
& .crisp-datepicker-cell[data-range-complete][data-range-end]:not([data-range-start])::before {
|
|
36
|
-
content: ""; position: absolute; z-index: -1;
|
|
37
|
-
inset-inline-start: 0; inset-inline-end: 50%;
|
|
38
|
-
top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak);
|
|
39
|
-
}
|
|
40
37
|
|
|
41
|
-
& .crisp-datepicker-
|
|
42
|
-
position: relative;
|
|
43
|
-
min-height: var(--crisp-size-calendarCell); min-width: 0;
|
|
44
|
-
align-items: flex-start; justify-content: center;
|
|
45
|
-
border: 0; padding: 0; background: transparent; cursor: pointer;
|
|
46
|
-
color: var(--crisp-fg-primary); font: inherit; outline: none;
|
|
38
|
+
& .crisp-datepicker-cell {
|
|
39
|
+
position: relative; min-width: 0; min-height: var(--crisp-size-calendarCell);
|
|
47
40
|
isolation: isolate;
|
|
48
41
|
}
|
|
42
|
+
& .crisp-datepicker-empty-cell { min-height: var(--crisp-size-calendarCell); }
|
|
43
|
+
|
|
44
|
+
/* The bridge joining days inside a completed range.
|
|
45
|
+
The endpoints do NOT carry data-in-range -- only the interior days do --
|
|
46
|
+
so these are three independent rules, not a base plus two modifiers. A
|
|
47
|
+
modifier that sets only inset-inline-start never generates a pseudo
|
|
48
|
+
element on an endpoint, which leaves a half-cell of white between the
|
|
49
|
+
endpoint's pill and the run of grey beside it. Each rule declares its own content. */
|
|
50
|
+
& .crisp-datepicker-cell[data-range-complete][data-in-range]::before { content: ""; position: absolute; z-index: -1; inset-inline: 0; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak); }
|
|
51
|
+
& .crisp-datepicker-cell[data-range-complete][data-range-start]:not([data-range-end])::before { content: ""; position: absolute; z-index: -1; inset-inline-start: 50%; inset-inline-end: 0; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak); }
|
|
52
|
+
& .crisp-datepicker-cell[data-range-complete][data-range-end]:not([data-range-start])::before { content: ""; position: absolute; z-index: -1; inset-inline-start: 0; inset-inline-end: 50%; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak); }
|
|
53
|
+
& .crisp-datepicker-date { position: relative; display: flex; width: 100%; height: 100%; min-height: var(--crisp-size-calendarCell); min-width: 0; align-items: flex-start; justify-content: center; border: 0; padding: 0; background: transparent; cursor: pointer; color: var(--crisp-fg-primary); font: inherit; outline: none; isolation: isolate; }
|
|
49
54
|
/* the round day highlight, behind the numeral */
|
|
50
|
-
& .crisp-datepicker-date::before {
|
|
51
|
-
content: ""; position: absolute; z-index: -1; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); left: 50%;
|
|
52
|
-
width: var(--crisp-size-calendarPill); height: var(--crisp-size-calendarPill);
|
|
53
|
-
border-radius: var(--crisp-radius-md);
|
|
54
|
-
background: transparent; transform: translateX(-50%);
|
|
55
|
-
}
|
|
55
|
+
& .crisp-datepicker-date::before { content: ""; position: absolute; z-index: -1; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); left: 50%; width: var(--crisp-size-calendarPill); height: var(--crisp-size-calendarPill); border-radius: var(--crisp-radius-md); background: transparent; transform: translateX(-50%); }
|
|
56
56
|
& .crisp-datepicker-date[data-today]::before { background: var(--crisp-bg-neutral-weak); }
|
|
57
57
|
& .crisp-datepicker-date[data-selected]::before { background: var(--crisp-bg-brand-solid); }
|
|
58
58
|
& .crisp-datepicker-date[data-selected] { color: var(--crisp-fg-on-brand); }
|
|
@@ -60,34 +60,35 @@
|
|
|
60
60
|
& .crisp-datepicker-date[data-in-range]::before { background: transparent; }
|
|
61
61
|
& .crisp-datepicker-date[data-unavailable] { color: var(--crisp-fg-disabled); }
|
|
62
62
|
& .crisp-datepicker-date:disabled { color: var(--crisp-fg-disabled); cursor: not-allowed; }
|
|
63
|
-
& .crisp-datepicker-date:focus-visible::after {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
& .crisp-datepicker-date:focus-visible::after { content: ""; position: absolute; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); left: 50%; width: var(--crisp-size-calendarPill); height: var(--crisp-size-calendarPill); transform: translateX(-50%); border-radius: var(--crisp-radius-md); outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: -2px; }
|
|
64
|
+
& .crisp-datepicker-date-content { position: relative; z-index: 1; display: flex; width: 100%; min-height: var(--crisp-size-calendarCell); min-width: 0; box-sizing: border-box; flex-direction: column; align-items: center; gap: 3px; padding-top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); color: inherit; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); font-variant-numeric: tabular-nums; }
|
|
65
|
+
/* The numeral is its own 42px flex box so it centres on the 42px pill. Without this the content column top-aligns the glyph and the pill reads as offset — the cell is 48px, the pill 42px inset 3px, and only a 42px-tall numeral box shares the pill's centre line. */
|
|
66
|
+
& .crisp-datepicker-date-content > [data-date-picker-day] {
|
|
67
|
+
display: flex; min-height: var(--crisp-size-calendarPill); flex-shrink: 0;
|
|
68
|
+
align-items: center; justify-content: center;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
& .crisp-datepicker-date-content {
|
|
71
|
-
position: relative; z-index: 1; display: flex; width: 100%;
|
|
72
|
-
min-height: var(--crisp-size-calendarCell); min-width: 0; box-sizing: border-box;
|
|
73
|
-
flex-direction: column; align-items: center; gap: 3px;
|
|
74
|
-
padding-top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); color: inherit;
|
|
75
|
-
font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium);
|
|
76
|
-
font-variant-numeric: tabular-nums;
|
|
77
|
-
}
|
|
78
|
-
/* The numeral is its own 42px flex box so it centres on the 42px pill.
|
|
79
|
-
Without this the content column top-aligns the glyph and the pill reads
|
|
80
|
-
as offset — the cell is 48px, the pill 42px inset 3px, and only a
|
|
81
|
-
42px-tall numeral box shares the pill's centre line. */
|
|
82
|
-
& .crisp-datepicker-date-content > [data-date-picker-day] { display: flex; min-height: var(--crisp-size-calendarPill); flex-shrink: 0; align-items: center; justify-content: center; }
|
|
83
71
|
/* the year/month wheel, laid over the grid it replaces */
|
|
84
|
-
& .crisp-datepicker-wheel-container {
|
|
72
|
+
& .crisp-datepicker-wheel-container {
|
|
73
|
+
position: absolute; z-index: 1; inset-inline: 0;
|
|
74
|
+
top: var(--crisp-size-menuRow); bottom: 0;
|
|
75
|
+
display: flex; align-items: center; overflow: hidden;
|
|
76
|
+
background: var(--crisp-bg-surface);
|
|
77
|
+
}
|
|
85
78
|
& .crisp-datepicker-wheel { width: 100%; }
|
|
86
|
-
|
|
79
|
+
|
|
80
|
+
& .crisp-datepicker-continuous-scroll {
|
|
81
|
+
height: 100%; min-height: 0; overflow-y: auto;
|
|
82
|
+
overscroll-behavior: contain; scrollbar-width: none;
|
|
83
|
+
}
|
|
87
84
|
& .crisp-datepicker-continuous-scroll::-webkit-scrollbar { display: none; }
|
|
88
85
|
& .crisp-datepicker-continuous-content { display: flex; flex-direction: column; }
|
|
86
|
+
|
|
89
87
|
/* announced, never drawn */
|
|
90
|
-
& .crisp-datepicker-live-region {
|
|
88
|
+
& .crisp-datepicker-live-region {
|
|
89
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
90
|
+
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
|
|
91
|
+
}
|
|
91
92
|
}
|
|
92
93
|
.crisp-datepicker--visibleRange_month {
|
|
93
94
|
|
|
@@ -56,9 +56,14 @@ const config = defineRecipe({
|
|
|
56
56
|
display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
|
|
57
57
|
position: relative; z-index: 2; background: ${c.bgSurface};
|
|
58
58
|
}
|
|
59
|
+
/* fgNeutralMuted, not seed's fgNeutralSubtle (#367): 3.42:1 in light
|
|
60
|
+
against bgSurface, fails AA for this 12px text. Not an Attio
|
|
61
|
+
measurement -- accessibility wins over the seed reference.
|
|
62
|
+
neutralMuted clears it, 6.62:1 light / 14.71:1 dark (dark was
|
|
63
|
+
already fine at 9.43:1). */
|
|
59
64
|
& .crisp-datepicker-weekday {
|
|
60
65
|
display: flex; align-items: center; justify-content: center;
|
|
61
|
-
height: ${c.size.calendarCell}; color: ${c.
|
|
66
|
+
height: ${c.size.calendarCell}; color: ${c.fgNeutralMuted};
|
|
62
67
|
font-size: ${c.text.sm}; line-height: 16px; font-weight: ${c.weight.medium};
|
|
63
68
|
}
|
|
64
69
|
|
|
@@ -3,7 +3,7 @@ import * as dialog from "@zag-js/dialog";
|
|
|
3
3
|
import { normalizeProps, Portal, useMachine } from "@zag-js/react";
|
|
4
4
|
import { X } from "lucide-react";
|
|
5
5
|
import * as React from "react";
|
|
6
|
-
import { cx, withDescribedBy } from "../../core/index.js";
|
|
6
|
+
import { cx, hasOpenFloatingLayer, withDescribedBy } from "../../core/index.js";
|
|
7
7
|
import { dialogRecipe } from "./dialog.recipe.js";
|
|
8
8
|
// A globally unique id per mounted instance (see command.tsx for the rationale —
|
|
9
9
|
// the machine defers its dismissal listeners a frame, so tree-relative useId
|
|
@@ -34,6 +34,18 @@ export function Dialog({ open, onOpenChange, title, icon, width = 640, "aria-lab
|
|
|
34
34
|
// search), rather than the close button; fall back to the machine default.
|
|
35
35
|
initialFocusEl: () => document.querySelector(`[data-crisp-dialog="${id}"] .crisp-dialog-body :where(input, textarea, select, [contenteditable]):not([disabled])`),
|
|
36
36
|
"aria-label": typeof title === "string" ? title : ariaLabel,
|
|
37
|
+
// Defer to a nested Menu/Select/Popover/ContextMenu's own Escape
|
|
38
|
+
// handling (#361): none of those five are Zag machines, so this
|
|
39
|
+
// machine's own dismissable-layer stack has no visibility into them --
|
|
40
|
+
// pressing Escape to close one opened from inside this Dialog closed
|
|
41
|
+
// the Dialog too, since both listen for the same keypress independently.
|
|
42
|
+
// Zag only calls its own dismiss when the event isn't already
|
|
43
|
+
// prevented (`@zag-js/dismissable`), so this cleanly defers instead of
|
|
44
|
+
// racing it.
|
|
45
|
+
onEscapeKeyDown(event) {
|
|
46
|
+
if (hasOpenFloatingLayer())
|
|
47
|
+
event.preventDefault();
|
|
48
|
+
},
|
|
37
49
|
});
|
|
38
50
|
const api = dialog.connect(service, normalizeProps);
|
|
39
51
|
if (!api.open)
|
|
@@ -3,7 +3,7 @@ import * as dialog from "@zag-js/dialog";
|
|
|
3
3
|
import { normalizeProps, Portal, useMachine } from "@zag-js/react";
|
|
4
4
|
import { X } from "lucide-react";
|
|
5
5
|
import * as React from "react";
|
|
6
|
-
import { cx, withDescribedBy } from "../../core/index.js";
|
|
6
|
+
import { cx, hasOpenFloatingLayer, withDescribedBy } from "../../core/index.js";
|
|
7
7
|
import { drawerRecipe } from "./drawer.recipe.js";
|
|
8
8
|
let instanceCounter = 0;
|
|
9
9
|
function useDrawerInstanceId() {
|
|
@@ -28,6 +28,13 @@ export function Drawer({ open, onOpenChange, side = "right", title, icon, "aria-
|
|
|
28
28
|
onOpenChange(details.open);
|
|
29
29
|
},
|
|
30
30
|
"aria-label": typeof title === "string" ? title : ariaLabel,
|
|
31
|
+
// Defer to a nested Menu/Select/Popover/ContextMenu's own Escape
|
|
32
|
+
// handling (#361) -- see Dialog's identical comment; this composes the
|
|
33
|
+
// same Zag machine.
|
|
34
|
+
onEscapeKeyDown(event) {
|
|
35
|
+
if (hasOpenFloatingLayer())
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
},
|
|
31
38
|
});
|
|
32
39
|
const api = dialog.connect(service, normalizeProps);
|
|
33
40
|
if (!api.open)
|
|
@@ -48,3 +48,23 @@ export function isInsideLaterFloatingLayer(target, self) {
|
|
|
48
48
|
}
|
|
49
49
|
return false;
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* True when at least one `Select`/`MultiSelect`/`Menu`/`Popover`/
|
|
53
|
+
* `ContextMenu` is currently open, anywhere (#361).
|
|
54
|
+
*
|
|
55
|
+
* A dismissable container that closes on Escape via its OWN raw keydown
|
|
56
|
+
* listener or a library's dismissable-layer machine (`RecordPanel`,
|
|
57
|
+
* `Dialog`, `Drawer`) has no visibility into these five: none of them are
|
|
58
|
+
* built on the same machine, so a Menu opened from inside a `Dialog`
|
|
59
|
+
* registers here, but the Dialog's own Escape handling doesn't know to
|
|
60
|
+
* check for it. Both listeners fire on the same Escape keydown, so both
|
|
61
|
+
* close — the outer container closing was never the point of that
|
|
62
|
+
* keypress, which was meant for the nested layer alone. Call this before
|
|
63
|
+
* closing on Escape (or in a dismissable-layer's `onEscapeKeyDown`, calling
|
|
64
|
+
* `event.preventDefault()` when it's true) to defer instead: the nested
|
|
65
|
+
* layer's own listener still runs and closes it, and the outer container
|
|
66
|
+
* gets its own, uncontested Escape on the next press.
|
|
67
|
+
*/
|
|
68
|
+
export function hasOpenFloatingLayer() {
|
|
69
|
+
return stack.length > 0;
|
|
70
|
+
}
|
package/dist/esm/core/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { defineRecipe } from "./define.js";
|
|
|
4
4
|
export { withDescribedBy } from "./dialog-description.js";
|
|
5
5
|
export { isRtlElement } from "./direction.js";
|
|
6
6
|
export { FloatingPortal } from "./floating-portal.js";
|
|
7
|
-
export { isInsideLaterFloatingLayer, registerFloatingLayer, } from "./floating-stack.js";
|
|
7
|
+
export { hasOpenFloatingLayer, isInsideLaterFloatingLayer, registerFloatingLayer, } from "./floating-stack.js";
|
|
8
8
|
export { formValueStyle } from "./form-value.js";
|
|
9
9
|
export { placeFloating, } from "./place-floating.js";
|
|
10
10
|
export { Primitive } from "./primitive.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Slot } from "@radix-ui/react-slot";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
const NODES = ["button", "div", "span", "a", "input", "p"];
|
|
4
|
+
const NODES = ["button", "div", "span", "a", "input", "p", "nav"];
|
|
5
5
|
export const Primitive = NODES.reduce((acc, node) => {
|
|
6
6
|
const Component = React.forwardRef(({ asChild, ...props }, ref) => {
|
|
7
7
|
const Comp = asChild ? Slot : node;
|
|
@@ -2,6 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { X } from "lucide-react";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Avatar } from "../../components/avatar/index.js";
|
|
5
|
+
import { hasOpenFloatingLayer } from "../../core/index.js";
|
|
5
6
|
import { tagTone, } from "../recordstable/index.js";
|
|
6
7
|
import { recordpanelRecipe } from "./recordpanel.recipe.js";
|
|
7
8
|
function FieldValue({ col, row, emptyLabel, }) {
|
|
@@ -28,11 +29,21 @@ function FieldValue({ col, row, emptyLabel, }) {
|
|
|
28
29
|
*/
|
|
29
30
|
export function RecordPanel({ record, columns, onClose, className, closeLabel = "Close panel", title, detailsLabel = "Details", emptyLabel = "Empty", footer, ...rest }) {
|
|
30
31
|
const titleId = React.useId();
|
|
31
|
-
// close on Escape while open
|
|
32
|
+
// close on Escape while open -- but not when a Menu/Select/Popover/
|
|
33
|
+
// ContextMenu opened from inside the panel (e.g. a footer "More actions"
|
|
34
|
+
// menu) is itself open (#361): that Escape belongs to the nested layer,
|
|
35
|
+
// which has its own listener and will close itself. Without this check
|
|
36
|
+
// both close on the same keypress, since neither knows about the other --
|
|
37
|
+
// none of the five are built on this listener or registered anywhere it
|
|
38
|
+
// can see, only on the shared floating-stack this checks instead.
|
|
32
39
|
React.useEffect(() => {
|
|
33
40
|
if (!record)
|
|
34
41
|
return;
|
|
35
|
-
const onKey = (e) =>
|
|
42
|
+
const onKey = (e) => {
|
|
43
|
+
if (e.key !== "Escape" || hasOpenFloatingLayer())
|
|
44
|
+
return;
|
|
45
|
+
onClose();
|
|
46
|
+
};
|
|
36
47
|
document.addEventListener("keydown", onKey);
|
|
37
48
|
return () => document.removeEventListener("keydown", onKey);
|
|
38
49
|
}, [record, onClose]);
|
|
@@ -81,7 +81,7 @@ export const ATTIO_SIDEBAR_FOOTER = {
|
|
|
81
81
|
function SectionHeader({ label, open, onToggle, }) {
|
|
82
82
|
return (_jsxs("button", { type: "button", className: "crisp-sidebar-section-header", "data-collapsed": open ? undefined : "", "aria-expanded": open, onClick: onToggle, children: [_jsx(IconChevronMini, {}), _jsx("span", { children: label })] }));
|
|
83
83
|
}
|
|
84
|
-
export const Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse, collapseLabel = "Collapse sidebar", inboxLabel = "Inbox", searchLabel = "Search", workspace = ATTIO_SIDEBAR_WORKSPACE, items = ATTIO_SIDEBAR_ITEMS, recordsSection = ATTIO_SIDEBAR_RECORDS_SECTION, listsSection = ATTIO_SIDEBAR_LISTS_SECTION, chatsSection = ATTIO_SIDEBAR_CHATS_SECTION, groups, footer = ATTIO_SIDEBAR_FOOTER, ...props }, ref) => {
|
|
84
|
+
export const Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse, collapseLabel = "Collapse sidebar", inboxLabel = "Inbox", searchLabel = "Search", workspace = ATTIO_SIDEBAR_WORKSPACE, items = ATTIO_SIDEBAR_ITEMS, recordsSection = ATTIO_SIDEBAR_RECORDS_SECTION, listsSection = ATTIO_SIDEBAR_LISTS_SECTION, chatsSection = ATTIO_SIDEBAR_CHATS_SECTION, groups, footer = ATTIO_SIDEBAR_FOOTER, commandItems, commandOpen: commandOpenProp, defaultCommandOpen, onCommandOpenChange, commandShortcut = true, "aria-label": ariaLabel = "Main", ...props }, ref) => {
|
|
85
85
|
// Seeded once, on mount, from whatever groups start collapsed — not
|
|
86
86
|
// reactive to `groups` changing later, matching every other piece of
|
|
87
87
|
// this component's state (recordsSection/listsSection/chatsSection have
|
|
@@ -89,8 +89,22 @@ export const Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse,
|
|
|
89
89
|
const [collapsedSections, setCollapsedSections] = React.useState(() => new Set(groups
|
|
90
90
|
?.filter((g) => g.defaultCollapsed)
|
|
91
91
|
.map((g) => g.key ?? g.label)));
|
|
92
|
-
|
|
92
|
+
// Controlled/uncontrolled triad (#364), same shape as Popover/Menu's own
|
|
93
|
+
// `open` -- an app driving a single ⌘K listener needs to be able to set
|
|
94
|
+
// `commandOpen` itself, not just read it.
|
|
95
|
+
const [internalCommandOpen, setInternalCommandOpen] = React.useState(defaultCommandOpen ?? false);
|
|
96
|
+
const commandOpen = commandOpenProp ?? internalCommandOpen;
|
|
97
|
+
const setCommandOpen = React.useCallback((next) => {
|
|
98
|
+
if (commandOpenProp === undefined)
|
|
99
|
+
setInternalCommandOpen(next);
|
|
100
|
+
onCommandOpenChange?.(next);
|
|
101
|
+
}, [commandOpenProp, onCommandOpenChange]);
|
|
93
102
|
React.useEffect(() => {
|
|
103
|
+
// #364: skip attaching entirely when the app owns its own ⌘K listener
|
|
104
|
+
// -- two listeners on the same key both firing opened two overlapping
|
|
105
|
+
// command palettes.
|
|
106
|
+
if (!commandShortcut)
|
|
107
|
+
return;
|
|
94
108
|
function handleKeyDown(event) {
|
|
95
109
|
if ((event.metaKey || event.ctrlKey) &&
|
|
96
110
|
event.key.toLowerCase() === "k") {
|
|
@@ -100,7 +114,7 @@ export const Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse,
|
|
|
100
114
|
}
|
|
101
115
|
document.addEventListener("keydown", handleKeyDown);
|
|
102
116
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
103
|
-
}, []);
|
|
117
|
+
}, [commandShortcut, setCommandOpen]);
|
|
104
118
|
const toggleSection = React.useCallback((key) => {
|
|
105
119
|
setCollapsedSections((prev) => {
|
|
106
120
|
const next = new Set(prev);
|
|
@@ -113,13 +127,13 @@ export const Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse,
|
|
|
113
127
|
return next;
|
|
114
128
|
});
|
|
115
129
|
}, []);
|
|
116
|
-
return (_jsxs(Primitive.
|
|
130
|
+
return (_jsxs(Primitive.nav, { ref: ref, asChild: asChild, "aria-label": ariaLabel, className: sidebarRecipe({ className }), ...props, children: [_jsxs("div", { className: "crisp-sidebar-ws", children: [_jsx(Avatar, { size: "lg", shape: "rounded", tone: workspace.avatarTone, children: workspace.avatarLabel }), _jsx("span", { className: "crisp-sidebar-ws-name", children: workspace.name }), _jsx(IconChevronDown, {}), _jsx(Button, { intent: "ghost", size: "sm", shape: "icon", className: "crisp-sidebar-ws-collapse", "aria-label": collapseLabel, onClick: onToggleCollapse, children: _jsx(IconCollapsePanel, {}) })] }), _jsxs("div", { className: "crisp-sidebar-searchrow", children: [_jsxs("button", { type: "button", className: "crisp-sidebar-search", onClick: () => setCommandOpen(true), children: [_jsx(IconSearch, {}), _jsx("span", { className: "crisp-sidebar-search-label", children: searchLabel }), _jsx("span", { className: "crisp-sidebar-search-kbd", children: "\u2318K" })] }), _jsx(Button, { intent: "neutral", size: "sm", shape: "icon", "aria-label": inboxLabel, children: _jsx(IconInbox, {}) })] }), _jsx("div", { className: "crisp-sidebar-divider" }), _jsx("div", { className: "crisp-sidebar-section", children: items.map(({ label, icon: ItemIcon, active, trailing }) => (_jsx(NavItem, { href: "#", active: active, icon: _jsx(ItemIcon, {}), trailing: trailing, children: label }, label))) }), groups ? (
|
|
117
131
|
// #325: navigation as data, reusing the exact section markup/
|
|
118
132
|
// recipe above — SectionHeader + .crisp-sidebar-section — instead
|
|
119
133
|
// of the three fixed Attio sections.
|
|
120
134
|
groups.map((group) => {
|
|
121
135
|
const groupKey = group.key ?? group.label;
|
|
122
136
|
return (_jsxs(React.Fragment, { children: [_jsx(SectionHeader, { label: group.label, open: !collapsedSections.has(groupKey), onToggle: () => toggleSection(groupKey) }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has(groupKey) ? "" : undefined, children: group.items.map((item) => (_jsx(NavItem, { href: item.href ?? "#", active: item.active, icon: item.icon ? _jsx(item.icon, {}) : undefined, trailing: item.count, onClick: item.onSelect, children: item.label }, item.href ?? item.label))) })] }, groupKey));
|
|
123
|
-
})) : (_jsxs(_Fragment, { children: [_jsx(SectionHeader, { label: recordsSection.label, open: !collapsedSections.has("records"), onToggle: () => toggleSection("records") }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("records") ? "" : undefined, children: recordsSection.items.map(({ label, tone, glyph: Glyph }) => (_jsx(NavItem, { href: "#", icon: _jsx(Avatar, { size: "sm", shape: "rounded", tone: tone, children: _jsx(Glyph, {}) }), children: label }, label))) }), _jsx(SectionHeader, { label: listsSection.label, open: !collapsedSections.has("lists"), onToggle: () => toggleSection("lists") }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("lists") ? "" : undefined, children: _jsx("button", { type: "button", className: "crisp-sidebar-newlist", children: listsSection.newListLabel }) }), _jsx(SectionHeader, { label: chatsSection.label, open: !collapsedSections.has("chats"), onToggle: () => toggleSection("chats") }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("chats") ? "" : undefined, children: chatsSection.items.map(({ label, icon: ItemIcon }) => (_jsx(NavItem, { href: "#", icon: _jsx(ItemIcon, {}), children: label }, label))) })] })), _jsxs("div", { className: "crisp-sidebar-footer", children: [_jsx(Button, { intent: "neutral", size: "sm", className: "crisp-sidebar-pill", children: footer.gettingStartedLabel }), _jsx(NavItem, { href: "#", icon: _jsx(IconInvite, {}), children: footer.inviteLabel }), _jsx("div", { className: "crisp-sidebar-divider" }), _jsxs("div", { className: "crisp-sidebar-trial", children: [_jsx("span", { children: footer.trialText }), _jsx(Button, { intent: "neutral", size: "sm", children: footer.trialActionLabel })] })] }), _jsx(Command, { open: commandOpen, onOpenChange: setCommandOpen })] }));
|
|
137
|
+
})) : (_jsxs(_Fragment, { children: [_jsx(SectionHeader, { label: recordsSection.label, open: !collapsedSections.has("records"), onToggle: () => toggleSection("records") }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("records") ? "" : undefined, children: recordsSection.items.map(({ label, tone, glyph: Glyph }) => (_jsx(NavItem, { href: "#", icon: _jsx(Avatar, { size: "sm", shape: "rounded", tone: tone, children: _jsx(Glyph, {}) }), children: label }, label))) }), _jsx(SectionHeader, { label: listsSection.label, open: !collapsedSections.has("lists"), onToggle: () => toggleSection("lists") }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("lists") ? "" : undefined, children: _jsx("button", { type: "button", className: "crisp-sidebar-newlist", children: listsSection.newListLabel }) }), _jsx(SectionHeader, { label: chatsSection.label, open: !collapsedSections.has("chats"), onToggle: () => toggleSection("chats") }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("chats") ? "" : undefined, children: chatsSection.items.map(({ label, icon: ItemIcon }) => (_jsx(NavItem, { href: "#", icon: _jsx(ItemIcon, {}), children: label }, label))) })] })), _jsxs("div", { className: "crisp-sidebar-footer", children: [_jsx(Button, { intent: "neutral", size: "sm", className: "crisp-sidebar-pill", children: footer.gettingStartedLabel }), _jsx(NavItem, { href: "#", icon: _jsx(IconInvite, {}), children: footer.inviteLabel }), _jsx("div", { className: "crisp-sidebar-divider" }), _jsxs("div", { className: "crisp-sidebar-trial", children: [_jsx("span", { children: footer.trialText }), _jsx(Button, { intent: "neutral", size: "sm", children: footer.trialActionLabel })] })] }), _jsx(Command, { open: commandOpen, onOpenChange: setCommandOpen, items: commandItems })] }));
|
|
124
138
|
});
|
|
125
139
|
Sidebar.displayName = "Sidebar";
|
package/dist/styles.css
CHANGED
|
@@ -601,47 +601,47 @@
|
|
|
601
601
|
& .crisp-datepicker-nav-button:focus-visible { outline-color: var(--crisp-stroke-focus-ring); }
|
|
602
602
|
& .crisp-datepicker-nav-button:disabled { opacity: .4; cursor: not-allowed; }
|
|
603
603
|
& .crisp-datepicker-weekday-row { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); position: relative; z-index: 2; background: var(--crisp-bg-surface); }
|
|
604
|
-
|
|
604
|
+
/* fgNeutralMuted, not seed's fgNeutralSubtle (#367): 3.42:1 in light
|
|
605
|
+
against bgSurface, fails AA for this 12px text. Not an Attio
|
|
606
|
+
measurement -- accessibility wins over the seed reference.
|
|
607
|
+
neutralMuted clears it, 6.62:1 light / 14.71:1 dark (dark was
|
|
608
|
+
already fine at 9.43:1). */
|
|
609
|
+
& .crisp-datepicker-weekday {
|
|
610
|
+
display: flex; align-items: center; justify-content: center;
|
|
611
|
+
height: var(--crisp-size-calendarCell); color: var(--crisp-fg-neutral-muted);
|
|
612
|
+
font-size: var(--crisp-text-sm); line-height: 16px; font-weight: var(--crisp-weight-medium);
|
|
613
|
+
}
|
|
614
|
+
|
|
605
615
|
& .crisp-datepicker-months { display: grid; grid-template-columns: minmax(0, 1fr); align-items: start; min-width: 0; }
|
|
606
616
|
& .crisp-datepicker-months[data-wheel-open] { visibility: hidden; }
|
|
607
617
|
& .crisp-datepicker-month { min-width: 0; }
|
|
608
|
-
& .crisp-datepicker-month-label {
|
|
618
|
+
& .crisp-datepicker-month-label {
|
|
619
|
+
display: flex; align-items: center; height: 24px;
|
|
620
|
+
color: var(--crisp-fg-primary); font-size: var(--crisp-text-lg); line-height: 20px;
|
|
621
|
+
font-weight: var(--crisp-weight-medium);
|
|
622
|
+
}
|
|
623
|
+
|
|
609
624
|
& .crisp-datepicker-grid { display: grid; min-width: 0; }
|
|
610
625
|
& .crisp-datepicker-week-row { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
|
|
611
|
-
& .crisp-datepicker-cell { position: relative; min-width: 0; min-height: var(--crisp-size-calendarCell); isolation: isolate; }
|
|
612
|
-
& .crisp-datepicker-empty-cell { min-height: var(--crisp-size-calendarCell); }
|
|
613
|
-
/* The bridge joining days inside a completed range. The endpoints do NOT carry data-in-range -- only the interior days do -- so these are three independent rules, not a base plus two modifiers. A modifier that sets only inset-inline-start never generates a pseudo element on an endpoint, which leaves a half-cell of white between the endpoint's pill and the run of grey beside it. Each rule declares its
|
|
614
|
-
own content. */
|
|
615
|
-
& .crisp-datepicker-cell[data-range-complete][data-in-range]::before {
|
|
616
|
-
content: ""; position: absolute; z-index: -1; inset-inline: 0;
|
|
617
|
-
top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak);
|
|
618
|
-
}
|
|
619
|
-
& .crisp-datepicker-cell[data-range-complete][data-range-start]:not([data-range-end])::before {
|
|
620
|
-
content: ""; position: absolute; z-index: -1;
|
|
621
|
-
inset-inline-start: 50%; inset-inline-end: 0;
|
|
622
|
-
top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak);
|
|
623
|
-
}
|
|
624
|
-
& .crisp-datepicker-cell[data-range-complete][data-range-end]:not([data-range-start])::before {
|
|
625
|
-
content: ""; position: absolute; z-index: -1;
|
|
626
|
-
inset-inline-start: 0; inset-inline-end: 50%;
|
|
627
|
-
top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak);
|
|
628
|
-
}
|
|
629
626
|
|
|
630
|
-
& .crisp-datepicker-
|
|
631
|
-
position: relative;
|
|
632
|
-
min-height: var(--crisp-size-calendarCell); min-width: 0;
|
|
633
|
-
align-items: flex-start; justify-content: center;
|
|
634
|
-
border: 0; padding: 0; background: transparent; cursor: pointer;
|
|
635
|
-
color: var(--crisp-fg-primary); font: inherit; outline: none;
|
|
627
|
+
& .crisp-datepicker-cell {
|
|
628
|
+
position: relative; min-width: 0; min-height: var(--crisp-size-calendarCell);
|
|
636
629
|
isolation: isolate;
|
|
637
630
|
}
|
|
631
|
+
& .crisp-datepicker-empty-cell { min-height: var(--crisp-size-calendarCell); }
|
|
632
|
+
|
|
633
|
+
/* The bridge joining days inside a completed range.
|
|
634
|
+
The endpoints do NOT carry data-in-range -- only the interior days do --
|
|
635
|
+
so these are three independent rules, not a base plus two modifiers. A
|
|
636
|
+
modifier that sets only inset-inline-start never generates a pseudo
|
|
637
|
+
element on an endpoint, which leaves a half-cell of white between the
|
|
638
|
+
endpoint's pill and the run of grey beside it. Each rule declares its own content. */
|
|
639
|
+
& .crisp-datepicker-cell[data-range-complete][data-in-range]::before { content: ""; position: absolute; z-index: -1; inset-inline: 0; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak); }
|
|
640
|
+
& .crisp-datepicker-cell[data-range-complete][data-range-start]:not([data-range-end])::before { content: ""; position: absolute; z-index: -1; inset-inline-start: 50%; inset-inline-end: 0; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak); }
|
|
641
|
+
& .crisp-datepicker-cell[data-range-complete][data-range-end]:not([data-range-start])::before { content: ""; position: absolute; z-index: -1; inset-inline-start: 0; inset-inline-end: 50%; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak); }
|
|
642
|
+
& .crisp-datepicker-date { position: relative; display: flex; width: 100%; height: 100%; min-height: var(--crisp-size-calendarCell); min-width: 0; align-items: flex-start; justify-content: center; border: 0; padding: 0; background: transparent; cursor: pointer; color: var(--crisp-fg-primary); font: inherit; outline: none; isolation: isolate; }
|
|
638
643
|
/* the round day highlight, behind the numeral */
|
|
639
|
-
& .crisp-datepicker-date::before {
|
|
640
|
-
content: ""; position: absolute; z-index: -1; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); left: 50%;
|
|
641
|
-
width: var(--crisp-size-calendarPill); height: var(--crisp-size-calendarPill);
|
|
642
|
-
border-radius: var(--crisp-radius-md);
|
|
643
|
-
background: transparent; transform: translateX(-50%);
|
|
644
|
-
}
|
|
644
|
+
& .crisp-datepicker-date::before { content: ""; position: absolute; z-index: -1; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); left: 50%; width: var(--crisp-size-calendarPill); height: var(--crisp-size-calendarPill); border-radius: var(--crisp-radius-md); background: transparent; transform: translateX(-50%); }
|
|
645
645
|
& .crisp-datepicker-date[data-today]::before { background: var(--crisp-bg-neutral-weak); }
|
|
646
646
|
& .crisp-datepicker-date[data-selected]::before { background: var(--crisp-bg-brand-solid); }
|
|
647
647
|
& .crisp-datepicker-date[data-selected] { color: var(--crisp-fg-on-brand); }
|
|
@@ -649,34 +649,35 @@
|
|
|
649
649
|
& .crisp-datepicker-date[data-in-range]::before { background: transparent; }
|
|
650
650
|
& .crisp-datepicker-date[data-unavailable] { color: var(--crisp-fg-disabled); }
|
|
651
651
|
& .crisp-datepicker-date:disabled { color: var(--crisp-fg-disabled); cursor: not-allowed; }
|
|
652
|
-
& .crisp-datepicker-date:focus-visible::after {
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
652
|
+
& .crisp-datepicker-date:focus-visible::after { content: ""; position: absolute; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); left: 50%; width: var(--crisp-size-calendarPill); height: var(--crisp-size-calendarPill); transform: translateX(-50%); border-radius: var(--crisp-radius-md); outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: -2px; }
|
|
653
|
+
& .crisp-datepicker-date-content { position: relative; z-index: 1; display: flex; width: 100%; min-height: var(--crisp-size-calendarCell); min-width: 0; box-sizing: border-box; flex-direction: column; align-items: center; gap: 3px; padding-top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); color: inherit; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); font-variant-numeric: tabular-nums; }
|
|
654
|
+
/* The numeral is its own 42px flex box so it centres on the 42px pill. Without this the content column top-aligns the glyph and the pill reads as offset — the cell is 48px, the pill 42px inset 3px, and only a 42px-tall numeral box shares the pill's centre line. */
|
|
655
|
+
& .crisp-datepicker-date-content > [data-date-picker-day] {
|
|
656
|
+
display: flex; min-height: var(--crisp-size-calendarPill); flex-shrink: 0;
|
|
657
|
+
align-items: center; justify-content: center;
|
|
657
658
|
}
|
|
658
659
|
|
|
659
|
-
& .crisp-datepicker-date-content {
|
|
660
|
-
position: relative; z-index: 1; display: flex; width: 100%;
|
|
661
|
-
min-height: var(--crisp-size-calendarCell); min-width: 0; box-sizing: border-box;
|
|
662
|
-
flex-direction: column; align-items: center; gap: 3px;
|
|
663
|
-
padding-top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); color: inherit;
|
|
664
|
-
font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium);
|
|
665
|
-
font-variant-numeric: tabular-nums;
|
|
666
|
-
}
|
|
667
|
-
/* The numeral is its own 42px flex box so it centres on the 42px pill.
|
|
668
|
-
Without this the content column top-aligns the glyph and the pill reads
|
|
669
|
-
as offset — the cell is 48px, the pill 42px inset 3px, and only a
|
|
670
|
-
42px-tall numeral box shares the pill's centre line. */
|
|
671
|
-
& .crisp-datepicker-date-content > [data-date-picker-day] { display: flex; min-height: var(--crisp-size-calendarPill); flex-shrink: 0; align-items: center; justify-content: center; }
|
|
672
660
|
/* the year/month wheel, laid over the grid it replaces */
|
|
673
|
-
& .crisp-datepicker-wheel-container {
|
|
661
|
+
& .crisp-datepicker-wheel-container {
|
|
662
|
+
position: absolute; z-index: 1; inset-inline: 0;
|
|
663
|
+
top: var(--crisp-size-menuRow); bottom: 0;
|
|
664
|
+
display: flex; align-items: center; overflow: hidden;
|
|
665
|
+
background: var(--crisp-bg-surface);
|
|
666
|
+
}
|
|
674
667
|
& .crisp-datepicker-wheel { width: 100%; }
|
|
675
|
-
|
|
668
|
+
|
|
669
|
+
& .crisp-datepicker-continuous-scroll {
|
|
670
|
+
height: 100%; min-height: 0; overflow-y: auto;
|
|
671
|
+
overscroll-behavior: contain; scrollbar-width: none;
|
|
672
|
+
}
|
|
676
673
|
& .crisp-datepicker-continuous-scroll::-webkit-scrollbar { display: none; }
|
|
677
674
|
& .crisp-datepicker-continuous-content { display: flex; flex-direction: column; }
|
|
675
|
+
|
|
678
676
|
/* announced, never drawn */
|
|
679
|
-
& .crisp-datepicker-live-region {
|
|
677
|
+
& .crisp-datepicker-live-region {
|
|
678
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
679
|
+
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
|
|
680
|
+
}
|
|
680
681
|
}
|
|
681
682
|
.crisp-datepicker--visibleRange_month {
|
|
682
683
|
|
package/dist/styles.layered.css
CHANGED
|
@@ -602,47 +602,47 @@
|
|
|
602
602
|
& .crisp-datepicker-nav-button:focus-visible { outline-color: var(--crisp-stroke-focus-ring); }
|
|
603
603
|
& .crisp-datepicker-nav-button:disabled { opacity: .4; cursor: not-allowed; }
|
|
604
604
|
& .crisp-datepicker-weekday-row { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); position: relative; z-index: 2; background: var(--crisp-bg-surface); }
|
|
605
|
-
|
|
605
|
+
/* fgNeutralMuted, not seed's fgNeutralSubtle (#367): 3.42:1 in light
|
|
606
|
+
against bgSurface, fails AA for this 12px text. Not an Attio
|
|
607
|
+
measurement -- accessibility wins over the seed reference.
|
|
608
|
+
neutralMuted clears it, 6.62:1 light / 14.71:1 dark (dark was
|
|
609
|
+
already fine at 9.43:1). */
|
|
610
|
+
& .crisp-datepicker-weekday {
|
|
611
|
+
display: flex; align-items: center; justify-content: center;
|
|
612
|
+
height: var(--crisp-size-calendarCell); color: var(--crisp-fg-neutral-muted);
|
|
613
|
+
font-size: var(--crisp-text-sm); line-height: 16px; font-weight: var(--crisp-weight-medium);
|
|
614
|
+
}
|
|
615
|
+
|
|
606
616
|
& .crisp-datepicker-months { display: grid; grid-template-columns: minmax(0, 1fr); align-items: start; min-width: 0; }
|
|
607
617
|
& .crisp-datepicker-months[data-wheel-open] { visibility: hidden; }
|
|
608
618
|
& .crisp-datepicker-month { min-width: 0; }
|
|
609
|
-
& .crisp-datepicker-month-label {
|
|
619
|
+
& .crisp-datepicker-month-label {
|
|
620
|
+
display: flex; align-items: center; height: 24px;
|
|
621
|
+
color: var(--crisp-fg-primary); font-size: var(--crisp-text-lg); line-height: 20px;
|
|
622
|
+
font-weight: var(--crisp-weight-medium);
|
|
623
|
+
}
|
|
624
|
+
|
|
610
625
|
& .crisp-datepicker-grid { display: grid; min-width: 0; }
|
|
611
626
|
& .crisp-datepicker-week-row { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
|
|
612
|
-
& .crisp-datepicker-cell { position: relative; min-width: 0; min-height: var(--crisp-size-calendarCell); isolation: isolate; }
|
|
613
|
-
& .crisp-datepicker-empty-cell { min-height: var(--crisp-size-calendarCell); }
|
|
614
|
-
/* The bridge joining days inside a completed range. The endpoints do NOT carry data-in-range -- only the interior days do -- so these are three independent rules, not a base plus two modifiers. A modifier that sets only inset-inline-start never generates a pseudo element on an endpoint, which leaves a half-cell of white between the endpoint's pill and the run of grey beside it. Each rule declares its
|
|
615
|
-
own content. */
|
|
616
|
-
& .crisp-datepicker-cell[data-range-complete][data-in-range]::before {
|
|
617
|
-
content: ""; position: absolute; z-index: -1; inset-inline: 0;
|
|
618
|
-
top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak);
|
|
619
|
-
}
|
|
620
|
-
& .crisp-datepicker-cell[data-range-complete][data-range-start]:not([data-range-end])::before {
|
|
621
|
-
content: ""; position: absolute; z-index: -1;
|
|
622
|
-
inset-inline-start: 50%; inset-inline-end: 0;
|
|
623
|
-
top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak);
|
|
624
|
-
}
|
|
625
|
-
& .crisp-datepicker-cell[data-range-complete][data-range-end]:not([data-range-start])::before {
|
|
626
|
-
content: ""; position: absolute; z-index: -1;
|
|
627
|
-
inset-inline-start: 0; inset-inline-end: 50%;
|
|
628
|
-
top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak);
|
|
629
|
-
}
|
|
630
627
|
|
|
631
|
-
& .crisp-datepicker-
|
|
632
|
-
position: relative;
|
|
633
|
-
min-height: var(--crisp-size-calendarCell); min-width: 0;
|
|
634
|
-
align-items: flex-start; justify-content: center;
|
|
635
|
-
border: 0; padding: 0; background: transparent; cursor: pointer;
|
|
636
|
-
color: var(--crisp-fg-primary); font: inherit; outline: none;
|
|
628
|
+
& .crisp-datepicker-cell {
|
|
629
|
+
position: relative; min-width: 0; min-height: var(--crisp-size-calendarCell);
|
|
637
630
|
isolation: isolate;
|
|
638
631
|
}
|
|
632
|
+
& .crisp-datepicker-empty-cell { min-height: var(--crisp-size-calendarCell); }
|
|
633
|
+
|
|
634
|
+
/* The bridge joining days inside a completed range.
|
|
635
|
+
The endpoints do NOT carry data-in-range -- only the interior days do --
|
|
636
|
+
so these are three independent rules, not a base plus two modifiers. A
|
|
637
|
+
modifier that sets only inset-inline-start never generates a pseudo
|
|
638
|
+
element on an endpoint, which leaves a half-cell of white between the
|
|
639
|
+
endpoint's pill and the run of grey beside it. Each rule declares its own content. */
|
|
640
|
+
& .crisp-datepicker-cell[data-range-complete][data-in-range]::before { content: ""; position: absolute; z-index: -1; inset-inline: 0; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak); }
|
|
641
|
+
& .crisp-datepicker-cell[data-range-complete][data-range-start]:not([data-range-end])::before { content: ""; position: absolute; z-index: -1; inset-inline-start: 50%; inset-inline-end: 0; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak); }
|
|
642
|
+
& .crisp-datepicker-cell[data-range-complete][data-range-end]:not([data-range-start])::before { content: ""; position: absolute; z-index: -1; inset-inline-start: 0; inset-inline-end: 50%; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak); }
|
|
643
|
+
& .crisp-datepicker-date { position: relative; display: flex; width: 100%; height: 100%; min-height: var(--crisp-size-calendarCell); min-width: 0; align-items: flex-start; justify-content: center; border: 0; padding: 0; background: transparent; cursor: pointer; color: var(--crisp-fg-primary); font: inherit; outline: none; isolation: isolate; }
|
|
639
644
|
/* the round day highlight, behind the numeral */
|
|
640
|
-
& .crisp-datepicker-date::before {
|
|
641
|
-
content: ""; position: absolute; z-index: -1; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); left: 50%;
|
|
642
|
-
width: var(--crisp-size-calendarPill); height: var(--crisp-size-calendarPill);
|
|
643
|
-
border-radius: var(--crisp-radius-md);
|
|
644
|
-
background: transparent; transform: translateX(-50%);
|
|
645
|
-
}
|
|
645
|
+
& .crisp-datepicker-date::before { content: ""; position: absolute; z-index: -1; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); left: 50%; width: var(--crisp-size-calendarPill); height: var(--crisp-size-calendarPill); border-radius: var(--crisp-radius-md); background: transparent; transform: translateX(-50%); }
|
|
646
646
|
& .crisp-datepicker-date[data-today]::before { background: var(--crisp-bg-neutral-weak); }
|
|
647
647
|
& .crisp-datepicker-date[data-selected]::before { background: var(--crisp-bg-brand-solid); }
|
|
648
648
|
& .crisp-datepicker-date[data-selected] { color: var(--crisp-fg-on-brand); }
|
|
@@ -650,34 +650,35 @@
|
|
|
650
650
|
& .crisp-datepicker-date[data-in-range]::before { background: transparent; }
|
|
651
651
|
& .crisp-datepicker-date[data-unavailable] { color: var(--crisp-fg-disabled); }
|
|
652
652
|
& .crisp-datepicker-date:disabled { color: var(--crisp-fg-disabled); cursor: not-allowed; }
|
|
653
|
-
& .crisp-datepicker-date:focus-visible::after {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
653
|
+
& .crisp-datepicker-date:focus-visible::after { content: ""; position: absolute; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); left: 50%; width: var(--crisp-size-calendarPill); height: var(--crisp-size-calendarPill); transform: translateX(-50%); border-radius: var(--crisp-radius-md); outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: -2px; }
|
|
654
|
+
& .crisp-datepicker-date-content { position: relative; z-index: 1; display: flex; width: 100%; min-height: var(--crisp-size-calendarCell); min-width: 0; box-sizing: border-box; flex-direction: column; align-items: center; gap: 3px; padding-top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); color: inherit; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); font-variant-numeric: tabular-nums; }
|
|
655
|
+
/* The numeral is its own 42px flex box so it centres on the 42px pill. Without this the content column top-aligns the glyph and the pill reads as offset — the cell is 48px, the pill 42px inset 3px, and only a 42px-tall numeral box shares the pill's centre line. */
|
|
656
|
+
& .crisp-datepicker-date-content > [data-date-picker-day] {
|
|
657
|
+
display: flex; min-height: var(--crisp-size-calendarPill); flex-shrink: 0;
|
|
658
|
+
align-items: center; justify-content: center;
|
|
658
659
|
}
|
|
659
660
|
|
|
660
|
-
& .crisp-datepicker-date-content {
|
|
661
|
-
position: relative; z-index: 1; display: flex; width: 100%;
|
|
662
|
-
min-height: var(--crisp-size-calendarCell); min-width: 0; box-sizing: border-box;
|
|
663
|
-
flex-direction: column; align-items: center; gap: 3px;
|
|
664
|
-
padding-top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); color: inherit;
|
|
665
|
-
font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium);
|
|
666
|
-
font-variant-numeric: tabular-nums;
|
|
667
|
-
}
|
|
668
|
-
/* The numeral is its own 42px flex box so it centres on the 42px pill.
|
|
669
|
-
Without this the content column top-aligns the glyph and the pill reads
|
|
670
|
-
as offset — the cell is 48px, the pill 42px inset 3px, and only a
|
|
671
|
-
42px-tall numeral box shares the pill's centre line. */
|
|
672
|
-
& .crisp-datepicker-date-content > [data-date-picker-day] { display: flex; min-height: var(--crisp-size-calendarPill); flex-shrink: 0; align-items: center; justify-content: center; }
|
|
673
661
|
/* the year/month wheel, laid over the grid it replaces */
|
|
674
|
-
& .crisp-datepicker-wheel-container {
|
|
662
|
+
& .crisp-datepicker-wheel-container {
|
|
663
|
+
position: absolute; z-index: 1; inset-inline: 0;
|
|
664
|
+
top: var(--crisp-size-menuRow); bottom: 0;
|
|
665
|
+
display: flex; align-items: center; overflow: hidden;
|
|
666
|
+
background: var(--crisp-bg-surface);
|
|
667
|
+
}
|
|
675
668
|
& .crisp-datepicker-wheel { width: 100%; }
|
|
676
|
-
|
|
669
|
+
|
|
670
|
+
& .crisp-datepicker-continuous-scroll {
|
|
671
|
+
height: 100%; min-height: 0; overflow-y: auto;
|
|
672
|
+
overscroll-behavior: contain; scrollbar-width: none;
|
|
673
|
+
}
|
|
677
674
|
& .crisp-datepicker-continuous-scroll::-webkit-scrollbar { display: none; }
|
|
678
675
|
& .crisp-datepicker-continuous-content { display: flex; flex-direction: column; }
|
|
676
|
+
|
|
679
677
|
/* announced, never drawn */
|
|
680
|
-
& .crisp-datepicker-live-region {
|
|
678
|
+
& .crisp-datepicker-live-region {
|
|
679
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
680
|
+
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
|
|
681
|
+
}
|
|
681
682
|
}
|
|
682
683
|
.crisp-datepicker--visibleRange_month {
|
|
683
684
|
|
|
@@ -11,3 +11,21 @@ export declare function registerFloatingLayer(el: HTMLElement): () => void;
|
|
|
11
11
|
* right query for "was this click inside some floating child I opened".
|
|
12
12
|
*/
|
|
13
13
|
export declare function isInsideLaterFloatingLayer(target: Node, self: HTMLElement | null): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* True when at least one `Select`/`MultiSelect`/`Menu`/`Popover`/
|
|
16
|
+
* `ContextMenu` is currently open, anywhere (#361).
|
|
17
|
+
*
|
|
18
|
+
* A dismissable container that closes on Escape via its OWN raw keydown
|
|
19
|
+
* listener or a library's dismissable-layer machine (`RecordPanel`,
|
|
20
|
+
* `Dialog`, `Drawer`) has no visibility into these five: none of them are
|
|
21
|
+
* built on the same machine, so a Menu opened from inside a `Dialog`
|
|
22
|
+
* registers here, but the Dialog's own Escape handling doesn't know to
|
|
23
|
+
* check for it. Both listeners fire on the same Escape keydown, so both
|
|
24
|
+
* close — the outer container closing was never the point of that
|
|
25
|
+
* keypress, which was meant for the nested layer alone. Call this before
|
|
26
|
+
* closing on Escape (or in a dismissable-layer's `onEscapeKeyDown`, calling
|
|
27
|
+
* `event.preventDefault()` when it's true) to defer instead: the nested
|
|
28
|
+
* layer's own listener still runs and closes it, and the outer container
|
|
29
|
+
* gets its own, uncontested Escape on the next press.
|
|
30
|
+
*/
|
|
31
|
+
export declare function hasOpenFloatingLayer(): boolean;
|
|
@@ -4,7 +4,7 @@ export { defineRecipe } from "./define";
|
|
|
4
4
|
export { withDescribedBy } from "./dialog-description";
|
|
5
5
|
export { isRtlElement } from "./direction";
|
|
6
6
|
export { FloatingPortal } from "./floating-portal";
|
|
7
|
-
export { isInsideLaterFloatingLayer, registerFloatingLayer, } from "./floating-stack";
|
|
7
|
+
export { hasOpenFloatingLayer, isInsideLaterFloatingLayer, registerFloatingLayer, } from "./floating-stack";
|
|
8
8
|
export { formValueStyle } from "./form-value";
|
|
9
9
|
export { type FloatingAlign, type PlaceFloatingOptions, placeFloating, } from "./place-floating";
|
|
10
10
|
export { Primitive } from "./primitive";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
declare const NODES: readonly ["button", "div", "span", "a", "input", "p"];
|
|
2
|
+
declare const NODES: readonly ["button", "div", "span", "a", "input", "p", "nav"];
|
|
3
3
|
type NodeName = (typeof NODES)[number];
|
|
4
4
|
type PrimitiveProps<E extends NodeName> = React.ComponentPropsWithoutRef<E> & {
|
|
5
5
|
asChild?: boolean;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import type { AvatarProps } from "../../components/avatar";
|
|
3
|
+
import { type CommandItem } from "../command";
|
|
3
4
|
type IconProps = {
|
|
4
5
|
className?: string;
|
|
5
6
|
};
|
|
@@ -115,6 +116,27 @@ export type SidebarProps = React.ComponentPropsWithoutRef<"nav"> & {
|
|
|
115
116
|
groups?: SidebarNavGroup[];
|
|
116
117
|
/** Footer copy (onboarding pill, invite row, trial banner). */
|
|
117
118
|
footer?: SidebarFooter;
|
|
119
|
+
/**
|
|
120
|
+
* Command-palette items, forwarded to the internal `Command`'s own
|
|
121
|
+
* `items` prop. Omit for Command's built-in Attio set (#364: GRC's
|
|
122
|
+
* commands aren't Attio's, and there was no way to reach them).
|
|
123
|
+
*/
|
|
124
|
+
commandItems?: CommandItem[];
|
|
125
|
+
/** Controlled open state for the command palette. Omit to let Sidebar
|
|
126
|
+
* manage it (see `defaultCommandOpen`). */
|
|
127
|
+
commandOpen?: boolean;
|
|
128
|
+
/** Initially open, when uncontrolled. */
|
|
129
|
+
defaultCommandOpen?: boolean;
|
|
130
|
+
onCommandOpenChange?: (open: boolean) => void;
|
|
131
|
+
/**
|
|
132
|
+
* Whether Sidebar attaches its own ⌘K/Ctrl+K document listener to open
|
|
133
|
+
* the command palette (default `true`). Set `false` when the app already
|
|
134
|
+
* owns a single global ⌘K listener — e.g. to route the same shortcut to
|
|
135
|
+
* more than Sidebar's palette elsewhere in the page — and drives
|
|
136
|
+
* `commandOpen` itself; two listeners on the same key both firing opened
|
|
137
|
+
* two overlapping instances (#364).
|
|
138
|
+
*/
|
|
139
|
+
commandShortcut?: boolean;
|
|
118
140
|
};
|
|
119
141
|
/** The measured workspace switcher — the default `workspace` prop. */
|
|
120
142
|
export declare const ATTIO_SIDEBAR_WORKSPACE: SidebarWorkspace;
|
|
@@ -160,5 +182,26 @@ export declare const Sidebar: React.ForwardRefExoticComponent<Omit<React.Detaile
|
|
|
160
182
|
groups?: SidebarNavGroup[];
|
|
161
183
|
/** Footer copy (onboarding pill, invite row, trial banner). */
|
|
162
184
|
footer?: SidebarFooter;
|
|
163
|
-
|
|
185
|
+
/**
|
|
186
|
+
* Command-palette items, forwarded to the internal `Command`'s own
|
|
187
|
+
* `items` prop. Omit for Command's built-in Attio set (#364: GRC's
|
|
188
|
+
* commands aren't Attio's, and there was no way to reach them).
|
|
189
|
+
*/
|
|
190
|
+
commandItems?: CommandItem[];
|
|
191
|
+
/** Controlled open state for the command palette. Omit to let Sidebar
|
|
192
|
+
* manage it (see `defaultCommandOpen`). */
|
|
193
|
+
commandOpen?: boolean;
|
|
194
|
+
/** Initially open, when uncontrolled. */
|
|
195
|
+
defaultCommandOpen?: boolean;
|
|
196
|
+
onCommandOpenChange?: (open: boolean) => void;
|
|
197
|
+
/**
|
|
198
|
+
* Whether Sidebar attaches its own ⌘K/Ctrl+K document listener to open
|
|
199
|
+
* the command palette (default `true`). Set `false` when the app already
|
|
200
|
+
* owns a single global ⌘K listener — e.g. to route the same shortcut to
|
|
201
|
+
* more than Sidebar's palette elsewhere in the page — and drives
|
|
202
|
+
* `commandOpen` itself; two listeners on the same key both firing opened
|
|
203
|
+
* two overlapping instances (#364).
|
|
204
|
+
*/
|
|
205
|
+
commandShortcut?: boolean;
|
|
206
|
+
} & React.RefAttributes<HTMLElement>>;
|
|
164
207
|
export {};
|