@robr0/design-system 0.1.0 → 0.3.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.
Files changed (91) hide show
  1. package/README.md +13 -9
  2. package/components/Accordion/Accordion.js +0 -1
  3. package/components/Avatar/Avatar.js +1 -0
  4. package/components/Breadcrumb/Breadcrumb.css +9 -1
  5. package/components/Breadcrumb/Breadcrumb.d.ts +7 -1
  6. package/components/Breadcrumb/Breadcrumb.js +3 -2
  7. package/components/Button/Button.css +18 -0
  8. package/components/Button/Button.d.ts +38 -16
  9. package/components/Button/Button.js +76 -58
  10. package/components/Card/Card.css +6 -0
  11. package/components/Card/Card.d.ts +17 -5
  12. package/components/Card/Card.js +101 -80
  13. package/components/Checkbox/Checkbox.d.ts +24 -12
  14. package/components/Checkbox/Checkbox.js +99 -85
  15. package/components/CircularButton/CircularButton.css +11 -0
  16. package/components/CircularButton/CircularButton.d.ts +7 -1
  17. package/components/CircularButton/CircularButton.js +15 -4
  18. package/components/CodeBlock/CodeBlock.js +10 -1
  19. package/components/ColorPicker/ColorPicker.css +278 -0
  20. package/components/ColorPicker/ColorPicker.d.ts +50 -0
  21. package/components/ColorPicker/ColorPicker.js +338 -0
  22. package/components/Combobox/Combobox.css +0 -36
  23. package/components/Combobox/Combobox.d.ts +19 -7
  24. package/components/Combobox/Combobox.js +106 -89
  25. package/components/CommandPalette/CommandPalette.css +1 -15
  26. package/components/CommandPalette/CommandPalette.js +6 -5
  27. package/components/ContextMenu/ContextMenu.css +262 -0
  28. package/components/ContextMenu/ContextMenu.d.ts +26 -0
  29. package/components/ContextMenu/ContextMenu.js +350 -0
  30. package/components/DateInput/DateInput.css +0 -36
  31. package/components/DateInput/DateInput.d.ts +18 -21
  32. package/components/DateInput/DateInput.js +76 -64
  33. package/components/DatePicker/DatePicker.d.ts +9 -2
  34. package/components/DatePicker/DatePicker.js +137 -128
  35. package/components/Dialog/Dialog.d.ts +15 -4
  36. package/components/Dialog/Dialog.js +136 -118
  37. package/components/Drawer/Drawer.d.ts +15 -4
  38. package/components/Drawer/Drawer.js +138 -119
  39. package/components/Dropdown/Dropdown.css +0 -36
  40. package/components/Dropdown/Dropdown.d.ts +24 -8
  41. package/components/Dropdown/Dropdown.js +219 -169
  42. package/components/DropdownMenu/DropdownMenu.js +10 -7
  43. package/components/Field/Field.css +80 -0
  44. package/components/Field/Field.d.ts +50 -0
  45. package/components/Field/Field.js +58 -0
  46. package/components/Field/FieldContext.d.ts +42 -0
  47. package/components/Field/FieldContext.js +7 -0
  48. package/components/FileInput/FileInput.css +0 -36
  49. package/components/FileInput/FileInput.d.ts +13 -17
  50. package/components/FileInput/FileInput.js +168 -141
  51. package/components/Input/Input.css +0 -43
  52. package/components/Input/Input.d.ts +23 -22
  53. package/components/Input/Input.js +93 -66
  54. package/components/Kbd/Kbd.css +28 -0
  55. package/components/Kbd/Kbd.d.ts +19 -0
  56. package/components/Kbd/Kbd.js +14 -0
  57. package/components/Popover/Popover.d.ts +0 -6
  58. package/components/Popover/Popover.js +22 -16
  59. package/components/ProgressBar/ProgressBar.js +1 -1
  60. package/components/RadioButton/RadioButton.d.ts +26 -13
  61. package/components/RadioButton/RadioButton.js +95 -70
  62. package/components/SelectionCard/SelectionCard.css +6 -2
  63. package/components/SelectionCard/SelectionCard.d.ts +10 -4
  64. package/components/SelectionCard/SelectionCard.js +72 -56
  65. package/components/Skeleton/Skeleton.css +2 -0
  66. package/components/Slider/Slider.d.ts +19 -10
  67. package/components/Slider/Slider.js +50 -40
  68. package/components/Spinner/Spinner.css +11 -0
  69. package/components/Spinner/Spinner.d.ts +2 -2
  70. package/components/Swatch/Swatch.css +52 -0
  71. package/components/Swatch/Swatch.d.ts +34 -0
  72. package/components/Swatch/Swatch.js +44 -0
  73. package/components/Table/Table.css +19 -0
  74. package/components/Table/Table.d.ts +10 -2
  75. package/components/Table/Table.js +58 -58
  76. package/components/Textarea/Textarea.css +0 -43
  77. package/components/Textarea/Textarea.d.ts +21 -22
  78. package/components/Textarea/Textarea.js +78 -67
  79. package/components/ToggleGroup/ToggleGroup.d.ts +18 -8
  80. package/components/ToggleGroup/ToggleGroup.js +61 -44
  81. package/components/ToggleSwitch/ToggleSwitch.css +7 -2
  82. package/components/ToggleSwitch/ToggleSwitch.d.ts +17 -9
  83. package/components/ToggleSwitch/ToggleSwitch.js +57 -44
  84. package/components/registry.d.ts +47 -10
  85. package/components/registry.js +5 -1
  86. package/components/registry.json +498 -56
  87. package/components/registry.json.d.ts +498 -56
  88. package/components/registry.json.js +4 -1
  89. package/index.d.ts +8 -1
  90. package/index.js +16 -1
  91. package/package.json +1 -1
@@ -1,8 +1,9 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
- import { useState, useRef, useId, useMemo, useCallback, useEffect } from "react";
2
+ import React, { useState, useRef, useId, useMemo, useCallback, useEffect } from "react";
3
+ import { Field } from "../Field/Field.js";
3
4
  import "./Combobox.css";
4
5
  import "../../fonts/material-symbols.css";
5
- const Combobox = ({
6
+ const Combobox = React.forwardRef(({
6
7
  label,
7
8
  placeholder = "Search…",
8
9
  value,
@@ -17,14 +18,16 @@ const Combobox = ({
17
18
  loading = false,
18
19
  emptyMessage = "No results found",
19
20
  clearable = false,
21
+ onValueChange,
20
22
  onChange,
21
23
  onSearchChange,
22
24
  manualFiltering = false,
23
25
  className = "",
24
26
  ariaLabel,
25
27
  name,
26
- id
27
- }) => {
28
+ id,
29
+ ...rest
30
+ }, ref) => {
28
31
  const [isOpen, setIsOpen] = useState(false);
29
32
  const [query, setQuery] = useState("");
30
33
  const [focusedIndex, setFocusedIndex] = useState(-1);
@@ -32,6 +35,11 @@ const Combobox = ({
32
35
  const inputRef = useRef(null);
33
36
  const listRef = useRef(null);
34
37
  const generatedId = useId();
38
+ const setRootRef = (node) => {
39
+ rootRef.current = node;
40
+ if (typeof ref === "function") ref(node);
41
+ else if (ref) ref.current = node;
42
+ };
35
43
  const baseClass = "ds-combobox";
36
44
  const isGrouped = Boolean(groups && groups.length > 0);
37
45
  const inputId = id || name || generatedId;
@@ -75,8 +83,9 @@ const Combobox = ({
75
83
  setQuery("");
76
84
  };
77
85
  const emitChange = (next) => {
78
- if (!onChange) return;
79
- onChange(multiple ? next : next[0] ?? "");
86
+ const payload = multiple ? next : next[0] ?? "";
87
+ onValueChange?.(payload);
88
+ onChange?.(payload);
80
89
  };
81
90
  const handleSelect = (option) => {
82
91
  if (option.disabled) return;
@@ -215,98 +224,106 @@ const Combobox = ({
215
224
  );
216
225
  };
217
226
  const hasSelection = selectedValues.length > 0;
218
- return /* @__PURE__ */ jsxs("div", { className: classes, ref: rootRef, children: [
219
- label && /* @__PURE__ */ jsxs("label", { className: `${baseClass}__label`, htmlFor: inputId, children: [
227
+ return /* @__PURE__ */ jsxs(
228
+ Field,
229
+ {
230
+ ...rest,
231
+ className: classes,
232
+ ref: setRootRef,
220
233
  label,
221
- required && /* @__PURE__ */ jsxs("span", { className: `${baseClass}__required`, "aria-hidden": "true", children: [
222
- " ",
223
- "*"
224
- ] })
225
- ] }),
226
- /* @__PURE__ */ jsxs("div", { className: `${baseClass}__control`, onClick: () => inputRef.current?.focus(), children: [
227
- /* @__PURE__ */ jsx("span", { className: `${baseClass}__search-icon material-symbols-rounded`, "aria-hidden": "true", children: "search" }),
228
- /* @__PURE__ */ jsxs("div", { className: `${baseClass}__field`, children: [
229
- multiple && selectedOptions.map((option) => /* @__PURE__ */ jsxs("span", { className: `${baseClass}__chip`, children: [
230
- option.label,
231
- /* @__PURE__ */ jsx(
234
+ helperText,
235
+ error,
236
+ required,
237
+ disabled,
238
+ size,
239
+ id: inputId,
240
+ children: [
241
+ /* @__PURE__ */ jsxs("div", { className: `${baseClass}__control`, onClick: () => inputRef.current?.focus(), children: [
242
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__search-icon material-symbols-rounded`, "aria-hidden": "true", children: "search" }),
243
+ /* @__PURE__ */ jsxs("div", { className: `${baseClass}__field`, children: [
244
+ multiple && selectedOptions.map((option) => /* @__PURE__ */ jsxs("span", { className: `${baseClass}__chip`, children: [
245
+ option.label,
246
+ /* @__PURE__ */ jsx(
247
+ "button",
248
+ {
249
+ type: "button",
250
+ className: `${baseClass}__chip-remove`,
251
+ onClick: (e) => {
252
+ e.stopPropagation();
253
+ handleRemove(option.value);
254
+ },
255
+ disabled,
256
+ "aria-label": `Remove ${option.label}`,
257
+ children: /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", "aria-hidden": "true", children: "close" })
258
+ }
259
+ )
260
+ ] }, option.value)),
261
+ /* @__PURE__ */ jsx(
262
+ "input",
263
+ {
264
+ ref: inputRef,
265
+ id: inputId,
266
+ name,
267
+ type: "text",
268
+ className: `${baseClass}__input`,
269
+ role: "combobox",
270
+ autoComplete: "off",
271
+ "aria-expanded": isOpen,
272
+ "aria-controls": listboxId,
273
+ "aria-autocomplete": "list",
274
+ "aria-activedescendant": activeOptionId,
275
+ "aria-label": ariaLabel || rest["aria-label"] || (!label ? placeholder : void 0),
276
+ "aria-describedby": helperText ? `${inputId}-helper` : void 0,
277
+ "aria-invalid": error || void 0,
278
+ "aria-required": required || void 0,
279
+ placeholder: multiple && hasSelection ? "" : placeholder,
280
+ value: inputValue,
281
+ disabled,
282
+ onChange: (e) => handleQueryChange(e.target.value),
283
+ onFocus: openMenu,
284
+ onKeyDown: handleKeyDown
285
+ }
286
+ )
287
+ ] }),
288
+ clearable && hasSelection && !disabled && /* @__PURE__ */ jsx(
232
289
  "button",
233
290
  {
234
291
  type: "button",
235
- className: `${baseClass}__chip-remove`,
292
+ className: `${baseClass}__clear`,
236
293
  onClick: (e) => {
237
294
  e.stopPropagation();
238
- handleRemove(option.value);
295
+ handleClear();
239
296
  },
240
- disabled,
241
- "aria-label": `Remove ${option.label}`,
297
+ "aria-label": "Clear selection",
242
298
  children: /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", "aria-hidden": "true", children: "close" })
243
299
  }
244
- )
245
- ] }, option.value)),
246
- /* @__PURE__ */ jsx(
247
- "input",
248
- {
249
- ref: inputRef,
250
- id: inputId,
251
- name,
252
- type: "text",
253
- className: `${baseClass}__input`,
254
- role: "combobox",
255
- autoComplete: "off",
256
- "aria-expanded": isOpen,
257
- "aria-controls": listboxId,
258
- "aria-autocomplete": "list",
259
- "aria-activedescendant": activeOptionId,
260
- "aria-label": ariaLabel || (!label ? placeholder : void 0),
261
- "aria-describedby": helperText ? `${inputId}-helper` : void 0,
262
- "aria-invalid": error || void 0,
263
- "aria-required": required || void 0,
264
- placeholder: multiple && hasSelection ? "" : placeholder,
265
- value: inputValue,
266
- disabled,
267
- onChange: (e) => handleQueryChange(e.target.value),
268
- onFocus: openMenu,
269
- onKeyDown: handleKeyDown
270
- }
271
- )
272
- ] }),
273
- clearable && hasSelection && !disabled && /* @__PURE__ */ jsx(
274
- "button",
275
- {
276
- type: "button",
277
- className: `${baseClass}__clear`,
278
- onClick: (e) => {
279
- e.stopPropagation();
280
- handleClear();
281
- },
282
- "aria-label": "Clear selection",
283
- children: /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", "aria-hidden": "true", children: "close" })
284
- }
285
- ),
286
- /* @__PURE__ */ jsx("span", { className: `${baseClass}__chevron material-symbols-rounded`, "aria-hidden": "true", children: "expand_more" })
287
- ] }),
288
- isOpen && /* @__PURE__ */ jsxs("ul", { className: `${baseClass}__menu`, role: "listbox", id: listboxId, ref: listRef, children: [
289
- loading && /* @__PURE__ */ jsx("li", { className: `${baseClass}__status`, role: "presentation", children: "Loading…" }),
290
- !loading && filteredOptions.length === 0 && /* @__PURE__ */ jsx("li", { className: `${baseClass}__status`, role: "presentation", children: emptyMessage }),
291
- !loading && (isGrouped ? (() => {
292
- let flatIndex = 0;
293
- return filteredGroups.map((group) => /* @__PURE__ */ jsxs("li", { className: `${baseClass}__group`, role: "none", children: [
294
- /* @__PURE__ */ jsx("span", { className: `${baseClass}__group-label`, role: "presentation", children: group.label }),
295
- /* @__PURE__ */ jsx(
296
- "ul",
297
- {
298
- className: `${baseClass}__group-list`,
299
- role: "group",
300
- "aria-label": group.label,
301
- children: group.options.map((option) => renderOption(option, flatIndex++))
302
- }
303
- )
304
- ] }, group.label));
305
- })() : filteredOptions.map((option, index) => renderOption(option, index)))
306
- ] }),
307
- helperText && /* @__PURE__ */ jsx("p", { className: `${baseClass}__helper`, id: `${inputId}-helper`, children: helperText })
308
- ] });
309
- };
300
+ ),
301
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__chevron material-symbols-rounded`, "aria-hidden": "true", children: "expand_more" })
302
+ ] }),
303
+ isOpen && /* @__PURE__ */ jsxs("ul", { className: `${baseClass}__menu`, role: "listbox", id: listboxId, ref: listRef, children: [
304
+ loading && /* @__PURE__ */ jsx("li", { className: `${baseClass}__status`, role: "presentation", children: "Loading…" }),
305
+ !loading && filteredOptions.length === 0 && /* @__PURE__ */ jsx("li", { className: `${baseClass}__status`, role: "presentation", children: emptyMessage }),
306
+ !loading && (isGrouped ? (() => {
307
+ let flatIndex = 0;
308
+ return filteredGroups.map((group) => /* @__PURE__ */ jsxs("li", { className: `${baseClass}__group`, role: "none", children: [
309
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__group-label`, role: "presentation", children: group.label }),
310
+ /* @__PURE__ */ jsx(
311
+ "ul",
312
+ {
313
+ className: `${baseClass}__group-list`,
314
+ role: "group",
315
+ "aria-label": group.label,
316
+ children: group.options.map((option) => renderOption(option, flatIndex++))
317
+ }
318
+ )
319
+ ] }, group.label));
320
+ })() : filteredOptions.map((option, index) => renderOption(option, index)))
321
+ ] })
322
+ ]
323
+ }
324
+ );
325
+ });
326
+ Combobox.displayName = "Combobox";
310
327
  export {
311
328
  Combobox
312
329
  };
@@ -241,21 +241,7 @@
241
241
  flex-shrink: 0;
242
242
  }
243
243
 
244
- .ds-command-palette__key {
245
- display: inline-flex;
246
- align-items: center;
247
- justify-content: center;
248
- min-width: 20px;
249
- padding: var(--padding-xxxs) var(--padding-xxs);
250
- border: var(--border-xs) solid var(--color-bg-container-border);
251
- border-radius: var(--radius-xs);
252
- background-color: var(--color-bg-container-primary);
253
- font-family: var(--font-paragraph-sm-family);
254
- font-size: var(--font-paragraph-sm-size);
255
- font-weight: var(--font-paragraph-sm-em-weight);
256
- line-height: var(--font-paragraph-sm-line-height);
257
- color: var(--color-text-tertiary);
258
- }
244
+ /* Keycaps render through the Kbd component (compact size) */
259
245
 
260
246
  /* ============================================
261
247
  FOOTER HINTS
@@ -1,6 +1,7 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { useState, useRef, useId, useSyncExternalStore, useCallback, useMemo, useEffect } from "react";
3
3
  import ReactDOM from "react-dom";
4
+ import { Kbd } from "../Kbd/Kbd.js";
4
5
  import "./CommandPalette.css";
5
6
  import "../../fonts/material-symbols.css";
6
7
  const emptySubscribe = () => () => {
@@ -225,7 +226,7 @@ const CommandPalette = ({
225
226
  /* @__PURE__ */ jsx("span", { className: `${baseClass}__command-label`, children: command.label }),
226
227
  command.description && /* @__PURE__ */ jsx("span", { className: `${baseClass}__command-description`, children: command.description })
227
228
  ] }),
228
- command.shortcut && command.shortcut.length > 0 && /* @__PURE__ */ jsx("span", { className: `${baseClass}__shortcut`, children: command.shortcut.map((key, i) => /* @__PURE__ */ jsx("kbd", { className: `${baseClass}__key`, children: key }, i)) })
229
+ command.shortcut && command.shortcut.length > 0 && /* @__PURE__ */ jsx("span", { className: `${baseClass}__shortcut`, children: command.shortcut.map((key, i) => /* @__PURE__ */ jsx(Kbd, { size: "compact", children: key }, i)) })
229
230
  ]
230
231
  },
231
232
  command.id
@@ -238,16 +239,16 @@ const CommandPalette = ({
238
239
  ] }),
239
240
  !hideFooter && /* @__PURE__ */ jsxs("div", { className: `${baseClass}__footer`, children: [
240
241
  /* @__PURE__ */ jsxs("span", { className: `${baseClass}__hint`, children: [
241
- /* @__PURE__ */ jsx("kbd", { className: `${baseClass}__key`, children: "↑" }),
242
- /* @__PURE__ */ jsx("kbd", { className: `${baseClass}__key`, children: "↓" }),
242
+ /* @__PURE__ */ jsx(Kbd, { size: "compact", children: "↑" }),
243
+ /* @__PURE__ */ jsx(Kbd, { size: "compact", children: "↓" }),
243
244
  "to navigate"
244
245
  ] }),
245
246
  /* @__PURE__ */ jsxs("span", { className: `${baseClass}__hint`, children: [
246
- /* @__PURE__ */ jsx("kbd", { className: `${baseClass}__key`, children: "↵" }),
247
+ /* @__PURE__ */ jsx(Kbd, { size: "compact", children: "↵" }),
247
248
  "to select"
248
249
  ] }),
249
250
  /* @__PURE__ */ jsxs("span", { className: `${baseClass}__hint`, children: [
250
- /* @__PURE__ */ jsx("kbd", { className: `${baseClass}__key`, children: "esc" }),
251
+ /* @__PURE__ */ jsx(Kbd, { size: "compact", children: "esc" }),
251
252
  "to close"
252
253
  ] })
253
254
  ] })
@@ -0,0 +1,262 @@
1
+ /* ============================================
2
+ CONTEXT MENU COMPONENT
3
+ Right-click menu anchored at the pointer.
4
+ Panel and item styling mirror DropdownMenu.
5
+ ============================================ */
6
+
7
+ /* Base — a plain block wrapper around the right-clickable area
8
+ (a real box, so the keyboard-invocation fallback can measure it) */
9
+ .ds-context-menu {
10
+ display: block;
11
+ }
12
+
13
+ /* ============================================
14
+ PANEL
15
+ Fixed-positioned at the pointer; left/top are
16
+ set inline from the contextmenu event coords.
17
+ ============================================ */
18
+
19
+ .ds-context-menu__panel {
20
+ position: fixed;
21
+ z-index: 50;
22
+ min-width: 200px;
23
+
24
+ /* Appearance — same recipe as DropdownMenu */
25
+ background: var(--color-bg-page-primary);
26
+ border: var(--border-xs) solid var(--color-input-border-primary);
27
+ border-radius: var(--radius-md);
28
+ box-shadow: var(--shadow-floating);
29
+
30
+ /* Inset gap */
31
+ padding: var(--padding-xxs);
32
+
33
+ /* Reset list */
34
+ list-style: none;
35
+ margin: 0;
36
+
37
+ outline: none;
38
+
39
+ /* Animation */
40
+ animation: ds-context-menu-appear var(--motion-duration-fast) var(--motion-ease-entrance);
41
+ }
42
+
43
+ /* Compact items: paragraph-sm-em, tighter padding, radius-sm */
44
+ .ds-context-menu__panel--compact .ds-context-menu__item {
45
+ padding: var(--padding-xs) var(--padding-sm-md);
46
+ border-radius: var(--radius-sm);
47
+ font-family: var(--font-paragraph-sm-em-family);
48
+ font-size: var(--font-paragraph-sm-em-size);
49
+ font-weight: var(--font-paragraph-sm-em-weight);
50
+ line-height: var(--font-paragraph-sm-em-line-height);
51
+ letter-spacing: var(--font-paragraph-sm-em-letter-spacing);
52
+ }
53
+
54
+ .ds-context-menu__panel--compact .ds-context-menu__item-icon {
55
+ --icon-size: var(--icon-size-sm);
56
+ }
57
+
58
+ .ds-context-menu__panel--compact .ds-context-menu__submenu-icon {
59
+ --icon-size: var(--icon-size-sm);
60
+ }
61
+
62
+ .ds-context-menu__panel--compact .ds-context-menu__group-label {
63
+ min-height: auto;
64
+ padding: var(--padding-xs) var(--padding-sm-md);
65
+ font-size: var(--font-paragraph-sm-em-size);
66
+ line-height: var(--font-paragraph-sm-em-line-height);
67
+ letter-spacing: var(--font-paragraph-sm-em-letter-spacing);
68
+ }
69
+
70
+ .ds-context-menu__panel--compact .ds-context-menu__separator {
71
+ margin: var(--padding-xxs) var(--padding-sm-md);
72
+ }
73
+
74
+ .ds-context-menu__panel--compact .ds-context-menu__shortcut {
75
+ font-size: var(--font-caption-size);
76
+ }
77
+
78
+ /* ============================================
79
+ SUB-MENU PANEL
80
+ Positioned relative to its parent item.
81
+ ============================================ */
82
+
83
+ .ds-context-menu__submenu {
84
+ position: absolute;
85
+ left: 100%;
86
+ top: 0;
87
+ margin-left: var(--gap-xxs);
88
+ }
89
+
90
+ /* ============================================
91
+ MENU ITEM
92
+ ============================================ */
93
+
94
+ .ds-context-menu__item {
95
+ display: flex;
96
+ align-items: center;
97
+ justify-content: space-between;
98
+ gap: var(--gap-md);
99
+ padding: var(--padding-sm) var(--padding-md);
100
+ border-radius: var(--radius-md);
101
+ cursor: pointer;
102
+ position: relative;
103
+ white-space: nowrap;
104
+ list-style: none;
105
+
106
+ font-family: var(--font-paragraph-em-family);
107
+ font-size: var(--font-paragraph-em-size);
108
+ font-weight: var(--font-paragraph-em-weight);
109
+ line-height: var(--font-paragraph-em-line-height);
110
+ letter-spacing: var(--font-paragraph-em-letter-spacing);
111
+ color: var(--color-action-passive-text);
112
+
113
+ transition: background-color var(--motion-duration-fast) var(--motion-ease-standard), color var(--motion-duration-fast) var(--motion-ease-standard);
114
+ }
115
+
116
+ .ds-context-menu__item:hover,
117
+ .ds-context-menu__item--focused {
118
+ background-color: var(--color-action-passive-bg-hover);
119
+ }
120
+
121
+ /* States — disabled */
122
+ .ds-context-menu__item--disabled {
123
+ opacity: 0.4;
124
+ cursor: not-allowed;
125
+ }
126
+
127
+ .ds-context-menu__item--disabled:hover,
128
+ .ds-context-menu__item--disabled.ds-context-menu__item--focused {
129
+ background-color: transparent;
130
+ }
131
+
132
+ /* ============================================
133
+ ITEM LABEL (text + optional icon)
134
+ ============================================ */
135
+
136
+ .ds-context-menu__item-label {
137
+ display: flex;
138
+ align-items: center;
139
+ gap: var(--gap-sm);
140
+ line-height: 1;
141
+ }
142
+
143
+ .ds-context-menu__item-icon {
144
+ --icon-size: var(--icon-size-md);
145
+ color: var(--color-icon-secondary);
146
+ flex-shrink: 0;
147
+ display: flex;
148
+ align-items: center;
149
+ justify-content: center;
150
+ line-height: 1;
151
+ }
152
+
153
+ /* ============================================
154
+ KEYBOARD SHORTCUT
155
+ ============================================ */
156
+
157
+ .ds-context-menu__shortcut {
158
+ font-family: var(--font-paragraph-sm-family);
159
+ font-size: var(--font-paragraph-sm-size);
160
+ font-weight: var(--font-paragraph-sm-weight);
161
+ line-height: var(--font-paragraph-sm-line-height);
162
+ letter-spacing: var(--font-paragraph-sm-letter-spacing);
163
+ color: var(--color-text-tertiary);
164
+ margin-left: auto;
165
+ flex-shrink: 0;
166
+ }
167
+
168
+ /* ============================================
169
+ SUB-MENU INDICATOR
170
+ ============================================ */
171
+
172
+ .ds-context-menu__submenu-icon {
173
+ --icon-size: var(--icon-size-md);
174
+ color: var(--color-text-tertiary);
175
+ margin-left: auto;
176
+ flex-shrink: 0;
177
+ display: flex;
178
+ align-items: center;
179
+ justify-content: center;
180
+ line-height: 1;
181
+ }
182
+
183
+ .ds-context-menu__item--has-submenu {
184
+ padding-right: var(--padding-sm-md);
185
+ }
186
+
187
+ /* ============================================
188
+ SEPARATOR
189
+ ============================================ */
190
+
191
+ .ds-context-menu__separator {
192
+ height: 1px;
193
+ background: var(--color-input-border-primary);
194
+ margin: var(--padding-xxs) var(--padding-md);
195
+ list-style: none;
196
+ }
197
+
198
+ /* ============================================
199
+ GROUP
200
+ ============================================ */
201
+
202
+ .ds-context-menu__group {
203
+ list-style: none;
204
+ }
205
+
206
+ .ds-context-menu__group-label {
207
+ display: flex;
208
+ align-items: center;
209
+ min-height: 40px;
210
+ font-family: var(--font-paragraph-em-family);
211
+ font-size: var(--font-paragraph-em-size);
212
+ font-weight: var(--font-paragraph-em-weight);
213
+ line-height: var(--font-paragraph-em-line-height);
214
+ letter-spacing: var(--font-paragraph-em-letter-spacing);
215
+ color: var(--color-text-tertiary);
216
+ padding-left: var(--padding-md);
217
+ padding-right: var(--padding-md);
218
+ user-select: none;
219
+ }
220
+
221
+ .ds-context-menu__group-list {
222
+ list-style: none;
223
+ margin: 0;
224
+ padding: 0;
225
+ }
226
+
227
+ /* ============================================
228
+ DESTRUCTIVE ITEM
229
+ ============================================ */
230
+
231
+ .ds-context-menu__item--destructive {
232
+ color: var(--color-core-accent-coral);
233
+ }
234
+
235
+ .ds-context-menu__item--destructive .ds-context-menu__item-icon {
236
+ color: var(--color-core-accent-coral);
237
+ }
238
+
239
+ .ds-context-menu__item--destructive .ds-context-menu__shortcut {
240
+ color: var(--color-core-accent-coral);
241
+ opacity: 0.7;
242
+ }
243
+
244
+ .ds-context-menu__item--destructive:hover,
245
+ .ds-context-menu__item--destructive.ds-context-menu__item--focused {
246
+ background-color: var(--color-status-error-bg);
247
+ }
248
+
249
+ /* ============================================
250
+ APPEAR ANIMATION
251
+ ============================================ */
252
+
253
+ @keyframes ds-context-menu-appear {
254
+ from {
255
+ opacity: 0;
256
+ transform: scale(0.96);
257
+ }
258
+ to {
259
+ opacity: 1;
260
+ transform: scale(1);
261
+ }
262
+ }
@@ -0,0 +1,26 @@
1
+ import { default as React } from 'react';
2
+ import { DropdownMenuEntry } from '../DropdownMenu/DropdownMenu';
3
+ /** Props owned by ContextMenu itself — everything else falls through to the wrapper div. */
4
+ type ContextMenuOwnProps = {
5
+ /** Menu entries — the same model as DropdownMenu (items, groups, separators) */
6
+ items: DropdownMenuEntry[];
7
+ /** Component size */
8
+ size?: 'default' | 'compact';
9
+ /** Accessible name for the menu */
10
+ ariaLabel?: string;
11
+ /** Additional CSS classes */
12
+ className?: string;
13
+ /** The right-clickable area the menu is attached to */
14
+ children: React.ReactNode;
15
+ };
16
+ export interface ContextMenuProps extends ContextMenuOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof ContextMenuOwnProps> {
17
+ }
18
+ /**
19
+ * ContextMenu opens a floating menu at the pointer position when the wrapped
20
+ * area is right-clicked (or the keyboard ContextMenu / Shift+F10 key is used).
21
+ * Entries use the same model as DropdownMenu — items, groups, separators,
22
+ * icons, shortcut hints, destructive and disabled states, and one level of
23
+ * sub-menus.
24
+ */
25
+ export declare const ContextMenu: React.ForwardRefExoticComponent<ContextMenuProps & React.RefAttributes<HTMLDivElement>>;
26
+ export {};