@rovula/ui 0.1.20 → 0.1.22

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 (99) hide show
  1. package/dist/cjs/bundle.css +316 -43
  2. package/dist/cjs/bundle.js +675 -675
  3. package/dist/cjs/bundle.js.map +1 -1
  4. package/dist/cjs/types/components/Badge/Badge.d.ts +40 -0
  5. package/dist/cjs/types/components/Badge/Badge.stories.d.ts +295 -0
  6. package/dist/cjs/types/components/Badge/Badge.styles.d.ts +7 -0
  7. package/dist/cjs/types/components/Badge/index.d.ts +2 -0
  8. package/dist/cjs/types/components/Dropdown/Dropdown.d.ts +4 -8
  9. package/dist/cjs/types/components/Dropdown/Dropdown.stories.d.ts +1 -6
  10. package/dist/cjs/types/components/DropdownMenu/DropdownMenu.d.ts +5 -1
  11. package/dist/cjs/types/components/DropdownMenu/DropdownMenu.stories.d.ts +45 -30
  12. package/dist/cjs/types/components/Form/Form.d.ts +2 -1
  13. package/dist/cjs/types/components/Form/Form.stories.d.ts +4 -0
  14. package/dist/cjs/types/components/ScrollArea/ScrollArea.d.ts +38 -0
  15. package/dist/cjs/types/components/ScrollArea/ScrollArea.stories.d.ts +301 -0
  16. package/dist/cjs/types/index.d.ts +4 -1
  17. package/dist/cjs/types/patterns/menu/Menu.d.ts +70 -0
  18. package/dist/cjs/types/{components/Menu → patterns/menu}/Menu.stories.d.ts +17 -10
  19. package/dist/cjs/types/utils/mergeRefs.d.ts +20 -0
  20. package/dist/components/Avatar/Avatar.styles.js +2 -2
  21. package/dist/components/Badge/Badge.js +36 -0
  22. package/dist/components/Badge/Badge.stories.js +51 -0
  23. package/dist/components/Badge/Badge.styles.js +62 -0
  24. package/dist/components/Badge/index.js +2 -0
  25. package/dist/components/Dropdown/Dropdown.js +54 -163
  26. package/dist/components/Dropdown/Dropdown.stories.js +29 -0
  27. package/dist/components/DropdownMenu/DropdownMenu.js +24 -13
  28. package/dist/components/DropdownMenu/DropdownMenu.stories.js +120 -88
  29. package/dist/components/Form/Form.js +11 -4
  30. package/dist/components/Form/Form.stories.js +27 -0
  31. package/dist/components/ScrollArea/ScrollArea.js +50 -0
  32. package/dist/components/ScrollArea/ScrollArea.stories.js +56 -0
  33. package/dist/components/TextInput/TextInput.js +6 -3
  34. package/dist/esm/bundle.css +316 -43
  35. package/dist/esm/bundle.js +1545 -1545
  36. package/dist/esm/bundle.js.map +1 -1
  37. package/dist/esm/types/components/Badge/Badge.d.ts +40 -0
  38. package/dist/esm/types/components/Badge/Badge.stories.d.ts +295 -0
  39. package/dist/esm/types/components/Badge/Badge.styles.d.ts +7 -0
  40. package/dist/esm/types/components/Badge/index.d.ts +2 -0
  41. package/dist/esm/types/components/Dropdown/Dropdown.d.ts +4 -8
  42. package/dist/esm/types/components/Dropdown/Dropdown.stories.d.ts +1 -6
  43. package/dist/esm/types/components/DropdownMenu/DropdownMenu.d.ts +5 -1
  44. package/dist/esm/types/components/DropdownMenu/DropdownMenu.stories.d.ts +45 -30
  45. package/dist/esm/types/components/Form/Form.d.ts +2 -1
  46. package/dist/esm/types/components/Form/Form.stories.d.ts +4 -0
  47. package/dist/esm/types/components/ScrollArea/ScrollArea.d.ts +38 -0
  48. package/dist/esm/types/components/ScrollArea/ScrollArea.stories.d.ts +301 -0
  49. package/dist/esm/types/index.d.ts +4 -1
  50. package/dist/esm/types/patterns/menu/Menu.d.ts +70 -0
  51. package/dist/esm/types/{components/Menu → patterns/menu}/Menu.stories.d.ts +17 -10
  52. package/dist/esm/types/utils/mergeRefs.d.ts +20 -0
  53. package/dist/index.d.ts +156 -74
  54. package/dist/index.js +3 -1
  55. package/dist/patterns/menu/Menu.js +95 -0
  56. package/dist/patterns/menu/Menu.stories.js +611 -0
  57. package/dist/src/theme/global.css +485 -57
  58. package/dist/utils/mergeRefs.js +42 -0
  59. package/package.json +1 -1
  60. package/src/components/Avatar/Avatar.styles.ts +2 -2
  61. package/src/components/Badge/Badge.stories.tsx +128 -0
  62. package/src/components/Badge/Badge.styles.ts +70 -0
  63. package/src/components/Badge/Badge.tsx +103 -0
  64. package/src/components/Badge/index.ts +3 -0
  65. package/src/components/Dropdown/Dropdown.stories.tsx +170 -1
  66. package/src/components/Dropdown/Dropdown.tsx +186 -276
  67. package/src/components/DropdownMenu/DropdownMenu.stories.tsx +1375 -253
  68. package/src/components/DropdownMenu/DropdownMenu.tsx +118 -55
  69. package/src/components/Form/Form.stories.tsx +70 -0
  70. package/src/components/Form/Form.tsx +23 -0
  71. package/src/components/ScrollArea/ScrollArea.stories.tsx +229 -0
  72. package/src/components/ScrollArea/ScrollArea.tsx +72 -0
  73. package/src/components/TextInput/TextInput.tsx +6 -3
  74. package/src/index.ts +4 -1
  75. package/src/patterns/menu/Menu.stories.tsx +1100 -0
  76. package/src/patterns/menu/Menu.tsx +282 -0
  77. package/src/theme/global.css +84 -11
  78. package/src/theme/themes/xspector/baseline.css +1 -1
  79. package/src/theme/themes/xspector/components/scrollbar.css +12 -0
  80. package/src/theme/tokens/baseline.css +3 -1
  81. package/src/theme/tokens/components/badge.css +54 -0
  82. package/src/theme/tokens/components/dropdown-menu.css +16 -5
  83. package/src/theme/tokens/components/scrollbar.css +18 -0
  84. package/src/utils/mergeRefs.ts +46 -0
  85. package/dist/cjs/types/components/Menu/Menu.d.ts +0 -65
  86. package/dist/cjs/types/components/Menu/helpers.d.ts +0 -19
  87. package/dist/cjs/types/components/Menu/index.d.ts +0 -4
  88. package/dist/components/Menu/Menu.js +0 -64
  89. package/dist/components/Menu/Menu.stories.js +0 -406
  90. package/dist/components/Menu/helpers.js +0 -28
  91. package/dist/components/Menu/index.js +0 -3
  92. package/dist/esm/types/components/Menu/Menu.d.ts +0 -65
  93. package/dist/esm/types/components/Menu/helpers.d.ts +0 -19
  94. package/dist/esm/types/components/Menu/index.d.ts +0 -4
  95. package/src/components/Menu/Menu.stories.tsx +0 -586
  96. package/src/components/Menu/Menu.tsx +0 -235
  97. package/src/components/Menu/helpers.ts +0 -45
  98. package/src/components/Menu/index.ts +0 -7
  99. package/src/theme/themes/xspector/components/dropdown-menu.css +0 -28
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  var __rest = (this && this.__rest) || function (s, e) {
2
3
  var t = {};
3
4
  for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
@@ -9,190 +10,80 @@ var __rest = (this && this.__rest) || function (s, e) {
9
10
  }
10
11
  return t;
11
12
  };
12
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
13
14
  import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, Fragment, } from "react";
14
- import * as Portal from "@radix-ui/react-portal";
15
+ import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption, } from "@headlessui/react";
15
16
  import TextInput from "../TextInput/TextInput";
16
17
  import { customInputVariant, dropdownIconVariant } from "./Dropdown.styles";
17
- import { Menu } from "../Menu/Menu";
18
- import { ChevronDownIcon } from "@heroicons/react/16/solid";
19
18
  import { cn } from "@/utils/cn";
19
+ import Icon from "../Icon/Icon";
20
+ import { ChevronDownIcon } from "@heroicons/react/16/solid";
21
+ // ---------------------------------------------------------------------------
22
+ // Shared menu item styles (used by Dropdown items + renderOptions consumers)
23
+ // ---------------------------------------------------------------------------
24
+ export const menuItemBaseStyles = cn("relative flex gap-1 cursor-pointer select-none box-border items-center py-4 pl-4 pr-8 typography-subtitle4 outline-none transition-colors", "bg-[var(--dropdown-menu-default-bg)] text-[var(--dropdown-menu-default-text)]", "active:opacity-75", "hover:bg-[var(--dropdown-menu-hover-bg)] hover:text-[var(--dropdown-menu-hover-text)]");
25
+ // ---------------------------------------------------------------------------
26
+ // Dropdown
27
+ // ---------------------------------------------------------------------------
20
28
  const Dropdown = forwardRef((_a, ref) => {
21
- var { id, options = [], value, label, size = "md", rounded = "normal", variant = "outline", defaultMenuItemType = "checkbox", helperText, errorMessage, fullwidth = true, disabled = false, error = false, filterMode = false, required = true, modal = false, onChangeText, onSelect, renderOptions: customRenderOptions, optionContainerClassName, optionItemClassName, optionNotFoundItemClassName, segmentedInput = true } = _a, props = __rest(_a, ["id", "options", "value", "label", "size", "rounded", "variant", "defaultMenuItemType", "helperText", "errorMessage", "fullwidth", "disabled", "error", "filterMode", "required", "modal", "onChangeText", "onSelect", "renderOptions", "optionContainerClassName", "optionItemClassName", "optionNotFoundItemClassName", "segmentedInput"]);
29
+ var { id, options = [], value, label, size = "md", rounded = "normal", variant = "outline", helperText, errorMessage, fullwidth = true, disabled = false, error = false, filterMode = false, required = true, modal: _modal, onChangeText, onSelect, renderOptions: customRenderOptions, optionContainerClassName, optionItemClassName, optionNotFoundItemClassName, segmentedInput = true } = _a, props = __rest(_a, ["id", "options", "value", "label", "size", "rounded", "variant", "helperText", "errorMessage", "fullwidth", "disabled", "error", "filterMode", "required", "modal", "onChangeText", "onSelect", "renderOptions", "optionContainerClassName", "optionItemClassName", "optionNotFoundItemClassName", "segmentedInput"]);
22
30
  const _id = id || `${label}-select`;
23
- const [isFocused, setIsFocused] = useState(false);
24
31
  const [selectedOption, setSelectedOption] = useState(null);
25
- const [textValue, setTextValue] = useState("");
26
- const keyCode = useRef("");
27
- const dropdownRef = useRef(null);
32
+ const [query, setQuery] = useState("");
28
33
  const inputRef = useRef(null);
29
- const [dropdownStyles, setDropdownStyles] = useState({});
30
- const [isAbove, setIsAbove] = useState(false);
31
- const [isInsideDialog, setIsInsideDialog] = useState(false);
32
- useImperativeHandle(ref, () => inputRef === null || inputRef === void 0 ? void 0 : inputRef.current);
34
+ // Expose the inner <input> element via forwardRef
35
+ useImperativeHandle(ref, () => inputRef.current, []);
36
+ // Sync external value prop
33
37
  useEffect(() => {
34
- var _a;
35
- setSelectedOption(value);
36
- setTextValue((_a = value === null || value === void 0 ? void 0 : value.label) !== null && _a !== void 0 ? _a : "");
38
+ setSelectedOption(value !== null && value !== void 0 ? value : null);
37
39
  }, [value]);
38
- /** ✅ Auto-detect if inside a Dialog */
39
- useEffect(() => {
40
- let node = inputRef.current;
41
- while (node) {
42
- if (node.getAttribute("role") === "dialog") {
43
- setIsInsideDialog(true);
44
- return;
45
- }
46
- node = node.parentElement;
47
- }
48
- setIsInsideDialog(false);
49
- }, []);
50
- const handleOnChangeText = useCallback((event) => {
51
- onChangeText === null || onChangeText === void 0 ? void 0 : onChangeText(event);
52
- setTextValue(event.target.value);
53
- if (!event.target.value) {
54
- clearMismatchValue(event);
55
- }
56
- }, [onChangeText]);
57
- const handleOptionClick = useCallback((option) => {
58
- setSelectedOption(option);
59
- setTextValue(option.label);
60
- onSelect === null || onSelect === void 0 ? void 0 : onSelect(option);
61
- setIsFocused(false);
62
- }, [onSelect]);
63
40
  const optionsFiltered = useMemo(() => {
64
- return options.filter((option) => {
65
- var _a;
66
- return !filterMode ||
67
- ((_a = option.label) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(textValue === null || textValue === void 0 ? void 0 : textValue.toLowerCase()));
68
- });
69
- }, [options, filterMode, textValue]);
70
- const usePortal = isInsideDialog ? false : modal;
71
- const updateDropdownPosition = useCallback(() => {
72
- if (inputRef.current && dropdownRef.current) {
73
- const rect = inputRef.current.getBoundingClientRect();
74
- const dropdownHeight = dropdownRef.current.offsetHeight;
75
- const spaceBelow = window.innerHeight - rect.bottom;
76
- const spaceAbove = rect.top;
77
- const shouldOpenAbove = spaceBelow < dropdownHeight && spaceAbove > spaceBelow;
78
- setIsAbove(shouldOpenAbove);
79
- if (usePortal) {
80
- setDropdownStyles({
81
- position: "absolute",
82
- top: shouldOpenAbove
83
- ? `${rect.top - dropdownHeight}px`
84
- : `${rect.bottom}px`,
85
- left: `${rect.left}px`,
86
- width: `${rect.width}px`,
87
- zIndex: 9999,
88
- });
89
- }
90
- else {
91
- setDropdownStyles({
92
- position: "absolute",
93
- top: shouldOpenAbove ? `-${dropdownHeight}px` : "100%",
94
- left: "0",
95
- width: "100%",
96
- zIndex: 9999,
97
- });
98
- }
99
- }
100
- }, [modal, isInsideDialog, usePortal]);
101
- useEffect(() => {
102
- if (isFocused) {
103
- updateDropdownPosition();
104
- window.addEventListener("resize", updateDropdownPosition);
41
+ if (!filterMode || !query)
42
+ return options;
43
+ return options.filter((opt) => opt.label.toLowerCase().includes(query.toLowerCase()));
44
+ }, [options, filterMode, query]);
45
+ const handleSelect = useCallback((option) => {
46
+ setSelectedOption(option);
47
+ setQuery("");
48
+ if (option) {
49
+ onSelect === null || onSelect === void 0 ? void 0 : onSelect(option);
50
+ // After selection Headless UI keeps focus on the input (via rAF refocus).
51
+ // Blur after that rAF so the next click triggers onFocus → immediate open.
52
+ setTimeout(() => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur(); }, 0);
105
53
  }
106
- return () => {
107
- window.removeEventListener("resize", updateDropdownPosition);
108
- };
109
- }, [isFocused, updateDropdownPosition]);
110
- const renderOptions = () => {
54
+ }, [onSelect]);
55
+ const handleInputChange = useCallback((e) => {
56
+ if (filterMode)
57
+ setQuery(e.target.value);
58
+ onChangeText === null || onChangeText === void 0 ? void 0 : onChangeText(e);
59
+ }, [filterMode, onChangeText]);
60
+ const renderOptionList = () => {
111
61
  if (customRenderOptions) {
112
62
  return customRenderOptions({
113
63
  optionsFiltered,
114
64
  selectedOption,
115
- onClick: handleOptionClick,
116
- style: dropdownStyles,
117
- dropdownRef,
65
+ onClick: handleSelect,
118
66
  });
119
67
  }
120
- // Convert options to MenuItemType
121
- let finalMenuItems;
122
- finalMenuItems = optionsFiltered.map((option) => {
68
+ if (optionsFiltered.length === 0) {
69
+ return (_jsx("div", { className: cn("px-4 py-14 text-center text-input-text", optionNotFoundItemClassName), children: "Not found" }));
70
+ }
71
+ return optionsFiltered.map((option) => {
123
72
  if (option.renderLabel) {
124
- return {
125
- type: "custom",
126
- render: () => (_jsx(Fragment, { children: option.renderLabel({
127
- value: option.value,
128
- label: option.label,
129
- handleOnClick: () => handleOptionClick(option),
130
- className: cn("relative flex gap-3 cursor-pointer select-none box-border items-center py-4 pl-9 pr-4 typography-subtitle4 outline-none transition-colors", "bg-[var(--dropdown-menu-default-bg)] text-[var(--dropdown-menu-default-text)]", "active:opacity-75", "hover:bg-[var(--dropdown-menu-hover-bg)] hover:text-[var(--dropdown-menu-hover-text)]", {
131
- "bg-[var(--dropdown-menu-selected-bg)] text-[var(--dropdown-menu-selected-text)] typography-subtitle5": (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.value) === option.value,
132
- }, optionItemClassName),
133
- }) }, option.value)),
134
- };
73
+ return (_jsx(Fragment, { children: option.renderLabel({
74
+ value: option.value,
75
+ label: option.label,
76
+ handleOnClick: () => handleSelect(option),
77
+ className: cn(menuItemBaseStyles, (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.value) === option.value &&
78
+ "bg-[var(--dropdown-menu-selected-bg)] text-[var(--dropdown-menu-selected-text)]", optionItemClassName),
79
+ }) }, option.value));
135
80
  }
136
- return {
137
- type: "item",
138
- item: {
139
- type: defaultMenuItemType,
140
- value: option.value,
141
- label: option.label,
142
- },
143
- };
81
+ return (_jsx(ComboboxOption, { value: option, className: ({ focus, selected }) => cn(menuItemBaseStyles, (selected || (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.value) === option.value) &&
82
+ "bg-[var(--dropdown-menu-selected-bg)] text-[var(--dropdown-menu-selected-text)]", focus &&
83
+ "bg-[var(--dropdown-menu-hover-bg)] text-[var(--dropdown-menu-hover-text)]", optionItemClassName), children: ({ selected }) => (_jsxs(_Fragment, { children: [_jsx("span", { className: "shrink-0 size-4 flex items-center justify-center", children: (selected || (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.value) === option.value) && (_jsx(Icon, { type: "heroicons", name: "check", className: "size-4 text-[var(--dropdown-menu-selected-text)]" })) }), option.label] })) }, option.value));
144
84
  });
145
- // Add "not found" message if no results
146
- if (finalMenuItems.length === 0) {
147
- finalMenuItems.push({
148
- type: "custom",
149
- render: () => (_jsx("div", { className: cn("px-4 py-14 text-center text-input-text", optionNotFoundItemClassName), children: "Not found" }, "not-found")),
150
- });
151
- }
152
- return (_jsx(Menu, { ref: dropdownRef, items: finalMenuItems, selectedValues: (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.value) ? [selectedOption.value] : [], onSelect: (value) => {
153
- const option = optionsFiltered.find((opt) => opt.value === value);
154
- if (option) {
155
- handleOptionClick(option);
156
- }
157
- }, className: cn("absolute mt-1 w-full max-h-60 overflow-y-auto", !usePortal && (isAbove ? "bottom-full mb-1" : "top-full mt-1"), optionContainerClassName), style: dropdownStyles }));
158
85
  };
159
- const handleOnFocus = useCallback((e) => {
160
- var _a;
161
- setIsFocused(true);
162
- (_a = props === null || props === void 0 ? void 0 : props.onFocus) === null || _a === void 0 ? void 0 : _a.call(props, e);
163
- }, [props === null || props === void 0 ? void 0 : props.onFocus]);
164
- const clearMismatchValue = useCallback((e) => {
165
- const matchSelectedValue = optionsFiltered.find((opt) => { var _a, _b; return opt.value === ((_a = e.target) === null || _a === void 0 ? void 0 : _a.value) || opt.label === ((_b = e.target) === null || _b === void 0 ? void 0 : _b.value); });
166
- const isMatchSelectedValue = !!matchSelectedValue;
167
- let option = matchSelectedValue || {
168
- value: "",
169
- label: "",
170
- };
171
- if (!isMatchSelectedValue && textValue) {
172
- option = {
173
- value: "",
174
- label: "",
175
- };
176
- }
177
- if (keyCode.current === "Enter") {
178
- return;
179
- }
180
- setSelectedOption(option);
181
- setTextValue(option.label);
182
- onSelect === null || onSelect === void 0 ? void 0 : onSelect(option);
183
- }, [optionsFiltered, textValue]);
184
- const handleOnBlur = useCallback((e) => {
185
- var _a;
186
- setTimeout(() => setIsFocused(false), 200);
187
- clearMismatchValue(e);
188
- (_a = props === null || props === void 0 ? void 0 : props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props, e);
189
- }, [props === null || props === void 0 ? void 0 : props.onBlur, clearMismatchValue]);
190
- const handleOnKeyDown = useCallback((e) => {
191
- var _a;
192
- keyCode.current = e.code;
193
- (_a = props === null || props === void 0 ? void 0 : props.onKeyDown) === null || _a === void 0 ? void 0 : _a.call(props, e);
194
- }, [props === null || props === void 0 ? void 0 : props.onKeyDown]);
195
- return (_jsxs("div", { className: `relative ${fullwidth ? "w-full" : ""}`, children: [_jsx(TextInput, Object.assign({ hasClearIcon: false, endIcon: _jsx(ChevronDownIcon, { className: dropdownIconVariant({ isFocus: isFocused }) }) }, props, { ref: inputRef, readOnly: !filterMode, value: textValue, onChange: handleOnChangeText, label: label, placeholder: " ", type: "text", autoComplete: "off", rounded: rounded, variant: variant, helperText: helperText, errorMessage: errorMessage, fullwidth: fullwidth, error: error, required: required, id: _id, disabled: disabled, size: size, className: segmentedInput ? customInputVariant({ size }) : undefined, onFocus: handleOnFocus, onBlur: handleOnBlur, onKeyDown: handleOnKeyDown })), isFocused &&
196
- (usePortal ? (_jsx(Portal.Root, { container: document.body, children: renderOptions() })) : (renderOptions()))] }));
86
+ return (_jsx(Combobox, { value: selectedOption, onChange: handleSelect, immediate: true, by: "value", disabled: disabled, children: ({ open }) => (_jsxs("div", { className: cn("relative", fullwidth && "w-full"), children: [_jsx(ComboboxInput, Object.assign({ as: TextInput, ref: inputRef, hasClearIcon: false, endIcon: _jsx(ChevronDownIcon, { className: dropdownIconVariant({ isFocus: open }) }), label: label, placeholder: " ", autoComplete: "off", rounded: rounded, variant: variant, helperText: helperText, errorMessage: errorMessage, fullwidth: fullwidth, error: error, required: required, id: _id, disabled: disabled, size: size, className: segmentedInput ? customInputVariant({ size }) : undefined, displayValue: (opt) => { var _a; return (_a = opt === null || opt === void 0 ? void 0 : opt.label) !== null && _a !== void 0 ? _a : ""; }, readOnly: !filterMode, onChange: handleInputChange }, props)), _jsx(ComboboxOptions, { className: cn("absolute top-full left-0 w-full mt-1 z-[51]", "min-w-[154px] max-h-60 overflow-y-auto", "rounded-lg bg-modal-surface text-text-g-contrast-high", optionContainerClassName), style: { boxShadow: "var(--dropdown-menu-shadow)" }, children: renderOptionList() })] })) }));
197
87
  });
88
+ Dropdown.displayName = "Dropdown";
198
89
  export default Dropdown;
@@ -3,6 +3,7 @@ import { useRef, useState } from "react";
3
3
  import Dropdown from "./Dropdown";
4
4
  import Button from "../Button/Button";
5
5
  import { cn } from "@/utils/cn";
6
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogBody, DialogFooter, DialogTrigger, DialogClose, } from "../Dialog/Dialog";
6
7
  // More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction
7
8
  const meta = {
8
9
  title: "Components/Dropdown",
@@ -113,3 +114,31 @@ export const WithIcons = {
113
114
  return (_jsx("div", { className: "flex flex-row gap-4 w-full", children: _jsx(Dropdown, { id: "with-icons", size: "lg", label: "Select Food", fullwidth: true, options: optionsWithIcons, value: selectedValue, onSelect: (option) => setSelectedValue(option) }) }));
114
115
  },
115
116
  };
117
+ // ---------------------------------------------------------------------------
118
+ // Dropdown inside Dialog — showcases the fix for overflow:hidden bug
119
+ // ---------------------------------------------------------------------------
120
+ const dialogOptions = [
121
+ { value: "design", label: "Design" },
122
+ { value: "engineering", label: "Engineering" },
123
+ { value: "product", label: "Product" },
124
+ { value: "marketing", label: "Marketing" },
125
+ { value: "data", label: "Data & Analytics" },
126
+ { value: "ops", label: "Operations" },
127
+ ];
128
+ const filterableOptions = new Array(20).fill("").map((_, i) => ({
129
+ value: `member-${i + 1}`,
130
+ label: `Team Member ${i + 1}`,
131
+ }));
132
+ export const InsideDialog = {
133
+ name: "Inside Dialog",
134
+ render: () => {
135
+ const [department, setDepartment] = useState();
136
+ const [member, setMember] = useState();
137
+ const [role, setRole] = useState();
138
+ return (_jsxs("div", { className: "flex gap-4 flex-wrap", children: [_jsxs("div", { children: [_jsx("p", { className: "typography-small4 text-text-g-contrast-medium mb-2", children: "Single Dropdown" }), _jsxs(Dialog, { children: [_jsx(DialogTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", children: "Open Dialog" }) }), _jsxs(DialogContent, { showCloseButton: true, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Assign to Department" }), _jsx(DialogDescription, { children: "Dropdown popup appears above the dialog overlay \u2014 not clipped by overflow." })] }), _jsx(DialogBody, { className: "gap-4 py-2", children: _jsx(Dropdown, { id: "dept", label: "Department", size: "md", fullwidth: true, options: dialogOptions, value: department, onSelect: setDepartment }) }), _jsxs(DialogFooter, { children: [_jsx(DialogClose, { asChild: true, children: _jsx(Button, { variant: "outline", children: "Cancel" }) }), _jsx(Button, { children: "Confirm" })] })] })] })] }), _jsxs("div", { children: [_jsx("p", { className: "typography-small4 text-text-g-contrast-medium mb-2", children: "Multiple Dropdowns" }), _jsxs(Dialog, { children: [_jsx(DialogTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", children: "Open Dialog" }) }), _jsxs(DialogContent, { showCloseButton: true, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Invite Team Member" }), _jsx(DialogDescription, { children: "Multiple dropdowns \u2014 each opens its own popup independently." })] }), _jsxs(DialogBody, { className: "gap-4 py-2", children: [_jsx(Dropdown, { id: "member", label: "Member", size: "md", fullwidth: true, options: filterableOptions, value: member, onSelect: setMember, filterMode: true }), _jsx(Dropdown, { id: "role", label: "Role", size: "md", fullwidth: true, options: [
139
+ { value: "viewer", label: "Viewer" },
140
+ { value: "editor", label: "Editor" },
141
+ { value: "admin", label: "Admin" },
142
+ ], value: role, onSelect: setRole })] }), _jsxs(DialogFooter, { children: [_jsx(DialogClose, { asChild: true, children: _jsx(Button, { variant: "outline", children: "Cancel" }) }), _jsx(Button, { disabled: !member || !role, children: "Send Invite" })] })] })] })] }), _jsxs("div", { children: [_jsx("p", { className: "typography-small4 text-text-g-contrast-medium mb-2", children: "Filter Mode" }), _jsxs(Dialog, { children: [_jsx(DialogTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", children: "Open Dialog" }) }), _jsxs(DialogContent, { showCloseButton: true, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Search & Select" }), _jsx(DialogDescription, { children: "filterMode=true \u2014 type to filter options, popup stays properly positioned." })] }), _jsx(DialogBody, { className: "gap-4 py-2", children: _jsx(Dropdown, { id: "member-filter", label: "Search member", size: "md", fullwidth: true, filterMode: true, options: filterableOptions, value: member, onSelect: setMember }) }), _jsxs(DialogFooter, { children: [_jsx(DialogClose, { asChild: true, children: _jsx(Button, { variant: "outline", children: "Cancel" }) }), _jsx(Button, { disabled: !member, children: "Select" })] })] })] })] })] }));
143
+ },
144
+ };
@@ -23,47 +23,58 @@ const DropdownMenuSub = DropdownMenuPrimitive.Sub;
23
23
  const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
24
24
  const DropdownMenuSubTrigger = React.forwardRef((_a, ref) => {
25
25
  var { className, inset, children } = _a, props = __rest(_a, ["className", "inset", "children"]);
26
- return (_jsxs(DropdownMenuPrimitive.SubTrigger, Object.assign({ ref: ref, className: cn(
27
- // "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-primary data-[state=open]:bg-primary",
28
- "relative flex gap-3 cursor-pointer select-none box-border items-center py-4 pl-9 pr-4 typography-subtitle4 outline-none transition-colors data-[disabled]:pointer-events-none", "bg-[var(--dropdown-menu-default-bg)] text-[var(--dropdown-menu-default-text)]", "active:opacity-75", "focus:!bg-[var(--dropdown-menu-hover-bg)] focus:!text-[var(--dropdown-menu-hover-text)]", "data-[disabled]:!bg-[var(--dropdown-menu-disabled-bg)] data-[disabled]:!text-[var(--dropdown-menu-disabled-text)]", inset && "pl-8", className) }, props, { children: [children, _jsx(Icon, { type: "heroicons", name: "chevron-right", className: "ml-auto h-4 w-4" })] })));
26
+ return (_jsxs(DropdownMenuPrimitive.SubTrigger, Object.assign({ ref: ref, className: cn("relative flex gap-4 cursor-pointer select-none box-border items-center py-4 pl-4 pr-4 typography-subtitle4 outline-none transition-colors data-[disabled]:pointer-events-none", "bg-[var(--dropdown-menu-default-bg)] text-[var(--dropdown-menu-default-text)]", "active:opacity-75", "focus:!bg-[var(--dropdown-menu-hover-bg)] focus:!text-[var(--dropdown-menu-hover-text)]", "data-[disabled]:!bg-[var(--dropdown-menu-disabled-bg)] data-[disabled]:!text-[var(--dropdown-menu-disabled-text)]", inset && "pl-8", className) }, props, { children: [children, _jsx(Icon, { type: "heroicons", name: "chevron-right", className: "ml-auto h-4 w-4" })] })));
29
27
  });
30
28
  DropdownMenuSubTrigger.displayName =
31
29
  DropdownMenuPrimitive.SubTrigger.displayName;
32
30
  const DropdownMenuSubContent = React.forwardRef((_a, ref) => {
33
31
  var { className } = _a, props = __rest(_a, ["className"]);
34
- return (_jsx(DropdownMenuPrimitive.SubContent, Object.assign({ ref: ref, className: cn("z-50 min-w-[154px] overflow-hidden rounded-md bg-modal-surface text-text-contrast-low data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className) }, props, { style: Object.assign({ boxShadow: "var(--dropdown-menu-shadow)" }, props.style) })));
32
+ return (_jsx(DropdownMenuPrimitive.SubContent, Object.assign({ ref: ref, className: cn("z-50 min-w-[154px] overflow-hidden rounded-lg bg-modal-surface text-text-contrast-low data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className) }, props, { style: Object.assign({ boxShadow: "var(--dropdown-menu-shadow)" }, props.style) })));
35
33
  });
36
34
  DropdownMenuSubContent.displayName =
37
35
  DropdownMenuPrimitive.SubContent.displayName;
38
36
  const DropdownMenuContent = React.forwardRef((_a, ref) => {
39
37
  var { className, sideOffset = 4 } = _a, props = __rest(_a, ["className", "sideOffset"]);
40
- return (_jsx(DropdownMenuPrimitive.Portal, { children: _jsx(DropdownMenuPrimitive.Content, Object.assign({ ref: ref, sideOffset: sideOffset, className: cn("z-50 min-w-[154px] overflow-hidden rounded-md bg-modal-surface text-text-contrast-low data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className) }, props, { style: Object.assign({ boxShadow: "var(--dropdown-menu-shadow)" }, props.style) })) }));
38
+ return (_jsx(DropdownMenuPrimitive.Portal, { children: _jsx(DropdownMenuPrimitive.Content, Object.assign({ ref: ref, sideOffset: sideOffset, className: cn("z-50 min-w-[154px] overflow-hidden rounded-lg bg-modal-surface text-text-contrast-low data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className) }, props, { style: Object.assign({ boxShadow: "var(--dropdown-menu-shadow)" }, props.style) })) }));
41
39
  });
42
40
  DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
43
41
  const DropdownMenuItem = React.forwardRef((_a, ref) => {
44
- var { className, inset } = _a, props = __rest(_a, ["className", "inset"]);
45
- return (_jsx(DropdownMenuPrimitive.Item, Object.assign({ ref: ref, className: cn("relative flex gap-3 cursor-pointer select-none box-border items-center py-4 pl-9 pr-xxl typography-subtitle4 outline-none transition-colors data-[disabled]:pointer-events-none", "bg-[var(--dropdown-menu-default-bg)] text-[var(--dropdown-menu-default-text)]", "active:opacity-75", "focus:!bg-[var(--dropdown-menu-hover-bg)] focus:!text-[var(--dropdown-menu-hover-text)]", "data-[disabled]:!bg-[var(--dropdown-menu-disabled-bg)] data-[disabled]:!text-[var(--dropdown-menu-disabled-text)]", inset && "pl-8", className) }, props)));
42
+ var { className, inset, selected, icon, children } = _a, props = __rest(_a, ["className", "inset", "selected", "icon", "children"]);
43
+ const hasIcon = !!icon;
44
+ return (_jsxs(DropdownMenuPrimitive.Item, Object.assign({ ref: ref, className: cn("relative flex cursor-pointer select-none box-border items-center py-4 pl-4 pr-8 typography-subtitle4 outline-none transition-colors data-[disabled]:pointer-events-none", "bg-[var(--dropdown-menu-default-bg)] text-[var(--dropdown-menu-default-text)]", "active:opacity-75", "focus:!bg-[var(--dropdown-menu-hover-bg)] focus:!text-[var(--dropdown-menu-hover-text)]", selected &&
45
+ "bg-[var(--dropdown-menu-selected-bg)] text-[var(--dropdown-menu-selected-text)] typography-subtitle5", "data-[disabled]:!bg-[var(--dropdown-menu-disabled-bg)] data-[disabled]:!text-[var(--dropdown-menu-disabled-text)]", inset && "pl-8", className, hasIcon ? "gap-4" : "gap-1") }, props, { children: [_jsxs("div", { className: "flex shrink-0 flex-row gap-1", children: [_jsx("span", { className: "size-4 flex items-center justify-center", children: selected && (_jsx(Icon, { type: "heroicons", name: "check", className: "size-4 text-[var(--dropdown-menu-selected-text)]" })) }), icon] }), children] })));
46
46
  });
47
47
  DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
48
48
  const DropdownMenuCheckboxItem = React.forwardRef((_a, ref) => {
49
- var { className, children, checked } = _a, props = __rest(_a, ["className", "children", "checked"]);
50
- return (_jsxs(DropdownMenuPrimitive.CheckboxItem, Object.assign({ ref: ref, className: cn("relative flex gap-3 cursor-pointer select-none box-border items-center py-4 pl-9 pr-xxl typography-subtitle4 outline-none transition-colors data-[disabled]:pointer-events-none", "bg-[var(--dropdown-menu-default-bg)] text-[var(--dropdown-menu-default-text)]", "active:opacity-75", "focus:!bg-[var(--dropdown-menu-hover-bg)] focus:!text-[var(--dropdown-menu-hover-text)]", "data-[state='checked']:bg-[var(--dropdown-menu-selected-bg)] data-[state='checked']:text-[var(--dropdown-menu-selected-text)] data-[state='checked']:typography-subtitle5", "data-[disabled]:!bg-[var(--dropdown-menu-disabled-bg)] data-[disabled]:!text-[var(--dropdown-menu-disabled-text)]", className), checked: checked }, props, { children: [_jsx("span", { className: "absolute left-4 flex items-center justify-center", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(Icon, { type: "heroicons", name: "check", className: "size-4" }) }) }), children] })));
49
+ var { className, children, checked, disabled } = _a, props = __rest(_a, ["className", "children", "checked", "disabled"]);
50
+ return (_jsxs(DropdownMenuPrimitive.CheckboxItem, Object.assign({ ref: ref, className: cn("relative flex gap-3 cursor-pointer select-none box-border items-center py-4 pl-4 pr-8 typography-subtitle4 outline-none transition-colors data-[disabled]:pointer-events-none", "bg-[var(--dropdown-menu-default-bg)] text-[var(--dropdown-menu-default-text)]", "active:opacity-75", "focus:!bg-[var(--dropdown-menu-hover-bg)] focus:!text-[var(--dropdown-menu-hover-text)]", "data-[state='checked']:bg-[var(--dropdown-menu-selected-bg)] data-[state='checked']:text-[var(--dropdown-menu-selected-text)]", "data-[disabled]:!bg-[var(--dropdown-menu-disabled-bg)] data-[disabled]:!text-[var(--dropdown-menu-disabled-text)]", className), checked: checked, disabled: disabled }, props, { children: [_jsx("span", { className: cn("shrink-0 size-4 rounded-[2px] border flex items-center justify-center transition-all overflow-hidden", checked &&
51
+ !disabled &&
52
+ "bg-[var(--dropdown-menu-checkbox-checked-bg)] border-[var(--dropdown-menu-checkbox-checked-bg)]", checked &&
53
+ disabled &&
54
+ "bg-[var(--dropdown-menu-checkbox-disabled-checked-bg)] border-transparent", !checked &&
55
+ disabled &&
56
+ "border-[var(--dropdown-menu-checkbox-disabled-border)]", !checked &&
57
+ !disabled &&
58
+ "border-[var(--dropdown-menu-checkbox-border)]"), children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(Icon, { type: "heroicons", name: "check", className: "size-3 text-[var(--dropdown-menu-checkbox-checked-icon)]" }) }) }), children] })));
51
59
  });
52
60
  DropdownMenuCheckboxItem.displayName =
53
61
  DropdownMenuPrimitive.CheckboxItem.displayName;
54
62
  const DropdownMenuRadioItem = React.forwardRef((_a, ref) => {
55
- var { className, children } = _a, props = __rest(_a, ["className", "children"]);
56
- return (_jsxs(DropdownMenuPrimitive.RadioItem, Object.assign({ ref: ref, className: cn("relative flex gap-3 cursor-pointer select-none box-border items-center py-4 pl-9 pr-xxl typography-subtitle4 outline-none transition-colors data-[disabled]:pointer-events-none", "bg-[var(--dropdown-menu-default-bg)] text-[var(--dropdown-menu-default-text)]", "active:opacity-75", "focus:!bg-[var(--dropdown-menu-hover-bg)] focus:!text-[var(--dropdown-menu-hover-text)]", "data-[state='checked']:bg-[var(--dropdown-menu-selected-bg)] data-[state='checked']:text-[var(--dropdown-menu-selected-text)] data-[state='checked']:typography-subtitle5", "data-[disabled]:!bg-[var(--dropdown-menu-disabled-bg)] data-[disabled]:!text-[var(--dropdown-menu-disabled-text)]", className) }, props, { children: [_jsx("span", { className: "absolute left-4 flex items-center justify-center", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { children: _jsx(Icon, { type: "heroicons", name: "circle", className: "h-2 w-2 fill-current" }) }) }), children] })));
63
+ var { className, children, disabled, icon } = _a, props = __rest(_a, ["className", "children", "disabled", "icon"]);
64
+ const hasIconSlot = !!icon;
65
+ return (_jsxs(DropdownMenuPrimitive.RadioItem, Object.assign({ ref: ref, className: cn("relative flex cursor-pointer select-none box-border items-center py-4 pl-4 pr-8 typography-subtitle4 outline-none transition-colors data-[disabled]:pointer-events-none", "bg-[var(--dropdown-menu-default-bg)] text-[var(--dropdown-menu-default-text)]", "active:opacity-75", "focus:!bg-[var(--dropdown-menu-hover-bg)] focus:!text-[var(--dropdown-menu-hover-text)]", "data-[state='checked']:bg-[var(--dropdown-menu-selected-bg)] data-[state='checked']:text-[var(--dropdown-menu-selected-text)]", "data-[disabled]:!bg-[var(--dropdown-menu-disabled-bg)] data-[disabled]:!text-[var(--dropdown-menu-radio-disabled-text)]", "data-[state='checked']:data-[disabled]:!text-[var(--dropdown-menu-radio-selected-disabled-text)]", className, hasIconSlot ? "gap-4" : "gap-1") }, props, { disabled: disabled, children: [_jsxs("div", { className: "flex shrink-0 flex-row gap-1", children: [_jsx("span", { className: "size-4", children: _jsx(DropdownMenuPrimitive.ItemIndicator, { className: "shrink-0", children: _jsx(Icon, { type: "heroicons", name: "check", className: cn("size-4", disabled
66
+ ? "text-[var(--dropdown-menu-radio-selected-disabled-text)]"
67
+ : "text-[var(--dropdown-menu-selected-text)]") }) }) }), icon] }), children] })));
57
68
  });
58
69
  DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
59
70
  const DropdownMenuLabel = React.forwardRef((_a, ref) => {
60
71
  var { className, inset } = _a, props = __rest(_a, ["className", "inset"]);
61
- return (_jsx(DropdownMenuPrimitive.Label, Object.assign({ ref: ref, className: cn("px-3 py-2 typography-small4 text-text-g-contrast-medium", inset && "pl-8", className) }, props)));
72
+ return (_jsx(DropdownMenuPrimitive.Label, Object.assign({ ref: ref, className: cn("px-3 py-2 typography-small4 text-text-g-contrast-high", inset && "pl-8", className) }, props)));
62
73
  });
63
74
  DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
64
75
  const DropdownMenuSeparator = React.forwardRef((_a, ref) => {
65
76
  var { className } = _a, props = __rest(_a, ["className"]);
66
- return (_jsx(DropdownMenuPrimitive.Separator, Object.assign({ ref: ref, className: cn("-mx-2 my-2 h-px bg-[var(--dropdown-menu-seperator-bg)]", className) }, props)));
77
+ return (_jsx(DropdownMenuPrimitive.Separator, Object.assign({ ref: ref, className: cn("h-px bg-[var(--dropdown-menu-seperator-bg)]", className) }, props)));
67
78
  });
68
79
  DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
69
80
  const DropdownMenuShortcut = (_a) => {