@crisp-ui-kit/crisp 0.40.0 → 0.42.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/contextmenu/contextmenu.js +3 -2
- package/dist/cjs/components/datepicker/datepicker.recipe.js +6 -1
- package/dist/cjs/core/primitive.js +1 -1
- 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/contextmenu/contextmenu.js +4 -3
- package/dist/esm/components/datepicker/datepicker.recipe.js +6 -1
- package/dist/esm/core/primitive.js +1 -1
- 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/components/contextmenu/contextmenu.d.ts +11 -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
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.ContextMenu = ContextMenu;
|
|
37
37
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
38
|
+
const react_slot_1 = require("@radix-ui/react-slot");
|
|
38
39
|
const React = __importStar(require("react"));
|
|
39
40
|
const core_1 = require("../../core");
|
|
40
41
|
const menu_1 = require("../menu");
|
|
@@ -44,7 +45,7 @@ const contextmenu_recipe_1 = require("./contextmenu.recipe");
|
|
|
44
45
|
* Wrap the target; right-clicking it opens the menu (flipping at the viewport
|
|
45
46
|
* edges), closing on select, outside pointer-down, and Escape.
|
|
46
47
|
*/
|
|
47
|
-
function ContextMenu({ children, items, menuLabel = "Context menu", className, }) {
|
|
48
|
+
function ContextMenu({ children, items, menuLabel = "Context menu", className, asChild = false, }) {
|
|
48
49
|
const rootRef = React.useRef(null);
|
|
49
50
|
const menuRef = React.useRef(null);
|
|
50
51
|
const returnFocusTo = React.useRef(null);
|
|
@@ -109,7 +110,7 @@ function ContextMenu({ children, items, menuLabel = "Context menu", className, }
|
|
|
109
110
|
document.removeEventListener("keydown", onKey);
|
|
110
111
|
};
|
|
111
112
|
}, [pos, close]);
|
|
112
|
-
return ((0, jsx_runtime_1.jsxs)("div", { ref: rootRef, className: (0, contextmenu_recipe_1.contextmenuRecipe)({ className }), children:
|
|
113
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [asChild ? ((0, jsx_runtime_1.jsx)(react_slot_1.Slot, { ref: rootRef, className: className, onContextMenu: open, children: children })) : ((0, jsx_runtime_1.jsx)("div", { ref: rootRef, className: (0, contextmenu_recipe_1.contextmenuRecipe)({ className }), children: (0, jsx_runtime_1.jsx)("div", { onContextMenu: open, children: children }) })), pos && ((0, jsx_runtime_1.jsx)(core_1.FloatingPortal, { sourceRef: rootRef, children: (0, jsx_runtime_1.jsx)("div", { ref: menuRef, className: "crisp-contextmenu-content", role: "menu", "aria-label": menuLabel, style: { left: pos.x, top: pos.y }, onKeyDown: onMenuKeyDown, children: items.map((item, i) => item.separator ? ((0, jsx_runtime_1.jsx)("hr", { className: "crisp-contextmenu-sep" }, i)) : ((0, jsx_runtime_1.jsxs)("button", { type: "button", role: "menuitem", disabled: item.disabled, className: item.danger
|
|
113
114
|
? "crisp-contextmenu-item crisp-contextmenu-item--danger"
|
|
114
115
|
: "crisp-contextmenu-item", tabIndex: -1, onClick: () => {
|
|
115
116
|
item.onSelect?.();
|
|
@@ -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
|
|
|
@@ -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;
|
|
@@ -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
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
2
3
|
import * as React from "react";
|
|
3
4
|
import { FloatingPortal, isInsideLaterFloatingLayer, registerFloatingLayer, } from "../../core/index.js";
|
|
4
5
|
import { useMenuKeyboard } from "../menu/index.js";
|
|
@@ -8,7 +9,7 @@ import { contextmenuRecipe } from "./contextmenu.recipe.js";
|
|
|
8
9
|
* Wrap the target; right-clicking it opens the menu (flipping at the viewport
|
|
9
10
|
* edges), closing on select, outside pointer-down, and Escape.
|
|
10
11
|
*/
|
|
11
|
-
export function ContextMenu({ children, items, menuLabel = "Context menu", className, }) {
|
|
12
|
+
export function ContextMenu({ children, items, menuLabel = "Context menu", className, asChild = false, }) {
|
|
12
13
|
const rootRef = React.useRef(null);
|
|
13
14
|
const menuRef = React.useRef(null);
|
|
14
15
|
const returnFocusTo = React.useRef(null);
|
|
@@ -73,7 +74,7 @@ export function ContextMenu({ children, items, menuLabel = "Context menu", class
|
|
|
73
74
|
document.removeEventListener("keydown", onKey);
|
|
74
75
|
};
|
|
75
76
|
}, [pos, close]);
|
|
76
|
-
return (_jsxs("div", { ref: rootRef, className: contextmenuRecipe({ className }), children:
|
|
77
|
+
return (_jsxs(_Fragment, { children: [asChild ? (_jsx(Slot, { ref: rootRef, className: className, onContextMenu: open, children: children })) : (_jsx("div", { ref: rootRef, className: contextmenuRecipe({ className }), children: _jsx("div", { onContextMenu: open, children: children }) })), pos && (_jsx(FloatingPortal, { sourceRef: rootRef, children: _jsx("div", { ref: menuRef, className: "crisp-contextmenu-content", role: "menu", "aria-label": menuLabel, style: { left: pos.x, top: pos.y }, onKeyDown: onMenuKeyDown, children: items.map((item, i) => item.separator ? (_jsx("hr", { className: "crisp-contextmenu-sep" }, i)) : (_jsxs("button", { type: "button", role: "menuitem", disabled: item.disabled, className: item.danger
|
|
77
78
|
? "crisp-contextmenu-item crisp-contextmenu-item--danger"
|
|
78
79
|
: "crisp-contextmenu-item", tabIndex: -1, onClick: () => {
|
|
79
80
|
item.onSelect?.();
|
|
@@ -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
|
|
|
@@ -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;
|
|
@@ -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
|
|
|
@@ -17,13 +17,23 @@ export type ContextMenuProps = {
|
|
|
17
17
|
/** Accessible name for the menu surface. */
|
|
18
18
|
menuLabel?: string;
|
|
19
19
|
className?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Render `onContextMenu` and the trigger ref directly onto `children`
|
|
22
|
+
* instead of wrapping it in a `<div>` (#340) — needed when `children` is
|
|
23
|
+
* an element that cannot have a wrapping element inserted around it, such
|
|
24
|
+
* as a `<tr>` inside a `<tbody>` (the browser's HTML parser hoists the row
|
|
25
|
+
* out of the wrapping `<div>`, breaking both the layout and the
|
|
26
|
+
* right-click target). `children` must then be a single valid React
|
|
27
|
+
* element, same as any other `asChild` in crisp (`core/primitive.tsx`).
|
|
28
|
+
*/
|
|
29
|
+
asChild?: boolean;
|
|
20
30
|
};
|
|
21
31
|
/**
|
|
22
32
|
* A right-click context menu — the popover surface positioned at the cursor.
|
|
23
33
|
* Wrap the target; right-clicking it opens the menu (flipping at the viewport
|
|
24
34
|
* edges), closing on select, outside pointer-down, and Escape.
|
|
25
35
|
*/
|
|
26
|
-
export declare function ContextMenu({ children, items, menuLabel, className, }: ContextMenuProps): React.JSX.Element;
|
|
36
|
+
export declare function ContextMenu({ children, items, menuLabel, className, asChild, }: ContextMenuProps): React.JSX.Element;
|
|
27
37
|
export declare namespace ContextMenu {
|
|
28
38
|
var displayName: string;
|
|
29
39
|
}
|
|
@@ -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 {};
|