mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
feat(dropdown): initial structure
This commit is contained in:
parent
1a88d1f3ee
commit
a1a1577df3
@ -105,11 +105,11 @@ After cloning the repository, execute the following commands in the root folder:
|
||||
1. Install dependencies
|
||||
|
||||
```bash
|
||||
pnpm i
|
||||
pnpm i --hoist
|
||||
|
||||
#or
|
||||
|
||||
pnpm install
|
||||
pnpm install --hoist
|
||||
```
|
||||
|
||||
We use [Turbo Repo](https://turborepo.org/) for the project management.
|
||||
|
||||
24
packages/components/dropdown/README.md
Normal file
24
packages/components/dropdown/README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# @nextui-org/dropdown
|
||||
|
||||
A Quick description of the component
|
||||
|
||||
> This is an internal utility, not intended for public usage.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
yarn add @nextui-org/dropdown
|
||||
# or
|
||||
npm i @nextui-org/dropdown
|
||||
```
|
||||
|
||||
## Contribution
|
||||
|
||||
Yes please! See the
|
||||
[contributing guidelines](https://github.com/nextui-org/nextui/blob/master/CONTRIBUTING.md)
|
||||
for details.
|
||||
|
||||
## Licence
|
||||
|
||||
This project is licensed under the terms of the
|
||||
[MIT license](https://github.com/nextui-org/nextui/blob/master/LICENSE).
|
||||
19
packages/components/dropdown/__tests__/dropdown.test.tsx
Normal file
19
packages/components/dropdown/__tests__/dropdown.test.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import * as React from "react";
|
||||
import {render} from "@testing-library/react";
|
||||
|
||||
import {Dropdown} from "../src";
|
||||
|
||||
describe("Dropdown", () => {
|
||||
it("should render correctly", () => {
|
||||
const wrapper = render(<Dropdown />);
|
||||
|
||||
expect(() => wrapper.unmount()).not.toThrow();
|
||||
});
|
||||
|
||||
it("ref should be forwarded", () => {
|
||||
const ref = React.createRef<HTMLDivElement>();
|
||||
|
||||
render(<Dropdown ref={ref} />);
|
||||
expect(ref.current).not.toBeNull();
|
||||
});
|
||||
});
|
||||
69
packages/components/dropdown/package.json
Normal file
69
packages/components/dropdown/package.json
Normal file
@ -0,0 +1,69 @@
|
||||
{
|
||||
"name": "@nextui-org/dropdown",
|
||||
"version": "2.0.0-beta.1",
|
||||
"description": "A dropdown displays a list of actions or options that a user can choose.",
|
||||
"keywords": [
|
||||
"dropdown"
|
||||
],
|
||||
"author": "Junior Garcia <jrgarciadev@gmail.com>",
|
||||
"homepage": "https://nextui.org",
|
||||
"license": "MIT",
|
||||
"main": "src/index.ts",
|
||||
"sideEffects": false,
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/nextui-org/nextui.git",
|
||||
"directory": "packages/components/dropdown"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextui-org/nextui/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup src --dts",
|
||||
"build:fast": "tsup src",
|
||||
"dev": "yarn build:fast -- --watch",
|
||||
"clean": "rimraf dist .turbo",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"prepack": "clean-package",
|
||||
"postpack": "clean-package restore"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nextui-org/aria-utils": "workspace:*",
|
||||
"@nextui-org/dom-utils": "workspace:*",
|
||||
"@nextui-org/popover": "workspace:*",
|
||||
"@nextui-org/shared-utils": "workspace:*",
|
||||
"@nextui-org/system": "workspace:*",
|
||||
"@nextui-org/theme": "workspace:*",
|
||||
"@react-aria/menu": "^3.9.0",
|
||||
"@react-aria/utils": "^3.16.0",
|
||||
"@react-stately/collections": "^3.6.0",
|
||||
"@react-stately/menu": "^3.5.1",
|
||||
"@react-stately/tree": "^3.6.0",
|
||||
"framer-motion": "^10.11.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nextui-org/button": "workspace:*",
|
||||
"@react-types/menu": "^3.9.0",
|
||||
"@react-types/shared": "^3.18.0",
|
||||
"clean-package": "2.2.0",
|
||||
"react": "^18.0.0"
|
||||
},
|
||||
"clean-package": "../../../clean-package.config.json",
|
||||
"tsup": {
|
||||
"clean": true,
|
||||
"target": "es2019",
|
||||
"format": [
|
||||
"cjs",
|
||||
"esm"
|
||||
]
|
||||
}
|
||||
}
|
||||
51
packages/components/dropdown/src/base/dropdown-item-base.tsx
Normal file
51
packages/components/dropdown/src/base/dropdown-item-base.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import type {DropdownItemVariantProps, DropdownItemSlots, SlotsToClasses} from "@nextui-org/theme";
|
||||
|
||||
import {BaseItem, ItemProps} from "@nextui-org/aria-utils";
|
||||
import {ReactNode} from "react";
|
||||
|
||||
export interface Props<T extends object = {}>
|
||||
extends Omit<ItemProps<"button", T>, "children" | "title"> {
|
||||
/**
|
||||
* The content of the component.
|
||||
*/
|
||||
children?: ReactNode | null;
|
||||
/**
|
||||
* The dropdown item title.
|
||||
*/
|
||||
title?: ReactNode | string;
|
||||
/**
|
||||
* The accordion item subtitle.
|
||||
*/
|
||||
description?: ReactNode | string;
|
||||
/**
|
||||
* The dropdown item start content.
|
||||
*/
|
||||
startContent?: ReactNode;
|
||||
/**
|
||||
* The dropdown item end content.
|
||||
*/
|
||||
endContent?: ReactNode;
|
||||
/**
|
||||
* Classname or List of classes to change the styles of the element.
|
||||
* if `className` is passed, it will be added to the base slot.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* <DropdownItem styles={{
|
||||
* base:"base-classes",
|
||||
* title:"label-classes",
|
||||
* description:"description-classes",
|
||||
* startContent:"startContent-classes",
|
||||
* endContent:"endContent-classes",
|
||||
* keyboardShortcut:"keyboardShortcut-classes",
|
||||
* }} />
|
||||
* ```
|
||||
*/
|
||||
styles?: SlotsToClasses<DropdownItemSlots>;
|
||||
}
|
||||
|
||||
export type DropdownItemBaseProps<T extends object = {}> = Props<T> & DropdownItemVariantProps;
|
||||
|
||||
const DropdownItemBase = BaseItem as (props: DropdownItemBaseProps) => JSX.Element;
|
||||
|
||||
export default DropdownItemBase;
|
||||
9
packages/components/dropdown/src/dropdown-context.ts
Normal file
9
packages/components/dropdown/src/dropdown-context.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import {createContext} from "@nextui-org/shared-utils";
|
||||
|
||||
import {UseDropdownReturn} from "./use-dropdown";
|
||||
|
||||
export const [DropdownProvider, useDropdownContext] = createContext<UseDropdownReturn>({
|
||||
name: "DropdownContext",
|
||||
errorMessage:
|
||||
"useDropdownContext: `context` is undefined. Seems you forgot to wrap all popover components within `<Dropdown />`",
|
||||
});
|
||||
50
packages/components/dropdown/src/dropdown-menu.tsx
Normal file
50
packages/components/dropdown/src/dropdown-menu.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import {forwardRef} from "@nextui-org/system";
|
||||
import {PopoverContent} from "@nextui-org/popover";
|
||||
import {DOMProps, AriaLabelingProps} from "@react-types/shared";
|
||||
import {useMenu} from "@react-aria/menu";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {AriaMenuProps} from "@react-types/menu";
|
||||
import {useTreeState} from "@react-stately/tree";
|
||||
|
||||
import {useDropdownContext} from "./dropdown-context";
|
||||
|
||||
export interface DropdownMenuProps<T = object>
|
||||
extends AriaMenuProps<T>,
|
||||
DOMProps,
|
||||
AriaLabelingProps {}
|
||||
|
||||
const DropdownMenu = forwardRef<DropdownMenuProps, "button">((props, ref) => {
|
||||
const {getMenuProps} = useDropdownContext();
|
||||
|
||||
const domRef = useDOMRef(ref);
|
||||
|
||||
const state = useTreeState(props);
|
||||
const {menuProps} = useMenu(props, state, domRef);
|
||||
|
||||
return (
|
||||
<PopoverContent>
|
||||
<div {...getMenuProps(menuProps, domRef)}>
|
||||
{[...state.collection].map((item) => {
|
||||
if (item.type === "section") {
|
||||
return <div>Section</div>;
|
||||
}
|
||||
let dropdownItem = (
|
||||
<div>
|
||||
<div>{item.rendered}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (item.wrapper) {
|
||||
dropdownItem = item.wrapper(dropdownItem);
|
||||
}
|
||||
|
||||
return dropdownItem;
|
||||
})}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
);
|
||||
});
|
||||
|
||||
DropdownMenu.displayName = "NextUI.DropdownMenu";
|
||||
|
||||
export default DropdownMenu;
|
||||
24
packages/components/dropdown/src/dropdown-trigger.tsx
Normal file
24
packages/components/dropdown/src/dropdown-trigger.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import {forwardRef} from "@nextui-org/system";
|
||||
import {PopoverTrigger} from "@nextui-org/popover";
|
||||
|
||||
import {useDropdownContext} from "./dropdown-context";
|
||||
|
||||
export interface DropdownTriggerProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* DropdownTrigger opens the popover's content. It must be an interactive element
|
||||
* such as `button` or `a`.
|
||||
*/
|
||||
const DropdownTrigger = forwardRef<DropdownTriggerProps, "button">((props, _) => {
|
||||
const {getMenuTriggerProps} = useDropdownContext();
|
||||
|
||||
const {children, ...otherProps} = props;
|
||||
|
||||
return <PopoverTrigger {...getMenuTriggerProps(otherProps)}>{children}</PopoverTrigger>;
|
||||
});
|
||||
|
||||
DropdownTrigger.displayName = "NextUI.DropdownTrigger";
|
||||
|
||||
export default DropdownTrigger;
|
||||
34
packages/components/dropdown/src/dropdown.tsx
Normal file
34
packages/components/dropdown/src/dropdown.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import React, {ReactNode} from "react";
|
||||
import {Popover} from "@nextui-org/popover";
|
||||
|
||||
import {DropdownProvider} from "./dropdown-context";
|
||||
import {UseDropdownProps, useDropdown} from "./use-dropdown";
|
||||
|
||||
export interface DropdownProps extends UseDropdownProps {
|
||||
/**
|
||||
* The content of the dropdown. It is usually the `Dropdown.Trigger`,
|
||||
* and `Dropdown.Menu`
|
||||
*/
|
||||
children: ReactNode[];
|
||||
}
|
||||
|
||||
const Dropdown = (props: DropdownProps) => {
|
||||
const {children, ...otherProps} = props;
|
||||
|
||||
const context = useDropdown(otherProps);
|
||||
|
||||
const [menuTrigger, menu] = React.Children.toArray(children);
|
||||
|
||||
return (
|
||||
<DropdownProvider value={context}>
|
||||
<Popover {...context.getPopoverProps()}>
|
||||
{menuTrigger}
|
||||
{menu}
|
||||
</Popover>
|
||||
</DropdownProvider>
|
||||
);
|
||||
};
|
||||
|
||||
Dropdown.displayName = "NextUI.Dropdown";
|
||||
|
||||
export default Dropdown;
|
||||
17
packages/components/dropdown/src/index.ts
Normal file
17
packages/components/dropdown/src/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import {Section as DropdownSection} from "@react-stately/collections";
|
||||
|
||||
import Dropdown from "./dropdown";
|
||||
import DropdownTrigger from "./dropdown-trigger";
|
||||
import DropdownMenu from "./dropdown-menu";
|
||||
|
||||
// export types
|
||||
export type {DropdownProps} from "./dropdown";
|
||||
export type {DropdownTriggerProps} from "./dropdown-trigger";
|
||||
export type {DropdownMenuProps} from "./dropdown-menu";
|
||||
|
||||
// export hooks
|
||||
export {useDropdown} from "./use-dropdown";
|
||||
|
||||
// export component
|
||||
export {Dropdown, DropdownTrigger, DropdownMenu, DropdownSection};
|
||||
export {default as DropdownItem} from "./base/dropdown-item-base";
|
||||
145
packages/components/dropdown/src/use-dropdown.ts
Normal file
145
packages/components/dropdown/src/use-dropdown.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import type {DropdownVariantProps, SlotsToClasses, DropdownSlots} from "@nextui-org/theme";
|
||||
|
||||
import {Ref, useId} from "react";
|
||||
import {HTMLNextUIProps, mapPropsVariants, PropGetter} from "@nextui-org/system";
|
||||
import {useMenuTriggerState} from "@react-stately/menu";
|
||||
import {MenuTriggerType} from "@react-types/menu";
|
||||
import {useMenuTrigger} from "@react-aria/menu";
|
||||
import {dropdown} from "@nextui-org/theme";
|
||||
import {clsx, mergeRefs, ReactRef} from "@nextui-org/shared-utils";
|
||||
import {PopoverProps} from "@nextui-org/popover";
|
||||
import {useMemo, useRef} from "react";
|
||||
import {mergeProps} from "@react-aria/utils";
|
||||
|
||||
interface Props extends HTMLNextUIProps<"div", Omit<PopoverProps, "children">> {
|
||||
/**
|
||||
* Type of overlay that is opened by the trigger.
|
||||
*/
|
||||
type?: "menu" | "listbox";
|
||||
/**
|
||||
* Ref to the DOM node.
|
||||
*/
|
||||
ref?: ReactRef<HTMLElement | null>;
|
||||
/**
|
||||
* How the menu is triggered.
|
||||
* @default 'press'
|
||||
*/
|
||||
trigger?: MenuTriggerType;
|
||||
/**
|
||||
* Whether menu trigger is disabled.
|
||||
* @default false
|
||||
*/
|
||||
isDisabled?: boolean;
|
||||
/**
|
||||
* Whether the Menu closes when a selection is made.
|
||||
* @default true
|
||||
*/
|
||||
closeOnSelect?: boolean;
|
||||
/**
|
||||
* Classname or List of classes to change the styles of the element.
|
||||
* if `className` is passed, it will be added to the base slot.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* <Dropdown styles={{
|
||||
* trigger: "trigger-classes",
|
||||
* base: "base-classes", // items wrapper
|
||||
* section: "section-classes",
|
||||
* sectionHeading: "sectionHeading-classes",
|
||||
* }} />
|
||||
* ```
|
||||
*/
|
||||
styles?: SlotsToClasses<DropdownSlots>;
|
||||
}
|
||||
|
||||
export type UseDropdownProps = Props & DropdownVariantProps;
|
||||
|
||||
export function useDropdown(originalProps: UseDropdownProps) {
|
||||
const [props, variantProps] = mapPropsVariants(originalProps, dropdown.variantKeys);
|
||||
|
||||
const {
|
||||
as,
|
||||
triggerRef: triggerRefProp,
|
||||
className,
|
||||
isOpen,
|
||||
defaultOpen,
|
||||
onOpenChange,
|
||||
type = "menu",
|
||||
trigger = "press",
|
||||
placement = "bottom",
|
||||
isDisabled = false,
|
||||
closeOnSelect = true,
|
||||
styles,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const Component = as || "div";
|
||||
|
||||
const triggerRef = useRef<HTMLElement>(null);
|
||||
const menuTriggerRef = triggerRefProp || triggerRef;
|
||||
const menuRef = useRef<HTMLUListElement>(null);
|
||||
const popoverRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const triggerId = useId();
|
||||
|
||||
const state = useMenuTriggerState({trigger, isOpen, defaultOpen, onOpenChange});
|
||||
|
||||
const {menuTriggerProps, menuProps} = useMenuTrigger(
|
||||
{type, trigger, isDisabled},
|
||||
state,
|
||||
menuTriggerRef,
|
||||
);
|
||||
|
||||
const slots = useMemo(
|
||||
() =>
|
||||
dropdown({
|
||||
...variantProps,
|
||||
}),
|
||||
[...Object.values(variantProps)],
|
||||
);
|
||||
|
||||
const baseStyles = clsx(styles?.base, className);
|
||||
|
||||
const getPopoverProps: PropGetter = (props = {}) => ({
|
||||
state,
|
||||
placement,
|
||||
ref: popoverRef,
|
||||
scrollRef: menuRef,
|
||||
triggerRef: menuTriggerRef,
|
||||
...mergeProps(otherProps, props),
|
||||
className: slots.base({class: clsx(baseStyles, props.className)}),
|
||||
});
|
||||
|
||||
const getMenuTriggerProps: PropGetter = (
|
||||
props = {},
|
||||
_ref: Ref<any> | null | undefined = null,
|
||||
) => {
|
||||
// These props are not needed for the menu trigger since it is handled by the popover trigger.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const {onKeyDown, onPress, onPressStart, ...otherMenuTriggerProps} = menuTriggerProps;
|
||||
|
||||
return {
|
||||
...mergeProps(otherMenuTriggerProps, props),
|
||||
id: triggerId,
|
||||
ref: mergeRefs(_ref, triggerRef),
|
||||
};
|
||||
};
|
||||
|
||||
const getMenuProps: PropGetter = (props = {}, _ref: Ref<any> | null | undefined = null) => ({
|
||||
...mergeProps(menuProps, props),
|
||||
ref: mergeRefs(_ref, menuRef),
|
||||
});
|
||||
|
||||
return {
|
||||
Component,
|
||||
styles,
|
||||
closeOnSelect,
|
||||
onClose: state.close,
|
||||
autoFocus: state.focusStrategy || true,
|
||||
getPopoverProps,
|
||||
getMenuTriggerProps,
|
||||
getMenuProps,
|
||||
};
|
||||
}
|
||||
|
||||
export type UseDropdownReturn = ReturnType<typeof useDropdown>;
|
||||
121
packages/components/dropdown/stories/dropdown.stories.tsx
Normal file
121
packages/components/dropdown/stories/dropdown.stories.tsx
Normal file
@ -0,0 +1,121 @@
|
||||
import React from "react";
|
||||
import {ComponentStory, ComponentMeta} from "@storybook/react";
|
||||
import {dropdown} from "@nextui-org/theme";
|
||||
import {Button} from "@nextui-org/button";
|
||||
|
||||
import {Dropdown, DropdownTrigger, DropdownMenu, DropdownItem, DropdownProps} from "../src";
|
||||
|
||||
export default {
|
||||
title: "Components/Dropdown",
|
||||
component: Dropdown,
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: ["solid", "bordered", "light", "flat", "faded", "shadow"],
|
||||
},
|
||||
},
|
||||
color: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: ["neutral", "foreground", "primary", "secondary", "success", "warning", "danger"],
|
||||
},
|
||||
},
|
||||
radius: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: ["none", "base", "sm", "md", "lg", "xl", "full"],
|
||||
},
|
||||
},
|
||||
placement: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: [
|
||||
"top",
|
||||
"bottom",
|
||||
"right",
|
||||
"left",
|
||||
"top-start",
|
||||
"top-end",
|
||||
"bottom-start",
|
||||
"bottom-end",
|
||||
"left-start",
|
||||
"left-end",
|
||||
"right-start",
|
||||
"right-end",
|
||||
],
|
||||
},
|
||||
},
|
||||
backdropVariant: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: ["transparent", "blur", "opaque"],
|
||||
},
|
||||
},
|
||||
offset: {
|
||||
control: {
|
||||
type: "number",
|
||||
},
|
||||
},
|
||||
defaultOpen: {
|
||||
control: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
showArrow: {
|
||||
control: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
disableAnimation: {
|
||||
control: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
children: {
|
||||
control: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="flex items-center justify-center w-screen h-screen">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
} as ComponentMeta<typeof Dropdown>;
|
||||
|
||||
const defaultProps = {
|
||||
...dropdown.defaultVariants,
|
||||
placement: "bottom",
|
||||
offset: 7,
|
||||
defaultOpen: false,
|
||||
disableAnimation: false,
|
||||
};
|
||||
|
||||
const Template: ComponentStory<typeof Dropdown> = (args: DropdownProps) => (
|
||||
<Dropdown {...args}>
|
||||
<DropdownTrigger>
|
||||
<Button>Trigger</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu aria-label="Actions" onAction={alert}>
|
||||
<DropdownItem key="new">New file</DropdownItem>
|
||||
<DropdownItem key="copy">Copy link</DropdownItem>
|
||||
<DropdownItem key="edit">Edit file</DropdownItem>
|
||||
<DropdownItem key="delete">Delete file</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
...defaultProps,
|
||||
};
|
||||
|
||||
export const WithArrow = Template.bind({});
|
||||
WithArrow.args = {
|
||||
...defaultProps,
|
||||
showArrow: true,
|
||||
};
|
||||
10
packages/components/dropdown/tsconfig.json
Normal file
10
packages/components/dropdown/tsconfig.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"tailwind-variants": ["../../../node_modules/tailwind-variants"]
|
||||
}
|
||||
},
|
||||
"include": ["src", "index.ts"]
|
||||
}
|
||||
@ -3,7 +3,7 @@ import type {HTMLMotionProps} from "framer-motion";
|
||||
import type {OverlayPlacement} from "@nextui-org/aria-utils";
|
||||
import type {RefObject, Ref} from "react";
|
||||
|
||||
import {useOverlayTriggerState} from "@react-stately/overlays";
|
||||
import {OverlayTriggerState, useOverlayTriggerState} from "@react-stately/overlays";
|
||||
import {useFocusRing} from "@react-aria/focus";
|
||||
import {
|
||||
AriaPopoverProps,
|
||||
@ -24,6 +24,10 @@ export interface Props extends HTMLNextUIProps<"div"> {
|
||||
* Ref to the DOM node.
|
||||
*/
|
||||
ref?: ReactRef<HTMLElement | null>;
|
||||
/**
|
||||
* The controlled state of the popover.
|
||||
*/
|
||||
state?: OverlayTriggerState;
|
||||
/**
|
||||
* A ref for the scrollable region within the overlay.
|
||||
* @default popoverRef
|
||||
@ -79,6 +83,7 @@ export function usePopover(originalProps: UsePopoverProps) {
|
||||
ref,
|
||||
as,
|
||||
children,
|
||||
state: stateProp,
|
||||
triggerRef: triggerRefProp,
|
||||
scrollRef,
|
||||
isOpen,
|
||||
@ -114,12 +119,14 @@ export function usePopover(originalProps: UsePopoverProps) {
|
||||
createDOMRef(popoverRef),
|
||||
);
|
||||
|
||||
const state = useOverlayTriggerState({
|
||||
const innerState = useOverlayTriggerState({
|
||||
isOpen,
|
||||
defaultOpen,
|
||||
onOpenChange,
|
||||
});
|
||||
|
||||
const state = stateProp || innerState;
|
||||
|
||||
const {popoverProps, underlayProps, arrowProps, placement} = useReactAriaPopover(
|
||||
{
|
||||
triggerRef,
|
||||
|
||||
@ -509,5 +509,5 @@ WithBackdrop.args = {
|
||||
placement: "left",
|
||||
variant: "shadow",
|
||||
backdropVariant: "blur",
|
||||
className: "w-[280px] bg-white dark:bg-content1",
|
||||
className: "bg-white dark:bg-content1",
|
||||
};
|
||||
|
||||
43
packages/core/theme/src/components/dropdown-item.ts
Normal file
43
packages/core/theme/src/components/dropdown-item.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import type {VariantProps} from "tailwind-variants";
|
||||
|
||||
import {tv} from "tailwind-variants";
|
||||
|
||||
/**
|
||||
* DropdownItem wrapper **Tailwind Variants** component
|
||||
*
|
||||
* const {base, heading, indicator, trigger, leftIndicator, title, subtitle, content } = dropdownItem({...})
|
||||
*
|
||||
* @example
|
||||
* <div className={base())}>
|
||||
* <div className={heading())}>
|
||||
* <button className={trigger())}>
|
||||
* <div className={leftIndicator()}>
|
||||
* // content
|
||||
* </div>
|
||||
* <div className={titleWrapper()}>
|
||||
* <h3 className={title())}>Title</h3>
|
||||
* <span className={subtitle())}>Subtitle</span>
|
||||
* </div>
|
||||
* <span className={indicator())}>Indicator</span>
|
||||
* </button>
|
||||
* </div>
|
||||
* <div className={content())}>Content</div>
|
||||
* </div>
|
||||
*/
|
||||
const dropdownItem = tv({
|
||||
slots: {
|
||||
base: [],
|
||||
title: [],
|
||||
description: [],
|
||||
startContent: [],
|
||||
endContent: [],
|
||||
keyboardShortcut: [],
|
||||
},
|
||||
variants: {},
|
||||
defaultVariants: {},
|
||||
});
|
||||
|
||||
export type DropdownItemVariantProps = VariantProps<typeof dropdownItem>;
|
||||
export type DropdownItemSlots = keyof ReturnType<typeof dropdownItem>;
|
||||
|
||||
export {dropdownItem};
|
||||
34
packages/core/theme/src/components/dropdown.ts
Normal file
34
packages/core/theme/src/components/dropdown.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import type {VariantProps} from "tailwind-variants";
|
||||
|
||||
import {tv} from "tailwind-variants";
|
||||
|
||||
// import {colorVariants, ringClasses} from "../utils";
|
||||
/**
|
||||
* Dropdown wrapper **Tailwind Variants** component
|
||||
*
|
||||
* const { base, trigger, arrow } = dropdown({...})
|
||||
*
|
||||
* @example
|
||||
* <div>
|
||||
* <button className={trigger()} aria-expanded="true/false">your trigger</button>
|
||||
* <div className={base()}>
|
||||
* // dropdown content
|
||||
* <span className={arrow()} data-placement="top/bottom/left/right..." /> // arrow
|
||||
* </div>
|
||||
* </div>
|
||||
*/
|
||||
const dropdown = tv({
|
||||
slots: {
|
||||
base: [],
|
||||
trigger: [],
|
||||
section: [],
|
||||
sectionHeading: [],
|
||||
},
|
||||
variants: {},
|
||||
defaultVariants: {},
|
||||
});
|
||||
|
||||
export type DropdownVariantProps = VariantProps<typeof dropdown>;
|
||||
export type DropdownSlots = keyof ReturnType<typeof dropdown>;
|
||||
|
||||
export {dropdown};
|
||||
@ -23,3 +23,5 @@ export * from "./accordion";
|
||||
export * from "./progress";
|
||||
export * from "./circular-progress";
|
||||
export * from "./input";
|
||||
export * from "./dropdown";
|
||||
export * from "./dropdown-item";
|
||||
|
||||
101
pnpm-lock.yaml
generated
101
pnpm-lock.yaml
generated
@ -817,6 +817,61 @@ importers:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0
|
||||
|
||||
packages/components/dropdown:
|
||||
dependencies:
|
||||
'@nextui-org/aria-utils':
|
||||
specifier: workspace:*
|
||||
version: link:../../utilities/aria-utils
|
||||
'@nextui-org/dom-utils':
|
||||
specifier: workspace:*
|
||||
version: link:../../utilities/dom-utils
|
||||
'@nextui-org/popover':
|
||||
specifier: workspace:*
|
||||
version: link:../popover
|
||||
'@nextui-org/shared-utils':
|
||||
specifier: workspace:*
|
||||
version: link:../../utilities/shared-utils
|
||||
'@nextui-org/system':
|
||||
specifier: workspace:*
|
||||
version: link:../../core/system
|
||||
'@nextui-org/theme':
|
||||
specifier: workspace:*
|
||||
version: link:../../core/theme
|
||||
'@react-aria/menu':
|
||||
specifier: ^3.9.0
|
||||
version: 3.9.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@react-aria/utils':
|
||||
specifier: ^3.16.0
|
||||
version: 3.16.0(react@18.2.0)
|
||||
'@react-stately/collections':
|
||||
specifier: ^3.6.0
|
||||
version: 3.7.0(react@18.2.0)
|
||||
'@react-stately/menu':
|
||||
specifier: ^3.5.1
|
||||
version: 3.5.1(react@18.2.0)
|
||||
'@react-stately/tree':
|
||||
specifier: ^3.6.0
|
||||
version: 3.6.0(react@18.2.0)
|
||||
framer-motion:
|
||||
specifier: ^10.11.2
|
||||
version: 10.11.2(react-dom@18.2.0)(react@18.2.0)
|
||||
devDependencies:
|
||||
'@nextui-org/button':
|
||||
specifier: workspace:*
|
||||
version: link:../button
|
||||
'@react-types/menu':
|
||||
specifier: ^3.9.0
|
||||
version: 3.9.0(react@18.2.0)
|
||||
'@react-types/shared':
|
||||
specifier: ^3.18.0
|
||||
version: 3.18.0(react@18.2.0)
|
||||
clean-package:
|
||||
specifier: 2.2.0
|
||||
version: 2.2.0
|
||||
react:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0
|
||||
|
||||
packages/components/input:
|
||||
dependencies:
|
||||
'@nextui-org/dom-utils':
|
||||
@ -4061,7 +4116,7 @@ packages:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.21.0
|
||||
'@types/react': 18.0.1
|
||||
clsx: 1.1.0
|
||||
clsx: 1.2.1
|
||||
focus-lock: 0.8.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
@ -5942,6 +5997,28 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@react-aria/menu@3.9.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-lIbfWzFvYE7EPOno3lVogXHlc6fzswymlpJWiMBKaB68wkfCtknIIL1cwWssiwgGU63v08H5YpQOZdxRwux2PQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
|
||||
dependencies:
|
||||
'@react-aria/i18n': 3.7.1(react@18.2.0)
|
||||
'@react-aria/interactions': 3.15.0(react@18.2.0)
|
||||
'@react-aria/overlays': 3.14.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@react-aria/selection': 3.14.0(react@18.2.0)
|
||||
'@react-aria/utils': 3.16.0(react@18.2.0)
|
||||
'@react-stately/collections': 3.7.0(react@18.2.0)
|
||||
'@react-stately/menu': 3.5.1(react@18.2.0)
|
||||
'@react-stately/tree': 3.6.0(react@18.2.0)
|
||||
'@react-types/button': 3.7.2(react@18.2.0)
|
||||
'@react-types/menu': 3.9.0(react@18.2.0)
|
||||
'@react-types/shared': 3.18.0(react@18.2.0)
|
||||
'@swc/helpers': 0.4.14
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@react-aria/overlays@3.14.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-lt4vOj44ho0LpmpaHwQ4VgX7eNfKXig9VD7cvE9u7uyECG51jqt9go19s4+/O+otX7pPrhdYlEB2FxLFJocxfw==}
|
||||
peerDependencies:
|
||||
@ -6156,6 +6233,19 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@react-stately/menu@3.5.1(react@18.2.0):
|
||||
resolution: {integrity: sha512-nnuZlDBFIc3gB34kofbKDStFg9r8rijY+7ez2VWQmss72I9D7+JTn7OXJxV0oQt2lBYmNfS5W6bC9uXk3Z4dLg==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
|
||||
dependencies:
|
||||
'@react-stately/overlays': 3.5.1(react@18.2.0)
|
||||
'@react-stately/utils': 3.6.0(react@18.2.0)
|
||||
'@react-types/menu': 3.9.0(react@18.2.0)
|
||||
'@react-types/shared': 3.18.0(react@18.2.0)
|
||||
'@swc/helpers': 0.4.14
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@react-stately/overlays@3.5.1(react@18.2.0):
|
||||
resolution: {integrity: sha512-lDKqqpdaIQdJb8DS4+tT7p0TLyCeaUaFpEtWZNjyv1/nguoqYtSeRwnyPR4p/YM4AW7SJspNiTJSLQxkTMIa8w==}
|
||||
peerDependencies:
|
||||
@ -6288,6 +6378,15 @@ packages:
|
||||
'@react-types/shared': 3.18.0(react@18.2.0)
|
||||
react: 18.2.0
|
||||
|
||||
/@react-types/menu@3.9.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-aalUYwOkzcHn8X59vllgtH96YLqZvAr4mTj5GEs8chv5JVlmArUzcDiOymNrYZ0p9JzshzSUqxxXyCFpnnxghw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
|
||||
dependencies:
|
||||
'@react-types/overlays': 3.7.1(react@18.2.0)
|
||||
'@react-types/shared': 3.18.0(react@18.2.0)
|
||||
react: 18.2.0
|
||||
|
||||
/@react-types/overlays@3.7.1(react@18.2.0):
|
||||
resolution: {integrity: sha512-2AwYQkelr4p1uXR1KJIGQEbubOumzM853Hsyup2y/TaMbjvBWOVyzYWSrQURex667JZmpwUb0qjkEH+4z3Q74g==}
|
||||
peerDependencies:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user