@rovula/ui 0.0.78 → 0.0.79

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 (42) hide show
  1. package/dist/cjs/bundle.css +15 -3
  2. package/dist/cjs/bundle.js +3 -3
  3. package/dist/cjs/bundle.js.map +1 -1
  4. package/dist/cjs/types/components/Dropdown/Dropdown.d.ts +3 -0
  5. package/dist/cjs/types/components/Dropdown/Dropdown.stories.d.ts +5 -1
  6. package/dist/cjs/types/components/Menu/Menu.d.ts +65 -0
  7. package/dist/cjs/types/components/Menu/Menu.stories.d.ts +31 -0
  8. package/dist/cjs/types/components/Menu/helpers.d.ts +19 -0
  9. package/dist/cjs/types/components/Menu/index.d.ts +4 -0
  10. package/dist/cjs/types/components/Search/Search.d.ts +46 -3
  11. package/dist/cjs/types/components/Search/Search.stories.d.ts +46 -27
  12. package/dist/cjs/types/index.d.ts +1 -0
  13. package/dist/components/Dropdown/Dropdown.js +41 -19
  14. package/dist/components/Dropdown/Dropdown.stories.js +13 -0
  15. package/dist/components/Menu/Menu.js +64 -0
  16. package/dist/components/Menu/Menu.stories.js +406 -0
  17. package/dist/components/Menu/helpers.js +28 -0
  18. package/dist/components/Menu/index.js +3 -0
  19. package/dist/esm/bundle.css +15 -3
  20. package/dist/esm/bundle.js +3 -3
  21. package/dist/esm/bundle.js.map +1 -1
  22. package/dist/esm/types/components/Dropdown/Dropdown.d.ts +3 -0
  23. package/dist/esm/types/components/Dropdown/Dropdown.stories.d.ts +5 -1
  24. package/dist/esm/types/components/Menu/Menu.d.ts +65 -0
  25. package/dist/esm/types/components/Menu/Menu.stories.d.ts +31 -0
  26. package/dist/esm/types/components/Menu/helpers.d.ts +19 -0
  27. package/dist/esm/types/components/Menu/index.d.ts +4 -0
  28. package/dist/esm/types/components/Search/Search.d.ts +46 -3
  29. package/dist/esm/types/components/Search/Search.stories.d.ts +46 -27
  30. package/dist/esm/types/index.d.ts +1 -0
  31. package/dist/index.d.ts +111 -3
  32. package/dist/index.js +1 -0
  33. package/dist/src/theme/global.css +20 -4
  34. package/package.json +1 -1
  35. package/src/components/Dropdown/Dropdown.stories.tsx +31 -0
  36. package/src/components/Dropdown/Dropdown.tsx +73 -54
  37. package/src/components/Menu/Menu.stories.tsx +586 -0
  38. package/src/components/Menu/Menu.tsx +235 -0
  39. package/src/components/Menu/helpers.ts +45 -0
  40. package/src/components/Menu/index.ts +7 -0
  41. package/src/components/Search/Search.tsx +24 -11
  42. package/src/index.ts +1 -0
@@ -1,5 +1,6 @@
1
1
  import React, { CSSProperties, ReactNode } from "react";
2
2
  import { InputProps } from "../TextInput/TextInput";
3
+ import { MenuOption } from "../Menu/Menu";
3
4
  type RenderLabelCallbackArg = {
4
5
  value: string;
5
6
  label: string;
@@ -17,6 +18,7 @@ export type DropdownProps = {
17
18
  size?: "sm" | "md" | "lg";
18
19
  rounded?: "none" | "normal" | "full";
19
20
  variant?: "flat" | "outline" | "underline";
21
+ defaultMenuItemType?: MenuOption["type"];
20
22
  helperText?: string;
21
23
  errorMessage?: string;
22
24
  filterMode?: boolean;
@@ -47,6 +49,7 @@ declare const Dropdown: React.ForwardRefExoticComponent<{
47
49
  size?: "sm" | "md" | "lg";
48
50
  rounded?: "none" | "normal" | "full";
49
51
  variant?: "flat" | "outline" | "underline";
52
+ defaultMenuItemType?: MenuOption["type"];
50
53
  helperText?: string;
51
54
  errorMessage?: string;
52
55
  filterMode?: boolean;
@@ -1,5 +1,6 @@
1
1
  import React from "react";
2
- import { Options } from "./Dropdown";
2
+ import type { StoryObj } from "@storybook/react";
3
+ import Dropdown, { Options } from "./Dropdown";
3
4
  declare const meta: {
4
5
  title: string;
5
6
  component: React.ForwardRefExoticComponent<{
@@ -8,6 +9,7 @@ declare const meta: {
8
9
  size?: "sm" | "md" | "lg";
9
10
  rounded?: "none" | "normal" | "full";
10
11
  variant?: "flat" | "outline" | "underline";
12
+ defaultMenuItemType?: import("../Menu/Menu").MenuOption["type"];
11
13
  helperText?: string;
12
14
  errorMessage?: string;
13
15
  filterMode?: boolean;
@@ -42,6 +44,7 @@ declare const meta: {
42
44
  size?: "sm" | "md" | "lg" | undefined;
43
45
  rounded?: "none" | "normal" | "full" | undefined;
44
46
  variant?: "flat" | "outline" | "underline" | undefined;
47
+ defaultMenuItemType?: "checkbox" | "radio" | "default" | undefined;
45
48
  helperText?: string | undefined;
46
49
  errorMessage?: string | undefined;
47
50
  filterMode?: boolean | undefined;
@@ -405,3 +408,4 @@ export declare const CustomOption: {
405
408
  };
406
409
  render: (args: {}) => import("react/jsx-runtime").JSX.Element;
407
410
  };
411
+ export declare const WithIcons: StoryObj<typeof Dropdown>;
@@ -0,0 +1,65 @@
1
+ import React, { CSSProperties, ReactNode } from "react";
2
+ export type MenuOption = {
3
+ value: string;
4
+ label: ReactNode;
5
+ /**
6
+ * Visual type - กำหนดว่าจะแสดง icon อะไร
7
+ * - "default": ไม่มี icon (แค่ highlight background)
8
+ * - "checkbox": แสดง ✓ icon
9
+ * - "radio": แสดง ● icon
10
+ */
11
+ type?: "default" | "checkbox" | "radio";
12
+ icon?: ReactNode;
13
+ disabled?: boolean;
14
+ danger?: boolean;
15
+ checked?: boolean;
16
+ onClick?: () => void;
17
+ };
18
+ export type MenuItemType = {
19
+ type: "item";
20
+ item: MenuOption;
21
+ } | {
22
+ type: "separator";
23
+ } | {
24
+ type: "label";
25
+ label: string;
26
+ } | {
27
+ type: "custom";
28
+ render: () => ReactNode;
29
+ };
30
+ export type MenuProps = {
31
+ items: MenuItemType[];
32
+ /**
33
+ * Selected values - ใช้กับ type="item"
34
+ */
35
+ selectedValues?: string[];
36
+ /**
37
+ * Callback เมื่อเลือก item
38
+ * - ถ้า item.type="checkbox" → toggle checked state
39
+ * - ถ้า item.type="radio" → single select (clear others)
40
+ * - ถ้า item.type="default" หรือไม่ระบุ → ตาม selectedValues
41
+ */
42
+ onSelect?: (value: string, item: MenuOption) => void;
43
+ className?: string;
44
+ style?: CSSProperties;
45
+ isAbove?: boolean;
46
+ };
47
+ export declare const Menu: React.ForwardRefExoticComponent<MenuProps & React.RefAttributes<HTMLDivElement>>;
48
+ type MenuItemProps = {
49
+ option: MenuOption;
50
+ visualType: "default" | "checkbox" | "radio";
51
+ isChecked: boolean;
52
+ onSelect: () => void;
53
+ className?: string;
54
+ };
55
+ export declare const MenuItem: React.ForwardRefExoticComponent<MenuItemProps & React.RefAttributes<HTMLDivElement>>;
56
+ type MenuSeparatorProps = {
57
+ className?: string;
58
+ };
59
+ export declare const MenuSeparator: React.ForwardRefExoticComponent<MenuSeparatorProps & React.RefAttributes<HTMLDivElement>>;
60
+ type MenuLabelProps = {
61
+ children: ReactNode;
62
+ className?: string;
63
+ };
64
+ export declare const MenuLabel: React.ForwardRefExoticComponent<MenuLabelProps & React.RefAttributes<HTMLDivElement>>;
65
+ export default Menu;
@@ -0,0 +1,31 @@
1
+ import React from "react";
2
+ import type { StoryObj } from "@storybook/react";
3
+ import { Menu, MenuItemType } from "./Menu";
4
+ declare const meta: {
5
+ title: string;
6
+ component: React.ForwardRefExoticComponent<import("./Menu").MenuProps & React.RefAttributes<HTMLDivElement>>;
7
+ tags: string[];
8
+ parameters: {
9
+ layout: string;
10
+ };
11
+ decorators: ((Story: import("@storybook/csf").PartialStoryFn<import("@storybook/react").ReactRenderer, {
12
+ items: MenuItemType[];
13
+ selectedValues?: string[] | undefined;
14
+ onSelect?: ((value: string, item: import("./Menu").MenuOption) => void) | undefined;
15
+ className?: string | undefined;
16
+ style?: React.CSSProperties | undefined;
17
+ isAbove?: boolean | undefined;
18
+ ref?: React.LegacyRef<HTMLDivElement> | undefined;
19
+ key?: React.Key | null | undefined;
20
+ }>) => import("react/jsx-runtime").JSX.Element)[];
21
+ };
22
+ export default meta;
23
+ export declare const Basic: StoryObj<typeof Menu>;
24
+ export declare const WithIcons: StoryObj<typeof Menu>;
25
+ export declare const WithCheckbox: StoryObj<typeof Menu>;
26
+ export declare const WithRadio: StoryObj<typeof Menu>;
27
+ export declare const ComplexMenu: StoryObj<typeof Menu>;
28
+ export declare const WithDropdownTrigger: StoryObj<typeof Menu>;
29
+ export declare const CustomItems: StoryObj<typeof Menu>;
30
+ export declare const DropdownPattern: StoryObj<typeof Menu>;
31
+ export declare const MultiSelectPattern: StoryObj<typeof Menu>;
@@ -0,0 +1,19 @@
1
+ import { MenuItemType } from "./Menu";
2
+ /**
3
+ * Helper function to convert simple options to MenuItemType
4
+ * Useful for integrating with Dropdown component
5
+ */
6
+ export declare function optionsToMenuItems(options: Array<{
7
+ value: string;
8
+ label: string | React.ReactNode;
9
+ disabled?: boolean;
10
+ renderLabel?: any;
11
+ }>): MenuItemType[];
12
+ /**
13
+ * Helper to add separator between menu items
14
+ */
15
+ export declare function withSeparator(items: MenuItemType[], atIndex: number): MenuItemType[];
16
+ /**
17
+ * Helper to add label/header to menu items
18
+ */
19
+ export declare function withLabel(label: string, items: MenuItemType[]): MenuItemType[];
@@ -0,0 +1,4 @@
1
+ export { Menu, MenuItem, MenuSeparator, MenuLabel } from "./Menu";
2
+ export type { MenuOption, MenuItemType, MenuProps } from "./Menu";
3
+ export { optionsToMenuItems, withSeparator, withLabel } from "./helpers";
4
+ export { default } from "./Menu";
@@ -1,5 +1,48 @@
1
1
  import React from "react";
2
- import { DropdownProps } from "../Dropdown/Dropdown";
3
- export type SearchProps = Omit<DropdownProps, "isFloatingLabel" | "keepCloseIconOnValue" | "hasClearIcon" | "hasSearchIcon" | "endIcon" | "filterMode" | "isFloatingLabel">;
4
- declare const Search: React.ForwardRefExoticComponent<SearchProps & React.RefAttributes<HTMLInputElement>>;
2
+ import { Options } from "../Dropdown/Dropdown";
3
+ import { InputProps } from "../TextInput/TextInput";
4
+ export type SearchProps = {
5
+ id?: string;
6
+ label?: string;
7
+ size?: "sm" | "md" | "lg";
8
+ rounded?: "none" | "normal" | "full";
9
+ variant?: "flat" | "outline" | "underline";
10
+ helperText?: string;
11
+ errorMessage?: string;
12
+ fullwidth?: boolean;
13
+ disabled?: boolean;
14
+ error?: boolean;
15
+ required?: boolean;
16
+ modal?: boolean;
17
+ className?: string;
18
+ optionContainerClassName?: string;
19
+ optionItemClassName?: string;
20
+ optionNotFoundItemClassName?: string;
21
+ options: Options[];
22
+ value?: Options;
23
+ onChangeText?: InputProps["onChange"];
24
+ onSelect?: (value: Options) => void;
25
+ } & Omit<InputProps, "value" | "onSelect">;
26
+ declare const Search: React.ForwardRefExoticComponent<{
27
+ id?: string;
28
+ label?: string;
29
+ size?: "sm" | "md" | "lg";
30
+ rounded?: "none" | "normal" | "full";
31
+ variant?: "flat" | "outline" | "underline";
32
+ helperText?: string;
33
+ errorMessage?: string;
34
+ fullwidth?: boolean;
35
+ disabled?: boolean;
36
+ error?: boolean;
37
+ required?: boolean;
38
+ modal?: boolean;
39
+ className?: string;
40
+ optionContainerClassName?: string;
41
+ optionItemClassName?: string;
42
+ optionNotFoundItemClassName?: string;
43
+ options: Options[];
44
+ value?: Options;
45
+ onChangeText?: InputProps["onChange"];
46
+ onSelect?: (value: Options) => void;
47
+ } & Omit<InputProps, "onSelect" | "value"> & React.RefAttributes<HTMLInputElement>>;
5
48
  export { Search };
@@ -2,18 +2,56 @@ import React from "react";
2
2
  import { Options } from "../Dropdown/Dropdown";
3
3
  declare const meta: {
4
4
  title: string;
5
- component: React.ForwardRefExoticComponent<import("./Search").SearchProps & React.RefAttributes<HTMLInputElement>>;
5
+ component: React.ForwardRefExoticComponent<{
6
+ id?: string;
7
+ label?: string;
8
+ size?: "sm" | "md" | "lg";
9
+ rounded?: "none" | "normal" | "full";
10
+ variant?: "flat" | "outline" | "underline";
11
+ helperText?: string;
12
+ errorMessage?: string;
13
+ fullwidth?: boolean;
14
+ disabled?: boolean;
15
+ error?: boolean;
16
+ required?: boolean;
17
+ modal?: boolean;
18
+ className?: string;
19
+ optionContainerClassName?: string;
20
+ optionItemClassName?: string;
21
+ optionNotFoundItemClassName?: string;
22
+ options: Options[];
23
+ value?: Options;
24
+ onChangeText?: import("../..").InputProps["onChange"];
25
+ onSelect?: (value: Options) => void;
26
+ } & Omit<import("../..").InputProps, "onSelect" | "value"> & React.RefAttributes<HTMLInputElement>>;
6
27
  tags: string[];
7
28
  parameters: {
8
29
  layout: string;
9
30
  };
10
31
  decorators: ((Story: import("@storybook/csf").PartialStoryFn<import("@storybook/react").ReactRenderer, {
32
+ id?: string | undefined;
33
+ label?: string | undefined;
34
+ size?: "sm" | "md" | "lg" | undefined;
35
+ rounded?: "none" | "normal" | "full" | undefined;
11
36
  variant?: "flat" | "outline" | "underline" | undefined;
12
- suppressHydrationWarning?: boolean | undefined | undefined;
37
+ helperText?: string | undefined;
38
+ errorMessage?: string | undefined;
39
+ fullwidth?: boolean | undefined;
40
+ disabled?: boolean | undefined;
41
+ error?: boolean | undefined;
42
+ required?: boolean | undefined;
43
+ modal?: boolean | undefined;
13
44
  className?: string | undefined;
45
+ optionContainerClassName?: string | undefined;
46
+ optionItemClassName?: string | undefined;
47
+ optionNotFoundItemClassName?: string | undefined;
48
+ options: Options[];
49
+ value?: Options | undefined;
50
+ onChangeText?: React.ChangeEventHandler<HTMLInputElement> | undefined;
51
+ onSelect?: ((value: Options) => void) | undefined;
52
+ suppressHydrationWarning?: boolean | undefined | undefined;
14
53
  color?: string | undefined | undefined;
15
54
  height?: number | string | undefined | undefined;
16
- id?: string | undefined;
17
55
  lang?: string | undefined | undefined;
18
56
  max?: number | string | undefined | undefined;
19
57
  min?: number | string | undefined | undefined;
@@ -200,7 +238,6 @@ declare const meta: {
200
238
  onMouseOverCapture?: React.MouseEventHandler<HTMLInputElement> | undefined;
201
239
  onMouseUp?: React.MouseEventHandler<HTMLInputElement> | undefined;
202
240
  onMouseUpCapture?: React.MouseEventHandler<HTMLInputElement> | undefined;
203
- onSelect?: ((value: Options) => void) | undefined;
204
241
  onSelectCapture?: React.ReactEventHandler<HTMLInputElement> | undefined;
205
242
  onTouchCancel?: React.TouchEventHandler<HTMLInputElement> | undefined;
206
243
  onTouchCancelCapture?: React.TouchEventHandler<HTMLInputElement> | undefined;
@@ -243,18 +280,14 @@ declare const meta: {
243
280
  form?: string | undefined | undefined;
244
281
  list?: string | undefined | undefined;
245
282
  step?: number | string | undefined | undefined;
246
- error?: boolean | undefined;
247
- size?: "sm" | "md" | "lg" | undefined;
248
- disabled?: boolean | undefined;
249
- fullwidth?: boolean | undefined;
250
283
  title?: string | undefined | undefined;
251
284
  startIcon?: React.ReactNode;
285
+ endIcon?: React.ReactNode;
252
286
  formAction?: string | undefined;
253
287
  formEncType?: string | undefined | undefined;
254
288
  formMethod?: string | undefined | undefined;
255
289
  formNoValidate?: boolean | undefined | undefined;
256
290
  formTarget?: string | undefined | undefined;
257
- value?: Options | undefined;
258
291
  defaultChecked?: boolean | undefined | undefined;
259
292
  defaultValue?: string | number | readonly string[] | undefined;
260
293
  suppressContentEditableWarning?: boolean | undefined | undefined;
@@ -295,7 +328,9 @@ declare const meta: {
295
328
  unselectable?: "on" | "off" | undefined | undefined;
296
329
  inputMode?: "none" | "text" | "tel" | "url" | "email" | "numeric" | "decimal" | "search" | undefined | undefined;
297
330
  is?: string | undefined | undefined;
298
- rounded?: "none" | "normal" | "full" | undefined;
331
+ hasClearIcon?: boolean | undefined;
332
+ hasSearchIcon?: boolean | undefined;
333
+ isFloatingLabel?: boolean | undefined;
299
334
  accept?: string | undefined | undefined;
300
335
  alt?: string | undefined | undefined;
301
336
  autoComplete?: React.HTMLInputAutoCompleteAttribute | undefined;
@@ -307,12 +342,9 @@ declare const meta: {
307
342
  pattern?: string | undefined | undefined;
308
343
  placeholder?: string | undefined | undefined;
309
344
  readOnly?: boolean | undefined | undefined;
310
- required?: boolean | undefined;
311
345
  src?: string | undefined | undefined;
312
- label?: string | undefined;
313
346
  iconMode?: "flat" | "solid" | undefined;
314
- helperText?: string | undefined;
315
- errorMessage?: string | undefined;
347
+ keepCloseIconOnValue?: boolean | undefined;
316
348
  labelClassName?: string | undefined;
317
349
  classes?: {
318
350
  iconWrapper?: string;
@@ -325,19 +357,6 @@ declare const meta: {
325
357
  onClickEndIcon?: (() => void) | undefined;
326
358
  renderStartIcon?: (() => React.ReactNode) | undefined;
327
359
  renderEndIcon?: (() => React.ReactNode) | undefined;
328
- modal?: boolean | undefined;
329
- optionContainerClassName?: string | undefined;
330
- optionItemClassName?: string | undefined;
331
- optionNotFoundItemClassName?: string | undefined;
332
- options: Options[];
333
- onChangeText?: React.ChangeEventHandler<HTMLInputElement> | undefined;
334
- renderOptions?: ((value: {
335
- optionsFiltered: Options[];
336
- selectedOption: Options | null | undefined;
337
- onClick: (option: Options) => void;
338
- style?: React.CSSProperties;
339
- dropdownRef?: React.RefObject<HTMLUListElement>;
340
- }) => React.ReactNode) | undefined;
341
360
  ref?: React.LegacyRef<HTMLInputElement> | undefined;
342
361
  key?: React.Key | null | undefined;
343
362
  }>) => import("react/jsx-runtime").JSX.Element)[];
@@ -30,6 +30,7 @@ export * from "./components/InputFilter/InputFilter";
30
30
  export * from "./components/Slider/Slider";
31
31
  export * from "./components/Switch/Switch";
32
32
  export * from "./components/DropdownMenu/DropdownMenu";
33
+ export * from "./components/Menu/Menu";
33
34
  export * from "./components/Tooltip/Tooltip";
34
35
  export * from "./components/Tooltip/TooltipSimple";
35
36
  export * from "./components/Toast/Toast";
@@ -10,14 +10,15 @@ var __rest = (this && this.__rest) || function (s, e) {
10
10
  return t;
11
11
  };
12
12
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
- import { Fragment, forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from "react";
13
+ import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, Fragment, } from "react";
14
14
  import * as Portal from "@radix-ui/react-portal";
15
15
  import TextInput from "../TextInput/TextInput";
16
16
  import { customInputVariant, dropdownIconVariant } from "./Dropdown.styles";
17
+ import { Menu } from "../Menu/Menu";
17
18
  import { ChevronDownIcon } from "@heroicons/react/16/solid";
18
19
  import { cn } from "@/utils/cn";
19
20
  const Dropdown = forwardRef((_a, ref) => {
20
- var { id, options = [], value, label, size = "md", rounded = "normal", variant = "outline", helperText, errorMessage, fullwidth = true, disabled = false, error = false, filterMode = false, required = true, modal = false, onChangeText, onSelect, renderOptions: customRenderOptions, optionContainerClassName, optionItemClassName, optionNotFoundItemClassName } = _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"]);
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 } = _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"]);
21
22
  const _id = id || `${label}-select`;
22
23
  const [isFocused, setIsFocused] = useState(false);
23
24
  const [selectedOption, setSelectedOption] = useState(null);
@@ -116,23 +117,44 @@ const Dropdown = forwardRef((_a, ref) => {
116
117
  dropdownRef,
117
118
  });
118
119
  }
119
- return (_jsxs("ul", { className: cn("absolute mt-1 w-full bg-base-popup border border-base-popup text-base-popup-foreground rounded-md shadow-md z-[9999] max-h-60 overflow-y-auto", !usePortal && (isAbove ? "bottom-full mb-1" : "top-full mt-1"), optionContainerClassName), style: dropdownStyles, ref: dropdownRef, children: [optionsFiltered.map((option) => {
120
- if (option.renderLabel) {
121
- return (_jsx(Fragment, { children: option.renderLabel({
122
- value: option.value,
123
- label: option.label,
124
- handleOnClick: () => handleOptionClick(option),
125
- className: cn(`px-4 py-2 hover:bg-primary-hover-bg cursor-pointer`, optionItemClassName, {
126
- "bg-base-popup-highligh": (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.value) === option.value,
127
- }),
128
- }) }, option.value));
129
- }
130
- return (_jsx("li", { onMouseDown: () => {
131
- handleOptionClick(option);
132
- }, className: cn(`px-4 py-2 hover:bg-primary-hover-bg cursor-pointer`, optionItemClassName, {
133
- "bg-base-popup-highligh": (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.value) === option.value,
134
- }), children: option.label }, option.value));
135
- }), optionsFiltered.length === 0 && (_jsx("li", { className: cn("px-4 py-14 text-center text-input-text", optionNotFoundItemClassName), children: "Not found" }))] }));
120
+ // Convert options to MenuItemType
121
+ let finalMenuItems;
122
+ finalMenuItems = optionsFiltered.map((option) => {
123
+ 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-subtitile4 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-subtitile5": (selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.value) === option.value,
132
+ }, optionItemClassName),
133
+ }) }, option.value)),
134
+ };
135
+ }
136
+ return {
137
+ type: "item",
138
+ item: {
139
+ type: defaultMenuItemType,
140
+ value: option.value,
141
+ label: option.label,
142
+ },
143
+ };
144
+ });
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 }));
136
158
  };
137
159
  const handleOnFocus = useCallback((e) => {
138
160
  var _a;
@@ -100,3 +100,16 @@ export const CustomOption = {
100
100
  return (_jsx("div", { className: "flex flex-row gap-4 w-full", children: _jsx(Dropdown, Object.assign({ id: "1", size: "lg", options: options }, args, { onChangeText: onChangeText })) }));
101
101
  },
102
102
  };
103
+ // ==================== Advanced Menu Features ====================
104
+ export const WithIcons = {
105
+ render: () => {
106
+ const [selectedValue, setSelectedValue] = useState();
107
+ const optionsWithIcons = [
108
+ { value: "apple", label: "🍎 Apple" },
109
+ { value: "banana", label: "🍌 Banana" },
110
+ { value: "carrot", label: "🥕 Carrot" },
111
+ { value: "broccoli", label: "🥦 Broccoli" },
112
+ ];
113
+ 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
+ };
@@ -0,0 +1,64 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { forwardRef, } from "react";
3
+ import { cn } from "@/utils/cn";
4
+ import Icon from "../Icon/Icon";
5
+ // ==================== Menu Container ====================
6
+ export const Menu = forwardRef(({ items, selectedValues = [], onSelect, className, style, isAbove = false }, ref) => {
7
+ return (_jsx("div", { ref: ref, className: cn("z-50 min-w-[154px] overflow-hidden rounded-md bg-base-popup text-base-popup-foreground", "border border-base-popup", className), style: Object.assign({ boxShadow: "var(--dropdown-menu-shadow)" }, style), children: items.map((item, index) => {
8
+ var _a;
9
+ if (item.type === "separator") {
10
+ return _jsx(MenuSeparator, {}, `separator-${index}`);
11
+ }
12
+ if (item.type === "label") {
13
+ return _jsx(MenuLabel, { children: item.label }, `label-${index}`);
14
+ }
15
+ if (item.type === "custom") {
16
+ return (_jsx(React.Fragment, { children: item.render() }, `custom-${index}`));
17
+ }
18
+ const itemOption = item.item;
19
+ const visualType = itemOption.type || "default";
20
+ // Determine checked/selected state
21
+ let isChecked = false;
22
+ if (visualType === "checkbox" || visualType === "radio") {
23
+ isChecked =
24
+ (_a = itemOption.checked) !== null && _a !== void 0 ? _a : selectedValues.includes(itemOption.value);
25
+ }
26
+ else {
27
+ isChecked = selectedValues.includes(itemOption.value);
28
+ }
29
+ return (_jsx(MenuItem, { option: itemOption, visualType: visualType, isChecked: isChecked, onSelect: () => {
30
+ var _a;
31
+ onSelect === null || onSelect === void 0 ? void 0 : onSelect(itemOption.value, itemOption);
32
+ (_a = itemOption.onClick) === null || _a === void 0 ? void 0 : _a.call(itemOption);
33
+ } }, itemOption.value));
34
+ }) }));
35
+ });
36
+ Menu.displayName = "Menu";
37
+ export const MenuItem = forwardRef(({ option, visualType, isChecked, onSelect, className }, ref) => {
38
+ // Render indicator based on visual type
39
+ const renderIndicator = () => {
40
+ if (visualType === "checkbox" && isChecked) {
41
+ return (_jsx("span", { className: "absolute left-4 flex items-center justify-center", children: _jsx(Icon, { type: "heroicons", name: "check", className: "size-4" }) }));
42
+ }
43
+ if (visualType === "radio" && isChecked) {
44
+ return (_jsx("span", { className: "absolute left-4 flex items-center justify-center", children: _jsx(Icon, { type: "heroicons", name: "circle", className: "h-2 w-2 fill-current" }) }));
45
+ }
46
+ return null;
47
+ };
48
+ return (_jsxs("div", { ref: ref, className: cn("relative flex gap-3 cursor-pointer select-none box-border items-center py-4 pl-9 pr-4 typography-subtitile4 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)]", {
49
+ "bg-[var(--dropdown-menu-selected-bg)] text-[var(--dropdown-menu-selected-text)] typography-subtitile5": isChecked,
50
+ "pointer-events-none opacity-50": option.disabled,
51
+ "text-red-500": option.danger,
52
+ }, className), onClick: option.disabled ? undefined : onSelect, children: [renderIndicator(), option.icon && _jsx("span", { className: "flex-shrink-0", children: option.icon }), option.label] }));
53
+ });
54
+ MenuItem.displayName = "MenuItem";
55
+ export const MenuSeparator = forwardRef(({ className }, ref) => {
56
+ return (_jsx("div", { ref: ref, className: cn("-mx-2 my-2 h-px bg-[var(--dropdown-menu-seperator-bg)]", className) }));
57
+ });
58
+ MenuSeparator.displayName = "MenuSeparator";
59
+ export const MenuLabel = forwardRef(({ children, className }, ref) => {
60
+ return (_jsx("div", { ref: ref, className: cn("px-3 py-2 typography-small4 text-text-grey-medium", className), children: children }));
61
+ });
62
+ MenuLabel.displayName = "MenuLabel";
63
+ // ==================== Exports ====================
64
+ export default Menu;