Splitted
-
+
{defaultContent}
-
+
{defaultContent}
-
+
{defaultContent}
diff --git a/apps/docs/content/components/disclosure/compact.raw.jsx b/apps/docs/content/components/disclosure/compact.raw.jsx
new file mode 100644
index 0000000000..600ee41cce
--- /dev/null
+++ b/apps/docs/content/components/disclosure/compact.raw.jsx
@@ -0,0 +1,12 @@
+import {Disclosure} from "@heroui/react";
+
+export default function App() {
+ const defaultContent =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
+
+ return (
+
+ {defaultContent}
+
+ );
+}
diff --git a/apps/docs/content/components/disclosure/compact.ts b/apps/docs/content/components/disclosure/compact.ts
new file mode 100644
index 0000000000..c3cdfc316e
--- /dev/null
+++ b/apps/docs/content/components/disclosure/compact.ts
@@ -0,0 +1,9 @@
+import App from "./compact.raw.jsx?raw";
+
+const react = {
+ "/App.jsx": App,
+};
+
+export default {
+ ...react,
+};
diff --git a/apps/docs/content/components/disclosure/controlled.raw.jsx b/apps/docs/content/components/disclosure/controlled.raw.jsx
new file mode 100644
index 0000000000..6aeda31cf4
--- /dev/null
+++ b/apps/docs/content/components/disclosure/controlled.raw.jsx
@@ -0,0 +1,31 @@
+import React from "react";
+import {Disclosure, Button} from "@heroui/react";
+
+export default function App() {
+ const defaultContent =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
+ const [isExpanded, onExpandedChange] = React.useState < boolean > false;
+
+ return (
+
+
+
+
+
+
+ {defaultContent}
+
+
+
+ );
+}
diff --git a/apps/docs/content/components/disclosure/controlled.ts b/apps/docs/content/components/disclosure/controlled.ts
new file mode 100644
index 0000000000..2c3f0cacb4
--- /dev/null
+++ b/apps/docs/content/components/disclosure/controlled.ts
@@ -0,0 +1,9 @@
+import App from "./controlled.raw.jsx?raw";
+
+const react = {
+ "/App.jsx": App,
+};
+
+export default {
+ ...react,
+};
diff --git a/apps/docs/content/components/disclosure/custom-motion.raw.jsx b/apps/docs/content/components/disclosure/custom-motion.raw.jsx
new file mode 100644
index 0000000000..d4a310e176
--- /dev/null
+++ b/apps/docs/content/components/disclosure/custom-motion.raw.jsx
@@ -0,0 +1,15 @@
+import {Disclosure} from "@heroui/react";
+
+export default function App() {
+ const defaultContent =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
+ const classNames = {
+ content: "ease-soft-spring",
+ };
+
+ return (
+
+ {defaultContent}
+
+ );
+}
diff --git a/apps/docs/content/components/disclosure/custom-motion.ts b/apps/docs/content/components/disclosure/custom-motion.ts
new file mode 100644
index 0000000000..389f462ddd
--- /dev/null
+++ b/apps/docs/content/components/disclosure/custom-motion.ts
@@ -0,0 +1,9 @@
+import App from "./custom-motion.raw.jsx?raw";
+
+const react = {
+ "/App.jsx": App,
+};
+
+export default {
+ ...react,
+};
diff --git a/apps/docs/content/components/disclosure/custom-styles.raw.jsx b/apps/docs/content/components/disclosure/custom-styles.raw.jsx
new file mode 100644
index 0000000000..0851a08a02
--- /dev/null
+++ b/apps/docs/content/components/disclosure/custom-styles.raw.jsx
@@ -0,0 +1,89 @@
+import {Disclosure} from "@heroui/disclosure";
+
+const MonitorMobileIcon = (props) => {
+ return (
+
+ );
+};
+
+export default function App() {
+ const classNames = {
+ base: "py-0 w-full",
+ title: "font-normal text-medium",
+ trigger: "px-2 py-0 data-[hover=true]:bg-default-100 rounded-lg h-14 flex items-center",
+ indicator: "text-medium",
+ content: "text-small px-2",
+ };
+
+ const defaultContent =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
+
+ return (
+
}
+ subtitle={
+
+ 2 issues to fix now
+
+ }
+ title="Connected devices"
+ >
+ {defaultContent}
+
+ );
+}
diff --git a/apps/docs/content/components/disclosure/custom-styles.ts b/apps/docs/content/components/disclosure/custom-styles.ts
new file mode 100644
index 0000000000..da3ea9093a
--- /dev/null
+++ b/apps/docs/content/components/disclosure/custom-styles.ts
@@ -0,0 +1,9 @@
+import App from "./custom-styles.raw.jsx?raw";
+
+const react = {
+ "/App.jsx": App,
+};
+
+export default {
+ ...react,
+};
diff --git a/apps/docs/content/components/disclosure/default-expanded.raw.jsx b/apps/docs/content/components/disclosure/default-expanded.raw.jsx
new file mode 100644
index 0000000000..97ef46fe02
--- /dev/null
+++ b/apps/docs/content/components/disclosure/default-expanded.raw.jsx
@@ -0,0 +1,12 @@
+import {Disclosure} from "@heroui/react";
+
+export default function App() {
+ const defaultContent =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
+
+ return (
+
+ {defaultContent}
+
+ );
+}
diff --git a/apps/docs/content/components/disclosure/default-expanded.ts b/apps/docs/content/components/disclosure/default-expanded.ts
new file mode 100644
index 0000000000..9e9d7e4a09
--- /dev/null
+++ b/apps/docs/content/components/disclosure/default-expanded.ts
@@ -0,0 +1,9 @@
+import App from "./default-expanded.raw.jsx?raw";
+
+const react = {
+ "/App.jsx": App,
+};
+
+export default {
+ ...react,
+};
diff --git a/apps/docs/content/components/disclosure/disabled.raw.jsx b/apps/docs/content/components/disclosure/disabled.raw.jsx
new file mode 100644
index 0000000000..5b44a3371f
--- /dev/null
+++ b/apps/docs/content/components/disclosure/disabled.raw.jsx
@@ -0,0 +1,12 @@
+import {Disclosure} from "@heroui/react";
+
+export default function App() {
+ const defaultContent =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
+
+ return (
+
+ {defaultContent}
+
+ );
+}
diff --git a/apps/docs/content/components/disclosure/disabled.ts b/apps/docs/content/components/disclosure/disabled.ts
new file mode 100644
index 0000000000..1a215cc91f
--- /dev/null
+++ b/apps/docs/content/components/disclosure/disabled.ts
@@ -0,0 +1,9 @@
+import App from "./disabled.raw.jsx?raw";
+
+const react = {
+ "/App.jsx": App,
+};
+
+export default {
+ ...react,
+};
diff --git a/apps/docs/content/components/disclosure/index.ts b/apps/docs/content/components/disclosure/index.ts
new file mode 100644
index 0000000000..4a4360d278
--- /dev/null
+++ b/apps/docs/content/components/disclosure/index.ts
@@ -0,0 +1,25 @@
+import usage from "./usage";
+import subtitle from "./subtitle";
+import compact from "./compact";
+import defaultExpanded from "./default-expanded";
+import disabled from "./disabled";
+import startContent from "./start-content";
+import indicator from "./indicator";
+import indicatorFunction from "./indicator-function";
+import customMotion from "./custom-motion";
+import controlled from "./controlled";
+import customStyles from "./custom-styles";
+
+export const disclosureContent = {
+ usage,
+ subtitle,
+ compact,
+ defaultExpanded,
+ disabled,
+ startContent,
+ indicator,
+ indicatorFunction,
+ customMotion,
+ controlled,
+ customStyles,
+};
diff --git a/apps/docs/content/components/disclosure/indicator-function.raw.jsx b/apps/docs/content/components/disclosure/indicator-function.raw.jsx
new file mode 100644
index 0000000000..3296fb828b
--- /dev/null
+++ b/apps/docs/content/components/disclosure/indicator-function.raw.jsx
@@ -0,0 +1,72 @@
+import {Disclosure} from "@heroui/react";
+
+const MoonIcon = (props) => {
+ return (
+
+ );
+};
+
+const SunIcon = (props) => {
+ return (
+
+ );
+};
+
+export default function App() {
+ const defaultContent =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
+
+ return (
+
(isExpanded ? : )}
+ title="Disclosure with indicator function"
+ >
+ {defaultContent}
+
+ );
+}
diff --git a/apps/docs/content/components/disclosure/indicator-function.ts b/apps/docs/content/components/disclosure/indicator-function.ts
new file mode 100644
index 0000000000..42c08c9f3c
--- /dev/null
+++ b/apps/docs/content/components/disclosure/indicator-function.ts
@@ -0,0 +1,9 @@
+import App from "./indicator-function.raw.jsx?raw";
+
+const react = {
+ "/App.jsx": App,
+};
+
+export default {
+ ...react,
+};
diff --git a/apps/docs/content/components/disclosure/indicator.raw.jsx b/apps/docs/content/components/disclosure/indicator.raw.jsx
new file mode 100644
index 0000000000..c7d67a9463
--- /dev/null
+++ b/apps/docs/content/components/disclosure/indicator.raw.jsx
@@ -0,0 +1,39 @@
+import {Disclosure} from "@heroui/react";
+
+const MoonIcon = (props) => {
+ return (
+
+ );
+};
+
+export default function App() {
+ const defaultContent =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
+
+ return (
+
}
+ title="Disclosure with custom indicator"
+ >
+ {defaultContent}
+
+ );
+}
diff --git a/apps/docs/content/components/disclosure/indicator.ts b/apps/docs/content/components/disclosure/indicator.ts
new file mode 100644
index 0000000000..221ac6ad9e
--- /dev/null
+++ b/apps/docs/content/components/disclosure/indicator.ts
@@ -0,0 +1,9 @@
+import App from "./indicator.raw.jsx?raw";
+
+const react = {
+ "/App.jsx": App,
+};
+
+export default {
+ ...react,
+};
diff --git a/apps/docs/content/components/disclosure/start-content.raw.jsx b/apps/docs/content/components/disclosure/start-content.raw.jsx
new file mode 100644
index 0000000000..a96fac92b7
--- /dev/null
+++ b/apps/docs/content/components/disclosure/start-content.raw.jsx
@@ -0,0 +1,25 @@
+import {Disclosure, Avatar} from "@heroui/react";
+
+export default function App() {
+ const defaultContent =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
+
+ return (
+
+ }
+ subtitle="4 unread messages"
+ title="Chung Miller"
+ >
+ {defaultContent}
+
+ );
+}
diff --git a/apps/docs/content/components/disclosure/start-content.ts b/apps/docs/content/components/disclosure/start-content.ts
new file mode 100644
index 0000000000..59191d72a7
--- /dev/null
+++ b/apps/docs/content/components/disclosure/start-content.ts
@@ -0,0 +1,9 @@
+import App from "./start-content.raw.jsx?raw";
+
+const react = {
+ "/App.jsx": App,
+};
+
+export default {
+ ...react,
+};
diff --git a/apps/docs/content/components/disclosure/subtitle.raw.jsx b/apps/docs/content/components/disclosure/subtitle.raw.jsx
new file mode 100644
index 0000000000..8a9273e845
--- /dev/null
+++ b/apps/docs/content/components/disclosure/subtitle.raw.jsx
@@ -0,0 +1,12 @@
+import {Disclosure} from "@heroui/react";
+
+export default function App() {
+ const defaultContent =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
+
+ return (
+
+ {defaultContent}
+
+ );
+}
diff --git a/apps/docs/content/components/disclosure/subtitle.ts b/apps/docs/content/components/disclosure/subtitle.ts
new file mode 100644
index 0000000000..d196f1d53f
--- /dev/null
+++ b/apps/docs/content/components/disclosure/subtitle.ts
@@ -0,0 +1,9 @@
+import App from "./subtitle.raw.jsx?raw";
+
+const react = {
+ "/App.jsx": App,
+};
+
+export default {
+ ...react,
+};
diff --git a/apps/docs/content/components/disclosure/usage.raw.jsx b/apps/docs/content/components/disclosure/usage.raw.jsx
new file mode 100644
index 0000000000..dc1e89a0d4
--- /dev/null
+++ b/apps/docs/content/components/disclosure/usage.raw.jsx
@@ -0,0 +1,8 @@
+import {Disclosure} from "@heroui/react";
+
+export default function App() {
+ const defaultContent =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.";
+
+ return
{defaultContent};
+}
diff --git a/apps/docs/content/components/disclosure/usage.ts b/apps/docs/content/components/disclosure/usage.ts
new file mode 100644
index 0000000000..1118304c37
--- /dev/null
+++ b/apps/docs/content/components/disclosure/usage.ts
@@ -0,0 +1,9 @@
+import App from "./usage.raw.jsx?raw";
+
+const react = {
+ "/App.jsx": App,
+};
+
+export default {
+ ...react,
+};
diff --git a/apps/docs/content/docs/components/accordion.mdx b/apps/docs/content/docs/components/accordion.mdx
index e474e6f981..97cd96bcd3 100644
--- a/apps/docs/content/docs/components/accordion.mdx
+++ b/apps/docs/content/docs/components/accordion.mdx
@@ -33,7 +33,7 @@ Accordion display a list of high-level options that can expand/collapse to revea
HeroUI exports 2 accordion-related components:
- **Accordion**: The main component to display a list of accordion items.
-- **AccordionItem**: The item component to display a single accordion item.
+- **AccordionItem**: The item component to display a single accordion item. This uses the Disclosure component internally.
@@ -106,7 +106,7 @@ Accordion items have a property called `indicator`. You can use it to customize
-The indicator can be also a `function`, which receives the `isOpen`, `isDisabled` and the default `indicator` as parameters.
+The indicator can be also a `function`, which receives the `isExpanded`, `isDisabled` and the default `indicator` as parameters.
-> Learn more about Framer motion variants [here](https://www.framer.com/motion/animation/#variants).
-
### Controlled
Accordion is a controlled component, which means you need to control the `selectedKeys` property by yourself.
@@ -156,12 +154,10 @@ Here's an example of how to customize the accordion styles:
## Data Attributes
-`AccordionItem` has the following attributes on the `base` element:
+`Disclosure` in the `AccordionItem` has the following attributes on the `base` element:
-- **data-open**:
+- **data-expanded**:
Whether the accordion item is open.
-- **data-disabled**:
- When the accordion item is disabled.
- **data-hover**:
When the accordion item is being hovered. Based on [useHover](https://react-spectrum.adobe.com/react-aria/useHover.html).
- **data-focus**:
@@ -177,7 +173,7 @@ Here's an example of how to customize the accordion styles:
## Accessibility
-- Keyboard event support for
Space,
Enter,
Arrow Up,
Arrow Down and
Home /
End keys.
+- Keyboard event support for
Space,
Enter,
Tab and
Shift + Tab keys.
- Keyboard focus management and cross browser normalization.
- `aria-expanded` attribute for the accordion item.
- `aria-disabled` attribute for the accordion item.
@@ -204,17 +200,11 @@ Here's an example of how to customize the accordion styles:
default: "light"
},
{
- attribute: "selectionMode",
- type: "none | single | multiple",
- description: "The type of selection that is allowed in the collection.",
+ attribute: "allowsMultipleExpanded",
+ type: "boolean",
+ description: "Should allow multiple accordion items to expand.",
default: "-"
},
- {
- attribute: "selectionBehavior",
- type: "toggle | replace",
- description: "The accordion selection behavior.",
- default: "toggle"
- },
{
attribute: "isCompact",
type: "boolean",
@@ -257,12 +247,6 @@ Here's an example of how to customize the accordion styles:
description: "Whether the Accordion items indicator animation is disabled.",
default: "false"
},
- {
- attribute: "disallowEmptySelection",
- type: "boolean",
- description: "Whether the collection allows empty selection.",
- default: "false"
- },
{
attribute: "keepContentMounted",
type: "boolean",
@@ -288,21 +272,15 @@ Here's an example of how to customize the accordion styles:
default: "-"
},
{
- attribute: "itemClasses",
- type: "AccordionItemClassnames",
- description: "The accordion items classNames.",
- default: "-"
- },
- {
- attribute: "selectedKeys",
+ attribute: "expandedKeys",
type: "all | React.Key[]",
- description: "The currently selected keys in the collection (controlled).",
+ description: "The currently expanded keys in the collection (controlled).",
default: "-"
},
{
- attribute: "defaultSelectedKeys",
+ attribute: "defaultExpandedKeys",
type: "all | React.Key[]",
- description: "The initial selected keys in the collection (uncontrolled).",
+ description: "The initial expanded keys in the collection (uncontrolled).",
default: "-"
}
]}
@@ -313,7 +291,7 @@ Here's an example of how to customize the accordion styles:
) => any",
description: "Handler that is called when the selection changes.",
default: "-"
@@ -345,7 +323,7 @@ Here's an example of how to customize the accordion styles:
},
{
attribute: "indicator",
- type: "IndicatorProps",
+ type: "AccordionItemIndicatorProps",
description: "The accordion item expanded indicator, usually an arrow icon.",
default: "-"
},
@@ -355,12 +333,6 @@ Here's an example of how to customize the accordion styles:
description: "The accordion item start content, usually an icon or avatar.",
default: "-"
},
- {
- attribute: "motionProps",
- type: "MotionProps",
- description: "The props to modify the framer motion animation. Use the variants API to create your own animation.",
- default: "-"
- },
{
attribute: "isCompact",
type: "boolean",
@@ -498,9 +470,9 @@ export type AccordionItemIndicatorProps = {
*/
indicator?: ReactNode;
/**
- * The current open status.
+ * The current expanded status.
*/
- isOpen?: boolean;
+ isExpanded?: boolean;
/**
* The current disabled status.
* @default false
@@ -512,6 +484,7 @@ type indicator?: ReactNode | ((props: AccordionItemIndicatorProps) => ReactNode)
```
### Accordion Item classNames
+- This classNames are propagated to the internal disclosure component.
```ts
export type AccordionItemClassnames = {
@@ -525,36 +498,4 @@ export type AccordionItemClassnames = {
indicator?: string;
content?: string;
};
-```
-
-#### Motion Props
-
-```ts
-export type MotionProps = {
- /**
- * If `true`, the opacity of the content will be animated
- * @default true
- */
- animateOpacity?: boolean;
- /**
- * The height you want the content in its collapsed state.
- * @default 0
- */
- startingHeight?: number;
- /**
- * The height you want the content in its expanded state.
- * @default "auto"
- */
- endingHeight?: number | string;
- /**
- * The y-axis offset you want the content in its collapsed state.
- * @default 10
- */
- startingY?: number;
- /**
- * The y-axis offset you want the content in its expanded state.
- * @default 0
- */
- endingY?: number;
-} & HTMLMotionProps;
-```
+```
\ No newline at end of file
diff --git a/apps/docs/content/docs/components/disclosure.mdx b/apps/docs/content/docs/components/disclosure.mdx
new file mode 100644
index 0000000000..e2369a6e52
--- /dev/null
+++ b/apps/docs/content/docs/components/disclosure.mdx
@@ -0,0 +1,328 @@
+---
+title: "Disclosure"
+description: "Disclosure displays hidden content that can be revealed or concealed."
+---
+
+import {disclosureContent} from "@/content/components/disclosure";
+
+# Disclosure
+
+Disclosure displays hidden content that can be revealed or concealed.
+
+
+
+---
+
+
+
+## Installation
+
+
+
+## Import
+
+
+
+## Usage
+
+
+
+### With Subtitle
+
+
+
+### Compact
+
+If you set `isCompact` to `true`, the `Disclosure` will be displayed in a compact style.
+
+
+
+### Default expanded
+
+If you want to expand the disclosure by default, you can set the `defaultExpanded` to `true`.
+
+
+
+### Disabled
+
+If you want to disable the disclosure, you can set the `isDisabled` property to `true`.
+
+
+
+### Start content
+
+If you want to display some content before the Disclosure, you can set the `startContent` property.
+
+
+
+### Custom Indicator
+
+Disclosure has a property called `indicator`. You can use it to customize the open/close indicator.
+
+
+
+The indicator can be also a `function`, which receives the `isExpanded`, `isDisabled` and the default `indicator` as parameters.
+
+
+
+### Custom Motion
+
+Disclosure's animation can be changed by upadting the `content` style.
+
+
+
+### Controlled
+
+Disclosure is a controlled component, which means you need to control the `isExpanded` property by yourself.
+
+
+
+### Custom Styles
+
+
+
+
+
+## Disclosure Slots
+
+- **base**: The disclosure wrapper.
+- **heading**: The dsiclosure heading. It contains the `indicator` and the `title`.
+- **trigger**: The button that open/close the disclosure.
+- **titleWrapper**: The wrapper of the `title` and `subtitle`.
+- **title**: The disclosure item title.
+- **subtitle**: The disclosure item subtitle.
+- **startContent**: The content before the disclosure.
+- **indicator**: The element that indicates the open/close state of the dsiclosure.
+- **content**: Disclosure content.
+
+## Data Attributes
+
+`Disclosure` has the following attributes on the `base` element:
+
+- **data-expanded**:
+ Whether the disclosure item is expanded.
+- **data-hover**:
+ When the disclosure item is being hovered. Based on [useHover](https://react-spectrum.adobe.com/react-aria/useHover.html).
+- **data-focus**:
+ When the disclosure item is being focused. Based on [useFocusRing](https://react-spectrum.adobe.com/react-aria/useFocusRing.html).
+- **data-focus-visible**:
+ When the disclosure item is being focused with the keyboard. Based on [useFocusRing](https://react-spectrum.adobe.com/react-aria/useFocusRing.html).
+- **data-disabled**:
+ When the disclosure item is disabled. Based on `isDisabled` prop.
+- **data-pressed**:
+ When the disclosure item is pressed. Based on [usePress](https://react-spectrum.adobe.com/react-aria/usePress.html).
+
+
+
+## Accessibility
+
+- Keyboard event support for Space, Enter, Tab and Shift + Tab keys.
+- Keyboard focus management and cross browser normalization.
+- `aria-expanded` attribute.
+- `aria-disabled` attribute.
+
+
+
+## API
+
+### Disclosure Props
+
+
+
+### Disclosure Events
+
+ void",
+ description: "Handler that is called when the element receives focus.",
+ default: "-"
+ },
+ {
+ attribute: "onBlur",
+ type: "(e: FocusEvent) => void",
+ description: "Handler that is called when the element loses focus.",
+ default: "-"
+ },
+ {
+ attribute: "onFocusChange",
+ type: "(isFocused: boolean) => void",
+ description: "Handler that is called when the element's focus status changes.",
+ default: "-"
+ },
+ {
+ attribute: "onKeyDown",
+ type: "(e: KeyboardEvent) => void",
+ description: "Handler that is called when a key is pressed.",
+ default: "-"
+ },
+ {
+ attribute: "onKeyUp",
+ type: "(e: KeyboardEvent) => void",
+ description: "Handler that is called when a key is released.",
+ default: "-"
+ },
+ {
+ attribute: "onPress",
+ type: "(e: PressEvent) => void",
+ description: "Handler called when the press is released over the target.",
+ default: "-"
+ },
+ {
+ attribute: "onPressStart",
+ type: "(e: PressEvent) => void",
+ description: "Handler called when a press interaction starts.",
+ default: "-"
+ },
+ {
+ attribute: "onPressEnd",
+ type: "(e: PressEvent) => void",
+ description: "Handler called when a press interaction ends, either over the target or when the pointer leaves the target.",
+ default: "-"
+ },
+ {
+ attribute: "onPressChange",
+ type: "(isPressed: boolean) => void",
+ description: "Handler called when the press state changes.",
+ default: "-"
+ },
+ {
+ attribute: "onPressUp",
+ type: "(e: PressEvent) => void",
+ description: "Handler called when a press is released over the target, regardless of whether it started on the target or not.",
+ default: "-"
+ },
+ {
+ attribute: "onClick",
+ type: "MouseEventHandler",
+ description: "The native button click event handler (Deprecated) use onPress instead.",
+ default: "-"
+ }
+ ]}
+/>
+
+---
+
+### Types
+
+#### Disclosure Indicator Props
+
+```ts
+export type DisclosureIndicatorProps = {
+ /**
+ * The current indicator, usually an arrow icon.
+ */
+ indicator?: ReactNode;
+ /**
+ * The current expanded status.
+ */
+ isExpanded?: boolean;
+ /**
+ * The current disabled status.
+ * @default false
+ */
+ isDisabled?: boolean;
+};
+
+type indicator?: ReactNode | ((props: DisclosureIndicatorProps) => ReactNode) | null;
+```
\ No newline at end of file
diff --git a/packages/components/accordion/__tests__/accordion.test.tsx b/packages/components/accordion/__tests__/accordion.test.tsx
index 85e4f58236..71837e8cab 100644
--- a/packages/components/accordion/__tests__/accordion.test.tsx
+++ b/packages/components/accordion/__tests__/accordion.test.tsx
@@ -24,7 +24,7 @@ describe("Accordion", () => {
it("should render correctly", () => {
const wrapper = render(
- Accordion Item
+ Accordion Item
,
);
@@ -38,7 +38,7 @@ describe("Accordion", () => {
render(
- Accordion Item
+ Accordion Item
,
);
expect(ref.current).not.toBeNull();
@@ -47,8 +47,8 @@ describe("Accordion", () => {
it("should display the correct number of items", () => {
const wrapper = render(
- Accordion Item
- Accordion Item
+ Accordion Item
+ Accordion Item
,
);
@@ -58,25 +58,25 @@ describe("Accordion", () => {
it("should be opened when defaultExpandedKeys is set", () => {
const wrapper = render(
-
+
Accordion Item 1 description
-
+
Accordion Item 2 description
,
);
- expect(wrapper.getByTestId("item-1")).toHaveAttribute("data-open", "true");
+ expect(wrapper.getAllByRole("button")[0]).toHaveAttribute("data-expanded", "true");
});
it("should be disabled when disabledKeys is set", () => {
const wrapper = render(
-
+
Accordion Item 1 description
-
+
Accordion Item 2 description
,
@@ -88,13 +88,13 @@ describe("Accordion", () => {
it("should hide the accordion item when the hidden prop is set", () => {
const wrapper = render(
-
+
Accordion Item 1 description
-
+
Accordion Item 2 description
-
+
Accordion Item 3 description
,
@@ -107,17 +107,16 @@ describe("Accordion", () => {
it("should expand the accordion item when clicked", async () => {
const wrapper = render(
-
+
Accordion Item 1 description
-
+
Accordion Item 2 description
,
);
- const base = wrapper.getByTestId("item-1");
- const button = base.querySelector("button") as HTMLElement;
+ const button = wrapper.getAllByRole("button")[0] as HTMLElement;
expect(button).toHaveAttribute("aria-expanded", "false");
@@ -130,8 +129,8 @@ describe("Accordion", () => {
const wrapper = render(
}
title="Accordion Item 1"
>
@@ -143,79 +142,20 @@ describe("Accordion", () => {
expect(wrapper.getByTestId("start-content")).toBeInTheDocument();
});
- it("arrow up & down moves focus to next/previous accordion item", async () => {
- const wrapper = render(
-
-
- Accordion Item 1 description
-
-
- Accordion Item 2 description
-
- ,
- );
-
- const first = wrapper.getByTestId("item-1");
- const firstButton = first.querySelector("button") as HTMLElement;
-
- const second = wrapper.getByTestId("item-2");
- const secondButton = second.querySelector("button") as HTMLElement;
-
- await focus(firstButton);
- await user.keyboard("[ArrowDown]");
- expect(secondButton).toHaveFocus();
-
- await user.keyboard("[ArrowUp]");
-
- expect(firstButton).toHaveFocus();
- });
-
- it("home & end keys moves focus to first/last accordion", async () => {
- const wrapper = render(
-
-
- Accordion Item 1 description
-
-
- Accordion Item 2 description
-
- ,
- );
-
- const first = wrapper.getByTestId("item-1");
- const firstButton = first.querySelector("button") as HTMLElement;
-
- const second = wrapper.getByTestId("item-2");
- const secondButton = second.querySelector("button") as HTMLElement;
-
- act(() => {
- focus(secondButton);
- });
-
- await user.keyboard("[Home]");
- expect(firstButton).toHaveFocus();
-
- await user.keyboard("[End]");
- expect(secondButton).toHaveFocus();
- });
-
it("tab moves focus to the next focusable element", async () => {
const wrapper = render(
-
+
Accordion Item 1 description
-
+
Accordion Item 2 description
,
);
- const first = wrapper.getByTestId("item-1");
- const firstButton = first.querySelector("button") as HTMLElement;
-
- const second = wrapper.getByTestId("item-2");
- const secondButton = second.querySelector("button") as HTMLElement;
+ const firstButton = wrapper.getAllByRole("button")[0] as HTMLElement;
+ const secondButton = wrapper.getAllByRole("button")[1] as HTMLElement;
act(() => {
focus(firstButton);
@@ -225,40 +165,19 @@ describe("Accordion", () => {
expect(secondButton).toHaveFocus();
});
- it("aria-controls for button is same as id for region", async () => {
- const wrapper = render(
-
-
- Accordion Item 1 description
-
-
- Accordion Item 2 description
-
- ,
- );
-
- const base = wrapper.getByTestId("item-1");
- const button = base.querySelector("button") as HTMLElement;
-
- const region = base.querySelector("[role='region']") as HTMLElement;
-
- expect(button).toHaveAttribute("aria-controls", region.id);
- });
-
it("aria-expanded is true/false when accordion is open/closed", async () => {
const wrapper = render(
-
+
Accordion Item 1 description
-
+
Accordion Item 2 description
,
);
- const base = wrapper.getByTestId("item-1");
- const button = base.querySelector("button") as HTMLElement;
+ const button = wrapper.getAllByRole("button")[0] as HTMLElement;
expect(button).toHaveAttribute("aria-expanded", "false");
@@ -266,39 +185,13 @@ describe("Accordion", () => {
expect(button).toHaveAttribute("aria-expanded", "true");
});
- it("should support keepContentMounted", async () => {
- const wrapper = render(
-
-
- Accordion Item 1 description
-
-
- Accordion Item 2 description
-
- ,
- );
-
- const item1 = wrapper.getByTestId("item-1");
- const button = item1.querySelector("button") as HTMLElement;
-
- expect(item1.querySelector("[role='region']")).toBeInTheDocument();
-
- await user.click(button);
- const item2 = wrapper.getByTestId("item-2");
- const button2 = item2.querySelector("button") as HTMLElement;
-
- await user.click(button2);
- expect(item1.querySelector("[role='region']")).toBeInTheDocument();
- expect(item2.querySelector("[role='region']")).toBeInTheDocument();
- });
-
it("should handle arrow key navigation within Input inside AccordionItem", async () => {
const wrapper = render(
-
+
-
+
Accordion Item 2 description
,
@@ -306,7 +199,7 @@ describe("Accordion", () => {
const input = wrapper.getByLabelText("name");
- const firstButton = await wrapper.getByRole("button", {name: "Accordion Item 1"});
+ const firstButton = wrapper.getAllByRole("button")[0] as HTMLElement;
act(() => {
focus(firstButton);
@@ -334,10 +227,10 @@ describe("Accordion", () => {
className: "bg-rose-500",
}}
>
-
+
Accordion Item 1 description
-
+
Accordion Item 2 description
,
diff --git a/packages/components/accordion/package.json b/packages/components/accordion/package.json
index 631ae49962..cfe5a77817 100644
--- a/packages/components/accordion/package.json
+++ b/packages/components/accordion/package.json
@@ -55,13 +55,14 @@
"@heroui/divider": "workspace:*",
"@heroui/use-aria-accordion": "workspace:*",
"@heroui/dom-animation": "workspace:*",
+ "@heroui/disclosure": "workspace:*",
"@react-aria/interactions": "3.22.5",
"@react-aria/focus": "3.19.0",
"@react-aria/utils": "3.26.0",
- "@react-stately/tree": "3.8.6",
"@react-aria/button": "3.11.0",
"@react-types/accordion": "3.0.0-alpha.25",
- "@react-types/shared": "3.26.0"
+ "@react-types/shared": "3.26.0",
+ "@react-stately/disclosure": "^3.0.0"
},
"devDependencies": {
"@heroui/theme": "workspace:*",
diff --git a/packages/components/accordion/src/accordian-context.tsx b/packages/components/accordion/src/accordian-context.tsx
new file mode 100644
index 0000000000..1b9946b5ab
--- /dev/null
+++ b/packages/components/accordion/src/accordian-context.tsx
@@ -0,0 +1,14 @@
+import {createContext} from "@heroui/react-utils";
+import {DisclosureGroupState} from "@react-stately/disclosure";
+
+import {ValuesType} from "./use-accordion";
+
+export const [AccordianContext, useAccordianContext] = createContext<{
+ state: DisclosureGroupState;
+ values: ValuesType;
+}>({
+ name: "AccordianContext",
+ strict: true,
+ errorMessage:
+ "useAccordianContext: `context` is undefined. Seems you forgot to wrap component within ",
+});
diff --git a/packages/components/accordion/src/accordion-item.tsx b/packages/components/accordion/src/accordion-item.tsx
index ee7d5e0aca..b9200bf413 100644
--- a/packages/components/accordion/src/accordion-item.tsx
+++ b/packages/components/accordion/src/accordion-item.tsx
@@ -1,128 +1,22 @@
-import type {Variants} from "framer-motion";
-
import {forwardRef} from "@heroui/system";
-import {useMemo, ReactNode} from "react";
-import {ChevronIcon} from "@heroui/shared-icons";
-import {AnimatePresence, LazyMotion, m, useWillChange} from "framer-motion";
-import {TRANSITION_VARIANTS} from "@heroui/framer-utils";
+import {Disclosure} from "@heroui/disclosure";
+import {Divider} from "@heroui/divider";
import {UseAccordionItemProps, useAccordionItem} from "./use-accordion-item";
export interface AccordionItemProps extends UseAccordionItemProps {}
-const domAnimation = () => import("@heroui/dom-animation").then((res) => res.default);
-
const AccordionItem = forwardRef<"button", AccordionItemProps>((props, ref) => {
- const {
- Component,
- HeadingComponent,
- classNames,
- slots,
- indicator,
- children,
- title,
- subtitle,
- startContent,
- isOpen,
- isDisabled,
- hideIndicator,
- keepContentMounted,
- disableAnimation,
- motionProps,
- getBaseProps,
- getHeadingProps,
- getButtonProps,
- getTitleProps,
- getSubtitleProps,
- getContentProps,
- getIndicatorProps,
- } = useAccordionItem({...props, ref});
-
- const willChange = useWillChange();
-
- const indicatorContent = useMemo(() => {
- if (typeof indicator === "function") {
- return indicator({indicator: , isOpen, isDisabled});
- }
-
- if (indicator) return indicator;
-
- return null;
- }, [indicator, isOpen, isDisabled]);
-
- const indicatorComponent = indicatorContent || ;
-
- const content = useMemo(() => {
- if (disableAnimation) {
- return {children}
;
- }
-
- const transitionVariants: Variants = {
- exit: {...TRANSITION_VARIANTS.collapse.exit, overflowY: "hidden"},
- enter: {...TRANSITION_VARIANTS.collapse.enter, overflowY: "unset"},
- };
-
- return keepContentMounted ? (
-
- {
- e.stopPropagation();
- }}
- {...motionProps}
- >
- {children}
-
-
- ) : (
-
- {isOpen && (
-
- {
- e.stopPropagation();
- }}
- {...motionProps}
- >
- {children}
-
-
- )}
-
- );
- }, [isOpen, disableAnimation, keepContentMounted, children, motionProps]);
+ const {disclosureProps, children, dividerProps, hidden, showDivider, getBaseProps} =
+ useAccordionItem(props);
return (
-
-
-
-
- {content}
-
+
+
+ {children}
+
+ {showDivider && !hidden &&
}
+
);
});
diff --git a/packages/components/accordion/src/accordion.tsx b/packages/components/accordion/src/accordion.tsx
index 84f6dab666..ef18bb2a01 100644
--- a/packages/components/accordion/src/accordion.tsx
+++ b/packages/components/accordion/src/accordion.tsx
@@ -1,61 +1,17 @@
import {forwardRef} from "@heroui/system";
-import {LayoutGroup} from "framer-motion";
-import {Divider} from "@heroui/divider";
-import {Fragment, Key, useCallback, useMemo} from "react";
-import {UseAccordionProps, useAccordion} from "./use-accordion";
-import AccordionItem from "./accordion-item";
+import {useAccordion, UseAccordionProps} from "./use-accordion";
+import {AccordianContext} from "./accordian-context";
export interface AccordionProps extends UseAccordionProps {}
const AccordionGroup = forwardRef<"div", AccordionProps>((props, ref) => {
- const {
- Component,
- values,
- state,
- isSplitted,
- showDivider,
- getBaseProps,
- disableAnimation,
- handleFocusChanged: handleFocusChangedProps,
- itemClasses,
- dividerProps,
- } = useAccordion({
- ...props,
- ref,
- });
- const handleFocusChanged = useCallback(
- (isFocused: boolean, key: Key) => handleFocusChangedProps(isFocused, key),
- [handleFocusChangedProps],
- );
-
- const content = useMemo(() => {
- return [...state.collection].map((item, index) => {
- const classNames = {...itemClasses, ...(item.props.classNames || {})};
-
- return (
-
-
- {!item.props.hidden &&
- !isSplitted &&
- showDivider &&
- index < state.collection.size - 1 && }
-
- );
- });
- }, [values, itemClasses, handleFocusChanged, isSplitted, showDivider, state.collection]);
+ const {state, values, children, Component, getBaseProps} = useAccordion({...props, ref});
return (
-
- {disableAnimation ? content : {content}}
-
+
+ {children}
+
);
});
diff --git a/packages/components/accordion/src/base/accordion-item-base.tsx b/packages/components/accordion/src/base/accordion-item-base.tsx
index d462e3398f..006cbc34b2 100644
--- a/packages/components/accordion/src/base/accordion-item-base.tsx
+++ b/packages/components/accordion/src/base/accordion-item-base.tsx
@@ -1,5 +1,3 @@
-import type {AccordionItemVariantProps, AccordionItemSlots, SlotsToClasses} from "@heroui/theme";
-
import {As} from "@heroui/system";
import {ItemProps, BaseItem} from "@heroui/aria-utils";
import {FocusableProps, PressEvents} from "@react-types/shared";
@@ -62,26 +60,6 @@ export interface Props
* @deprecated - use `onPress` instead.
*/
onClick?: MouseEventHandler;
- /**
- * Classname or List of classes to change the classNames of the element.
- * if `className` is passed, it will be added to the base slot.
- *
- * @example
- * ```ts
- *
- * ```
- */
- classNames?: SlotsToClasses;
/**
* Customizable heading tag for Web accessibility:
* use headings to describe content and use them consistently and semantically.
@@ -90,7 +68,7 @@ export interface Props
HeadingComponent?: As;
}
-export type AccordionItemBaseProps = Props & AccordionItemVariantProps;
+export type AccordionItemBaseProps = Props;
const AccordionItemBase = BaseItem as (props: AccordionItemBaseProps) => JSX.Element;
diff --git a/packages/components/accordion/src/index.ts b/packages/components/accordion/src/index.ts
index be286a2064..d8bc423344 100644
--- a/packages/components/accordion/src/index.ts
+++ b/packages/components/accordion/src/index.ts
@@ -1,4 +1,4 @@
-import AccordionItem from "./base/accordion-item-base";
+import AccordionItem from "./accordion-item";
import Accordion from "./accordion";
// export types
diff --git a/packages/components/accordion/src/use-accordion-item.ts b/packages/components/accordion/src/use-accordion-item.ts
index 9ece5c27be..3134d571be 100644
--- a/packages/components/accordion/src/use-accordion-item.ts
+++ b/packages/components/accordion/src/use-accordion-item.ts
@@ -1,274 +1,94 @@
-import type {AccordionItemVariantProps} from "@heroui/theme";
+import type {DisclosureSlots, DisclosureVariantProps} from "@heroui/theme";
-import {HTMLHeroUIProps, PropGetter, useProviderContext} from "@heroui/system";
-import {useFocusRing} from "@react-aria/focus";
-import {accordionItem} from "@heroui/theme";
-import {clsx, callAllHandlers, dataAttr, objectToDeps} from "@heroui/shared-utils";
-import {ReactRef, useDOMRef, filterDOMProps} from "@heroui/react-utils";
-import {NodeWithProps} from "@heroui/aria-utils";
-import {useReactAriaAccordionItem} from "@heroui/use-aria-accordion";
-import {useCallback, useMemo} from "react";
-import {chain, mergeProps} from "@react-aria/utils";
-import {useHover, usePress} from "@react-aria/interactions";
-import {TreeState} from "@react-stately/tree";
+import {HTMLHeroUIProps, PropGetter} from "@heroui/system";
+import {ReactRef} from "@heroui/react-utils";
+import {DisclosureProps} from "@heroui/disclosure";
+import {SlotsToClasses} from "@heroui/theme";
+import {Key, useCallback} from "react";
+import {callAllHandlers} from "@heroui/shared-utils";
+import {useAccordianContext} from "./accordian-context";
import {AccordionItemBaseProps} from "./base/accordion-item-base";
-export interface Props extends HTMLHeroUIProps<"div"> {
+export interface Props extends HTMLHeroUIProps<"div"> {
/**
* Ref to the DOM node.
*/
ref?: ReactRef;
- /**
- * The item node.
- */
- item: NodeWithProps>;
- /**
- * The accordion tree state.
- */
- state: TreeState;
- /**
- * Current focused key.
- */
- focusedKey: React.Key | null;
- /**
- * Callback fired when the focus state changes.
- */
+ id: string;
+ disabledKeys?: Iterable;
+ classNames?: SlotsToClasses;
onFocusChange?: (isFocused: boolean, key?: React.Key) => void;
}
-export type UseAccordionItemProps = Props &
- AccordionItemVariantProps &
+export type UseAccordionItemProps = Props &
+ DisclosureVariantProps &
+ DisclosureProps &
Omit;
-export function useAccordionItem(props: UseAccordionItemProps) {
- const globalContext = useProviderContext();
-
- const {ref, as, item, onFocusChange} = props;
-
- const {
- state,
- className,
- indicator,
- children,
- title,
- subtitle,
- startContent,
- motionProps,
- focusedKey,
- variant,
- isCompact = false,
- classNames: classNamesProp = {},
- isDisabled: isDisabledProp = false,
- hideIndicator = false,
- disableAnimation = globalContext?.disableAnimation ?? false,
- keepContentMounted = false,
- disableIndicatorAnimation = false,
- HeadingComponent = as || "h2",
- onPress,
- onPressStart,
- onPressEnd,
- onPressChange,
- onPressUp,
- onClick,
- ...otherProps
- } = props;
-
- const Component = as || "div";
- const shouldFilterDOMProps = typeof Component === "string";
-
- const domRef = useDOMRef(ref);
-
- const isDisabled = state.disabledKeys.has(item.key) || isDisabledProp;
- const isOpen = state.selectionManager.isSelected(item.key);
-
- const {buttonProps: buttonCompleteProps, regionProps} = useReactAriaAccordionItem(
- {item, isDisabled},
- {...state, focusedKey: focusedKey},
- domRef,
- );
+export function useAccordionItem(originalProps: UseAccordionItemProps) {
+ const {state, values} = useAccordianContext();
- const {onFocus: onFocusButton, onBlur: onBlurButton, ...buttonProps} = buttonCompleteProps;
+ const {id, classNames, onFocusChange, ...otherProps} = originalProps;
+ const {isDisabled} = values;
+ const showDivider = values.showDivider && values.lastChildId != id;
- const {isFocused, isFocusVisible, focusProps} = useFocusRing({
- autoFocus: item.props?.autoFocus,
- });
+ const containsKey = (iterable: Iterable | undefined, key: Key): boolean => {
+ if (!iterable) {
+ return false;
+ }
+ for (const item of iterable) {
+ if (item === key) {
+ return true;
+ }
+ }
- const {isHovered, hoverProps} = useHover({isDisabled});
+ return false;
+ };
- const {pressProps, isPressed} = usePress({
- ref: domRef,
- isDisabled,
- onPress,
- onPressStart,
- onPressEnd,
- onPressChange,
- onPressUp,
- });
+ const disabledKeys = values.disabledKeys;
const handleFocus = useCallback(() => {
- onFocusChange?.(true, item.key);
+ onFocusChange?.(true, id);
}, []);
const handleBlur = useCallback(() => {
- onFocusChange?.(false, item.key);
+ onFocusChange?.(false, id);
}, []);
- const classNames = useMemo(
- () => ({
- ...classNamesProp,
- }),
- [objectToDeps(classNamesProp)],
- );
-
- const slots = useMemo(
- () =>
- accordionItem({
- isCompact,
- isDisabled,
- hideIndicator,
- disableAnimation,
- disableIndicatorAnimation,
- variant,
- }),
- [isCompact, isDisabled, hideIndicator, disableAnimation, disableIndicatorAnimation, variant],
- );
-
- const baseStyles = clsx(classNames?.base, className);
-
- const getBaseProps = useCallback(
- (props = {}) => {
- return {
- "data-open": dataAttr(isOpen),
- "data-disabled": dataAttr(isDisabled),
- className: slots.base({class: baseStyles}),
- ...mergeProps(
- filterDOMProps(otherProps, {
- enabled: shouldFilterDOMProps,
- }),
- props,
- ),
- };
+ const disclosureProps: DisclosureProps = {
+ ...values,
+ ...otherProps,
+ isExpanded: state.expandedKeys.has(id),
+ isDisabled: containsKey(disabledKeys, id) || isDisabled,
+ onExpandedChange(isExpanded) {
+ if (state) {
+ state.toggleKey(id);
+ }
+ originalProps.onExpandedChange?.(isExpanded);
},
- [baseStyles, shouldFilterDOMProps, otherProps, slots, item.props, isOpen, isDisabled],
- );
-
- const getButtonProps: PropGetter = (props = {}) => {
- return {
- ref: domRef,
- "data-open": dataAttr(isOpen),
- "data-focus": dataAttr(isFocused),
- "data-focus-visible": dataAttr(isFocusVisible),
- "data-disabled": dataAttr(isDisabled),
- "data-hover": dataAttr(isHovered),
- "data-pressed": dataAttr(isPressed),
- className: slots.trigger({class: classNames?.trigger}),
- onFocus: callAllHandlers(
- handleFocus,
- onFocusButton,
- focusProps.onFocus,
- otherProps.onFocus,
- item.props?.onFocus,
- ),
- onBlur: callAllHandlers(
- handleBlur,
- onBlurButton,
- focusProps.onBlur,
- otherProps.onBlur,
- item.props?.onBlur,
- ),
- ...mergeProps(buttonProps, hoverProps, pressProps, props, {
- onClick: chain(pressProps.onClick, onClick),
- }),
- };
+ onFocus: callAllHandlers(handleFocus, originalProps.onFocus),
+ onBlur: callAllHandlers(handleBlur, originalProps.onBlur),
+ classNames,
};
- const getContentProps = useCallback(
+ const getBaseProps: PropGetter = useCallback(
(props = {}) => {
return {
- "data-open": dataAttr(isOpen),
- "data-disabled": dataAttr(isDisabled),
- className: slots.content({class: classNames?.content}),
- ...mergeProps(regionProps, props),
- };
- },
- [slots, classNames, regionProps, isOpen, isDisabled, classNames?.content],
- );
-
- const getIndicatorProps = useCallback(
- (props = {}) => {
- return {
- "aria-hidden": dataAttr(true),
- "data-open": dataAttr(isOpen),
- "data-disabled": dataAttr(isDisabled),
- className: slots.indicator({class: classNames?.indicator}),
- ...props,
- };
- },
- [slots, classNames?.indicator, isOpen, isDisabled, classNames?.indicator],
- );
-
- const getHeadingProps = useCallback(
- (props = {}) => {
- return {
- "data-open": dataAttr(isOpen),
- "data-disabled": dataAttr(isDisabled),
- className: slots.heading({class: classNames?.heading}),
+ "data-hidden": originalProps.hidden,
...props,
};
},
- [slots, classNames?.heading, isOpen, isDisabled, classNames?.heading],
- );
-
- const getTitleProps = useCallback(
- (props = {}) => {
- return {
- "data-open": dataAttr(isOpen),
- "data-disabled": dataAttr(isDisabled),
- className: slots.title({class: classNames?.title}),
- ...props,
- };
- },
- [slots, classNames?.title, isOpen, isDisabled, classNames?.title],
- );
-
- const getSubtitleProps = useCallback(
- (props = {}) => {
- return {
- "data-open": dataAttr(isOpen),
- "data-disabled": dataAttr(isDisabled),
- className: slots.subtitle({class: classNames?.subtitle}),
- ...props,
- };
- },
- [slots, classNames, isOpen, isDisabled, classNames?.subtitle],
+ [originalProps.hidden],
);
return {
- Component,
- HeadingComponent,
- item,
- slots,
- classNames,
- domRef,
- indicator,
- children,
- title,
- subtitle,
- startContent,
- isOpen,
- isDisabled,
- hideIndicator,
- keepContentMounted,
- disableAnimation,
- motionProps,
+ disclosureProps,
+ children: originalProps.children,
+ dividerProps: values.dividerProps,
+ hidden: originalProps.hidden,
+ showDivider,
getBaseProps,
- getHeadingProps,
- getButtonProps,
- getContentProps,
- getIndicatorProps,
- getTitleProps,
- getSubtitleProps,
};
}
diff --git a/packages/components/accordion/src/use-accordion.ts b/packages/components/accordion/src/use-accordion.ts
index d5668543ce..f561824c5b 100644
--- a/packages/components/accordion/src/use-accordion.ts
+++ b/packages/components/accordion/src/use-accordion.ts
@@ -1,18 +1,16 @@
-import type {SelectionBehavior, MultipleSelection} from "@react-types/shared";
import type {AriaAccordionProps} from "@react-types/accordion";
-import type {AccordionGroupVariantProps} from "@heroui/theme";
import type {HTMLHeroUIProps, PropGetter} from "@heroui/system";
+import {AccordionGroupVariantProps, accordion} from "@heroui/theme";
import {useProviderContext} from "@heroui/system";
import {ReactRef, filterDOMProps} from "@heroui/react-utils";
-import React, {Key, useCallback} from "react";
-import {TreeState, useTreeState} from "@react-stately/tree";
+import {Children, isValidElement, Key, useCallback} from "react";
import {mergeProps} from "@react-aria/utils";
-import {accordion} from "@heroui/theme";
import {useDOMRef} from "@heroui/react-utils";
-import {useMemo, useState} from "react";
+import {useMemo} from "react";
import {DividerProps} from "@heroui/divider";
-import {useReactAriaAccordion} from "@heroui/use-aria-accordion";
+import {useDisclosureGroupState} from "@react-stately/disclosure";
+import {clsx} from "@heroui/shared-utils";
import {AccordionItemProps} from "./accordion-item";
@@ -31,11 +29,7 @@ interface Props extends HTMLHeroUIProps<"div"> {
* The divider props.
*/
dividerProps?: Partial;
- /**
- * The accordion selection behavior.
- * @default "toggle"
- */
- selectionBehavior?: SelectionBehavior;
+ allowsMultipleExpanded?: boolean;
/**
* Whether to keep the accordion content mounted when collapsed.
* @default false
@@ -45,24 +39,19 @@ interface Props extends HTMLHeroUIProps<"div"> {
* The accordion items classNames.
*/
itemClasses?: AccordionItemProps["classNames"];
+ disabledKeys?: Iterable;
}
export type UseAccordionProps = Props &
+ AccordionGroupVariantProps &
+ AriaAccordionProps &
AccordionGroupVariantProps &
Pick<
AccordionItemProps,
- | "isCompact"
- | "isDisabled"
- | "hideIndicator"
- | "disableAnimation"
- | "disableIndicatorAnimation"
- | "motionProps"
- > &
- AriaAccordionProps &
- MultipleSelection;
+ "isCompact" | "isDisabled" | "hideIndicator" | "disableAnimation" | "disableIndicatorAnimation"
+ >;
-export type ValuesType = {
- state: TreeState;
+export type ValuesType = {
focusedKey?: Key | null;
isCompact?: AccordionItemProps["isCompact"];
isDisabled?: AccordionItemProps["isDisabled"];
@@ -70,180 +59,104 @@ export type ValuesType = {
disableAnimation?: AccordionItemProps["disableAnimation"];
keepContentMounted?: Props["keepContentMounted"];
disableIndicatorAnimation?: AccordionItemProps["disableAnimation"];
- motionProps?: AccordionItemProps["motionProps"];
+ disabledKeys?: Iterable;
+ lastChildId?: string;
+ dividerProps?: Partial;
+ showDivider?: boolean;
+ fullWidth?: boolean;
};
-export function useAccordion(props: UseAccordionProps) {
+export function useAccordion(originalProps: UseAccordionProps) {
const globalContext = useProviderContext();
const {
- ref,
as,
- className,
- items,
- variant,
- motionProps,
- expandedKeys,
- disabledKeys,
- selectedKeys,
- children: childrenProp,
- defaultExpandedKeys,
- selectionMode = "single",
- selectionBehavior = "toggle",
- keepContentMounted = false,
- disallowEmptySelection,
- defaultSelectedKeys,
- onExpandedChange,
- onSelectionChange,
- dividerProps = {},
+ ref,
isCompact = false,
- isDisabled = false,
- showDivider = true,
+ isDisabled,
hideIndicator = false,
disableAnimation = globalContext?.disableAnimation ?? false,
disableIndicatorAnimation = false,
- itemClasses,
- ...otherProps
- } = props;
-
- const [focusedKey, setFocusedKey] = useState(null);
-
- const Component = as || "div";
- const shouldFilterDOMProps = typeof Component === "string";
-
- const domRef = useDOMRef(ref);
-
- const classNames = useMemo(
- () =>
- accordion({
- variant,
- className,
- }),
- [variant, className],
- );
-
- // TODO: Remove this once the issue is fixed.
- const children = useMemo(() => {
- let treeChildren: any = [];
-
- /**
- * This is a workaround for rendering ReactNode children in the AccordionItem.
- * @see https://github.com/adobe/react-spectrum/issues/3882
- */
- React.Children.map(childrenProp, (child) => {
- if (React.isValidElement(child) && typeof child.props?.children !== "string") {
- const clonedChild = React.cloneElement(child, {
- // @ts-ignore
- hasChildItems: false,
- });
-
- treeChildren.push(clonedChild);
- } else {
- treeChildren.push(child);
- }
- });
-
- return treeChildren;
- }, [childrenProp]);
-
- const commonProps = {
+ disabledKeys,
+ variant,
+ className,
children,
- items,
- };
-
- const expandableProps = {
- expandedKeys,
- defaultExpandedKeys,
+ dividerProps,
+ keepContentMounted,
+ showDivider = true,
+ fullWidth = true,
onExpandedChange,
- };
-
- const treeProps = {
- disabledKeys,
- selectedKeys,
- selectionMode,
- selectionBehavior,
- disallowEmptySelection,
- defaultSelectedKeys: defaultSelectedKeys ?? defaultExpandedKeys,
- onSelectionChange,
- ...commonProps,
- ...expandableProps,
- };
+ } = originalProps;
- const state = useTreeState(treeProps);
+ const state = useDisclosureGroupState({...originalProps, onExpandedChange});
- state.selectionManager.setFocusedKey = (key: Key | null) => {
- setFocusedKey(key);
- };
-
- const {accordionProps} = useReactAriaAccordion(
- {
- ...commonProps,
- ...expandableProps,
- },
- state,
- domRef,
- );
+ const Component = as || "div";
+ const shouldFilterDOMProps = typeof Component === "string";
+ const lastChild = Children.toArray(children).at(-1);
+ const lastChildId = isValidElement(lastChild) ? lastChild.props.id : undefined;
- const values: ValuesType = useMemo(
+ const values: ValuesType = useMemo(
() => ({
- state,
- focusedKey,
- motionProps,
isCompact,
isDisabled,
hideIndicator,
disableAnimation,
- keepContentMounted,
disableIndicatorAnimation,
+ disabledKeys,
+ lastChildId,
+ dividerProps,
+ keepContentMounted,
+ showDivider,
+ fullWidth,
}),
[
- focusedKey,
isCompact,
isDisabled,
hideIndicator,
- selectedKeys,
disableAnimation,
- keepContentMounted,
state?.expandedKeys.values,
disableIndicatorAnimation,
state.expandedKeys.size,
- state.disabledKeys.size,
- motionProps,
+ disabledKeys,
+ lastChildId,
+ keepContentMounted,
+ showDivider,
+ fullWidth,
],
);
+ const domRef = useDOMRef(ref);
+ const classNames = useMemo(
+ () =>
+ accordion({
+ variant,
+ className,
+ }),
+ [variant, className],
+ );
+
const getBaseProps: PropGetter = useCallback((props = {}) => {
return {
ref: domRef,
- className: classNames,
"data-orientation": "vertical",
...mergeProps(
- accordionProps,
- filterDOMProps(otherProps, {
+ filterDOMProps(originalProps, {
enabled: shouldFilterDOMProps,
}),
props,
),
+ className: clsx(classNames, className),
};
}, []);
- const handleFocusChanged = useCallback((isFocused: boolean, key: Key | null) => {
- isFocused && setFocusedKey(key);
- }, []);
-
return {
- Component,
- values,
state,
- focusedKey,
+ values,
+ children,
+ Component,
getBaseProps,
- isSplitted: variant === "splitted",
- classNames,
+ domRef,
showDivider,
- dividerProps,
- disableAnimation,
- handleFocusChanged,
- itemClasses,
};
}
diff --git a/packages/components/accordion/stories/accordion.stories.tsx b/packages/components/accordion/stories/accordion.stories.tsx
index fcf55191a3..f8f45ee52d 100644
--- a/packages/components/accordion/stories/accordion.stories.tsx
+++ b/packages/components/accordion/stories/accordion.stories.tsx
@@ -2,7 +2,8 @@ import type {Selection} from "@react-types/shared";
import React from "react";
import {Meta} from "@storybook/react";
-import {accordionItem, button} from "@heroui/theme";
+import {button} from "@heroui/theme";
+
import {
AnchorIcon,
MoonIcon,
@@ -33,23 +34,31 @@ export default {
type: "boolean",
},
},
- selectionMode: {
+ allowsMultipleExpanded: {
control: {
- type: "select",
+ type: "boolean",
},
- options: ["single", "multiple"],
},
disableAnimation: {
control: {
type: "boolean",
},
},
+ showDivider: {
+ control: {
+ type: "boolean",
+ },
+ },
+ hideIndicator: {
+ control: {
+ type: "boolean",
+ },
+ },
},
} as Meta;
const defaultProps = {
- ...accordionItem.defaultVariants,
- selectionMode: "single",
+ allowsMultipleExpanded: false,
};
const defaultContent =
@@ -57,13 +66,13 @@ const defaultContent =
const Template = (args: AccordionProps) => (
-
+
{defaultContent}
-
+
{defaultContent}
-
+
{defaultContent}
@@ -71,22 +80,22 @@ const Template = (args: AccordionProps) => (
const TemplateWithSubtitle = (args: AccordionProps) => (
-
+
{defaultContent}
- Press to expand key 2
+ Press to expand id 2
}
title="Accordion 2"
>
{defaultContent}
-
+
{defaultContent}
@@ -95,8 +104,8 @@ const TemplateWithSubtitle = (args: AccordionProps) => (
const TemplateWithStartContent = (args: AccordionProps) => (
(
{defaultContent}
(
{defaultContent}
(
Default
-
+
{defaultContent}
-
+
{defaultContent}
-
+
{defaultContent}
@@ -168,13 +177,13 @@ const VariantsTemplate = (args: AccordionProps) => (
Shadow
-
+
{defaultContent}
-
+
{defaultContent}
-
+
{defaultContent}
@@ -182,13 +191,13 @@ const VariantsTemplate = (args: AccordionProps) => (
Bordered
-
+
{defaultContent}
-
+
{defaultContent}
-
+
{defaultContent}
@@ -196,13 +205,13 @@ const VariantsTemplate = (args: AccordionProps) => (
Splitted
-
+
{defaultContent}
-
+
{defaultContent}
-
+
{defaultContent}
@@ -210,15 +219,35 @@ const VariantsTemplate = (args: AccordionProps) => (
);
+const CustomAnimationTemplate = (args: AccordionProps) => {
+ const classNames = {
+ content: "ease-soft-spring",
+ };
+
+ return (
+
+
+ {defaultContent}
+
+
+ {defaultContent}
+
+
+ {defaultContent}
+
+
+ );
+};
+
const CustomInidicatorTemplate = (args: AccordionProps) => (
- } title="Anchor">
+ } title="Anchor">
{defaultContent}
- } title="Moon">
+ } title="Moon">
{defaultContent}
- } title="Sun">
+ } title="Sun">
{defaultContent}
@@ -232,14 +261,14 @@ const ControlledTemplate = (args: AccordionProps) => {
return (
-
-
+
+
{defaultContent}
-
+
{defaultContent}
-
+
{defaultContent}
@@ -288,9 +317,9 @@ const CustomWithClassNamesTemplate = (args: AccordionProps) => {
variant="shadow"
>
}
subtitle={
@@ -302,9 +331,9 @@ const CustomWithClassNamesTemplate = (args: AccordionProps) => {
{defaultContent}
}
subtitle="3 apps have read permissions"
title="Apps Permissions"
@@ -312,9 +341,9 @@ const CustomWithClassNamesTemplate = (args: AccordionProps) => {
{defaultContent}
}
subtitle="Complete your profile"
title="Pending tasks"
@@ -322,9 +351,9 @@ const CustomWithClassNamesTemplate = (args: AccordionProps) => {
{defaultContent}
}
subtitle="Please, update now"
title={
@@ -363,13 +392,13 @@ const WithFormTemplate = (args: AccordionProps) => {
return (
-
+
{form}
-
+
{defaultContent}
-
+
{defaultContent}
@@ -398,7 +427,7 @@ export const Multiple = {
args: {
...defaultProps,
- selectionMode: "multiple",
+ allowsMultipleExpanded: "multiple",
},
};
@@ -462,46 +491,10 @@ export const WithForm = {
};
export const CustomMotion = {
- render: Template,
+ render: CustomAnimationTemplate,
args: {
...defaultProps,
- motionProps: {
- variants: {
- enter: {
- y: 0,
- opacity: 1,
- height: "auto",
- transition: {
- height: {
- type: "spring",
- stiffness: 500,
- damping: 30,
- duration: 1,
- },
- opacity: {
- easings: "ease",
- duration: 1,
- },
- },
- },
- exit: {
- y: -10,
- opacity: 0,
- height: 0,
- transition: {
- height: {
- easings: "ease",
- duration: 0.25,
- },
- opacity: {
- easings: "ease",
- duration: 0.3,
- },
- },
- },
- },
- },
},
};
diff --git a/packages/components/disclosure/README.md b/packages/components/disclosure/README.md
new file mode 100644
index 0000000000..6e9d364a6b
--- /dev/null
+++ b/packages/components/disclosure/README.md
@@ -0,0 +1,22 @@
+# @heroui/disclosure
+
+Disclosure displays hidden content that can be revealed or concealed.
+
+## Installation
+
+```sh
+yarn add @heroui/disclosure
+# or
+npm i @heroui/disclosure
+```
+
+## Contribution
+
+Yes please! See the
+[contributing guidelines](https://github.com/nextui-org/nextui/blob/master/CONTRIBUTING.md)
+for details.
+
+## License
+
+This project is licensed under the terms of the
+[MIT license](https://github.com/nextui-org/nextui/blob/master/LICENSE).
diff --git a/packages/components/disclosure/__tests__/disclosure.test.tsx b/packages/components/disclosure/__tests__/disclosure.test.tsx
new file mode 100644
index 0000000000..ad40e41df7
--- /dev/null
+++ b/packages/components/disclosure/__tests__/disclosure.test.tsx
@@ -0,0 +1,19 @@
+import * as React from "react";
+import {render} from "@testing-library/react";
+
+import {Disclosure} from "../src";
+
+describe("Disclosure", () => {
+ it("should render correctly", () => {
+ const wrapper = render();
+
+ expect(() => wrapper.unmount()).not.toThrow();
+ });
+
+ it("ref should be forwarded", () => {
+ const ref = React.createRef();
+
+ render();
+ expect(ref.current).not.toBeNull();
+ });
+});
diff --git a/packages/components/disclosure/package.json b/packages/components/disclosure/package.json
new file mode 100644
index 0000000000..57c23bcee2
--- /dev/null
+++ b/packages/components/disclosure/package.json
@@ -0,0 +1,63 @@
+{
+ "name": "@heroui/disclosure",
+ "version": "2.0.0",
+ "description": "A disclosure is a collapsible section of content.",
+ "keywords": [
+ "disclosure"
+ ],
+ "author": "Junior Garcia ",
+ "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/disclosure"
+ },
+ "bugs": {
+ "url": "https://github.com/nextui-org/nextui/issues"
+ },
+ "scripts": {
+ "build": "tsup src --dts",
+ "build:fast": "tsup src",
+ "dev": "pnpm build:fast --watch",
+ "clean": "rimraf dist .turbo",
+ "typecheck": "tsc --noEmit",
+ "prepack": "clean-package",
+ "postpack": "clean-package restore"
+ },
+ "peerDependencies": {
+ "react": ">=18 || >=19.0.0-rc.0",
+ "react-dom": ">=18 || >=19.0.0-rc.0",
+ "@heroui/theme": ">=2.4.0",
+ "@heroui/system": ">=2.4.0"
+ },
+ "dependencies": {
+ "@heroui/shared-utils": "workspace:*",
+ "@heroui/react-utils": "workspace:*",
+ "@heroui/shared-icons": "workspace:*",
+ "@react-aria/disclosure": "^3.0.0",
+ "@react-aria/button": "^3.11.0",
+ "@react-stately/disclosure": "^3.0.0",
+ "@react-aria/utils": "3.26.0",
+ "@react-aria/focus": "3.19.0"
+ },
+ "devDependencies": {
+ "@heroui/theme": "workspace:*",
+ "@heroui/system": "workspace:*",
+ "@heroui/avatar": "workspace:*",
+ "@heroui/input": "workspace:*",
+ "@heroui/button": "workspace:*",
+ "clean-package": "2.2.0",
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ },
+ "clean-package": "../../../clean-package.config.json"
+}
diff --git a/packages/components/disclosure/src/disclosure.tsx b/packages/components/disclosure/src/disclosure.tsx
new file mode 100644
index 0000000000..87297ba368
--- /dev/null
+++ b/packages/components/disclosure/src/disclosure.tsx
@@ -0,0 +1,73 @@
+import {forwardRef} from "@heroui/system";
+import {ChevronIcon} from "@heroui/shared-icons";
+import {ReactNode, useMemo} from "react";
+
+import {UseDisclosureProps, useDisclosure} from "./use-disclosure";
+
+export interface DisclosureProps extends UseDisclosureProps {}
+
+const Disclosure = forwardRef<"div", DisclosureProps>((props, ref) => {
+ const {
+ Component,
+ HeadingComponent,
+ domRef,
+ slots,
+ classNames,
+ startContent,
+ title,
+ subtitle,
+ children,
+ isExpanded,
+ isDisabled,
+ indicator,
+ hideIndicator,
+ keepContentMounted,
+ getBaseProps,
+ getTriggerProps,
+ getContentProps,
+ getHeadingProps,
+ getTitleProps,
+ getSubtitleProps,
+ getIndicatorProps,
+ } = useDisclosure({...props, ref});
+
+ const indicatorContent = useMemo(() => {
+ if (typeof indicator === "function") {
+ return indicator({indicator: , isExpanded: isExpanded, isDisabled});
+ }
+
+ if (indicator) return indicator;
+
+ return null;
+ }, [indicator, isExpanded, isDisabled]);
+
+ const indicatorComponent = indicatorContent || ;
+
+ return (
+
+
+
+
+
+ {keepContentMounted || isExpanded ? children : null}
+
+
+ );
+});
+
+Disclosure.displayName = "NextUI.Disclosure";
+
+export default Disclosure;
diff --git a/packages/components/disclosure/src/index.ts b/packages/components/disclosure/src/index.ts
new file mode 100644
index 0000000000..f7dc7be37d
--- /dev/null
+++ b/packages/components/disclosure/src/index.ts
@@ -0,0 +1,10 @@
+import Disclosure from "./disclosure";
+
+// export types
+export type {DisclosureProps} from "./disclosure";
+
+// export hooks
+export {useDisclosure as useDisclosureComponent} from "./use-disclosure";
+
+// export component
+export {Disclosure};
diff --git a/packages/components/disclosure/src/use-disclosure.ts b/packages/components/disclosure/src/use-disclosure.ts
new file mode 100644
index 0000000000..524c9093b0
--- /dev/null
+++ b/packages/components/disclosure/src/use-disclosure.ts
@@ -0,0 +1,260 @@
+import type {DisclosureSlots, DisclosureVariantProps, SlotsToClasses} from "@heroui/theme";
+
+import {disclosure} from "@heroui/theme";
+import {As, HTMLHeroUIProps, mapPropsVariants, PropGetter} from "@heroui/system";
+import {ReactRef, useDOMRef} from "@heroui/react-utils";
+import {useDisclosure as useAriaDisclosure} from "@react-aria/disclosure";
+import {DisclosureProps, useDisclosureState} from "@react-stately/disclosure";
+import {ReactNode, useCallback, useMemo, useRef} from "react";
+import {clsx, dataAttr, objectToDeps} from "@heroui/shared-utils";
+import {chain, mergeProps} from "@react-aria/utils";
+import {useButton} from "@react-aria/button";
+import {useFocusRing} from "@react-aria/focus";
+import {usePress, useHover} from "@react-aria/interactions";
+import {PressEvents} from "@react-types/shared";
+
+export type DisclosureIndicatorProps = {
+ /**
+ * The current indicator, usually an arrow icon.
+ */
+ indicator?: ReactNode;
+ /**
+ * The current open status.
+ */
+ isExpanded?: boolean;
+ /**
+ * The current disabled status.
+ * @default false
+ */
+ isDisabled?: boolean;
+};
+
+interface Props extends Omit, "title"> {
+ /**
+ * Ref to the DOM node.
+ */
+ ref?: ReactRef;
+ /**
+ * Start icon to be displayed inside the disclosure.
+ */
+ startContent?: ReactNode;
+ /**
+ * The accordion item `expanded` indicator, it's usually an arrow icon.
+ * If you pass a function, NextUI will expose the current indicator and the open status,
+ * In case you want to use a custom indicator or modify the current one.
+ */
+ indicator?: ReactNode | ((props: DisclosureIndicatorProps) => ReactNode) | null;
+ /**
+ * Customizable heading tag for Web accessibility:
+ * use headings to describe content and use them consistently and semantically.
+ * This will help all users to better find the content they are looking for.
+ */
+ HeadingComponent?: As;
+ /**
+ * The content of the component.
+ */
+ children?: ReactNode | null;
+ title?: ReactNode | string;
+ subtitle?: ReactNode | string;
+ classNames?: SlotsToClasses;
+ keepContentMounted?: boolean;
+}
+
+export type UseDisclosureProps = Props & DisclosureVariantProps & DisclosureProps & PressEvents;
+
+export function useDisclosure(originalProps: UseDisclosureProps) {
+ const [props, variantProps] = mapPropsVariants(originalProps, disclosure.variantKeys);
+ const {
+ ref,
+ as,
+ className,
+ defaultExpanded,
+ onExpandedChange,
+ classNames,
+ title,
+ subtitle,
+ startContent,
+ children,
+ HeadingComponent = as || "h2",
+ indicator,
+ onPress,
+ onPressStart,
+ onPressEnd,
+ onPressChange,
+ onPressUp,
+ onClick,
+ } = props;
+
+ const Component = as || "div";
+ const domRef = useDOMRef(ref);
+ const {
+ isDisabled,
+ isExpanded: isExpandedProp,
+ isCompact = false,
+ hideIndicator = false,
+ disableIndicatorAnimation = false,
+ disableAnimation = false,
+ hidden = false,
+ keepContentMounted = false,
+ } = originalProps;
+
+ const slots = useMemo(
+ () =>
+ disclosure({
+ ...variantProps,
+ disableAnimation,
+ disableIndicatorAnimation,
+ isCompact,
+ className,
+ }),
+ [objectToDeps(variantProps), className, disableAnimation, disableIndicatorAnimation, isCompact],
+ );
+
+ const state = useDisclosureState({
+ isExpanded: isExpandedProp,
+ defaultExpanded,
+ onExpandedChange: (isExpanded) => {
+ onExpandedChange?.(isExpanded);
+ },
+ });
+ const isExpanded = state.isExpanded;
+
+ const {isFocused, isFocusVisible, focusProps} = useFocusRing({
+ autoFocus: originalProps?.autoFocus,
+ });
+
+ const triggerRef = useRef(null);
+ const contentRef = useRef(null);
+
+ const {buttonProps: triggerProps, panelProps: contentProps} = useAriaDisclosure(
+ props,
+ state,
+ contentRef,
+ );
+
+ const {buttonProps} = useButton(triggerProps, triggerRef);
+
+ const {pressProps, isPressed} = usePress({
+ ref: domRef,
+ isDisabled,
+ onPress,
+ onPressStart,
+ onPressEnd,
+ onPressChange,
+ onPressUp,
+ });
+ const {isHovered, hoverProps} = useHover({isDisabled});
+
+ const getBaseProps = useCallback(
+ (props = {}) => ({
+ className: slots.base({class: clsx(classNames?.base, props?.className)}),
+ ...props,
+ }),
+ [],
+ );
+
+ const getHeadingProps = useCallback(
+ (props = {}) => ({
+ className: slots.heading({class: clsx(classNames?.heading, props?.className)}),
+ ...props,
+ }),
+ [],
+ );
+
+ const getTriggerProps = useCallback(
+ (props = {}) => ({
+ ref: triggerRef,
+ className: slots.trigger({class: clsx(classNames?.trigger, props?.className)}),
+ "aria-expanded": isExpanded,
+ "data-expanded": isExpanded,
+ "data-pressed": dataAttr(isPressed),
+ "data-hover": dataAttr(isHovered),
+ "data-focus": dataAttr(isFocused),
+ "data-disabled": dataAttr(isDisabled),
+ "data-focus-visible": dataAttr(isFocusVisible),
+ onFocus: chain(originalProps.onFocus, focusProps.onFocus),
+ onBlur: chain(originalProps.onBlur, focusProps.onBlur),
+ ...mergeProps(buttonProps, props, focusProps, hoverProps, pressProps, {
+ onClick: chain(pressProps.onClick, onClick),
+ }),
+ disabled: isDisabled,
+ hidden,
+ }),
+ [triggerProps, focusProps, pressProps, isExpanded, isDisabled, hidden],
+ );
+
+ const getContentProps = useCallback(
+ (props = {}) => ({
+ ref: contentRef,
+ className: slots.content({class: clsx(classNames?.content, props?.className)}),
+ "data-expanded": dataAttr(isExpanded),
+ ...mergeProps(contentProps, props),
+ }),
+ [contentProps, contentRef, isExpanded],
+ );
+
+ const getTitleProps = useCallback(
+ (props = {}) => {
+ return {
+ "data-expanded": dataAttr(isExpanded),
+ "data-disabled": dataAttr(isDisabled),
+ className: slots.title({class: classNames?.title}),
+ ...props,
+ };
+ },
+ [slots, classNames?.title, isExpanded, isDisabled],
+ );
+
+ const getSubtitleProps = useCallback(
+ (props = {}) => {
+ return {
+ "data-expanded": dataAttr(isExpanded),
+ "data-disabled": dataAttr(isDisabled),
+ className: slots.subtitle({class: classNames?.subtitle}),
+ ...props,
+ };
+ },
+ [slots, classNames?.subtitle, isExpanded, isDisabled],
+ );
+
+ const getIndicatorProps = useCallback(
+ (props = {}) => {
+ return {
+ "aria-hidden": dataAttr(true),
+ "data-expanded": dataAttr(isExpanded),
+ "data-disabled": dataAttr(isDisabled),
+ className: slots.indicator({class: classNames?.indicator}),
+ ...props,
+ };
+ },
+ [slots, classNames?.indicator, isExpanded, isDisabled, classNames?.indicator],
+ );
+
+ return {
+ Component,
+ HeadingComponent,
+ domRef,
+ startContent,
+ classNames,
+ slots,
+ title,
+ subtitle,
+ children,
+ isExpanded,
+ isDisabled,
+ indicator,
+ hideIndicator,
+ contentRef,
+ keepContentMounted,
+ getBaseProps,
+ getTriggerProps,
+ getContentProps,
+ getHeadingProps,
+ getTitleProps,
+ getSubtitleProps,
+ getIndicatorProps,
+ state,
+ };
+}
+
+export type UseDisclosureReturn = ReturnType;
diff --git a/packages/components/disclosure/stories/disclosure.stories.tsx b/packages/components/disclosure/stories/disclosure.stories.tsx
new file mode 100644
index 0000000000..1a3fc2739a
--- /dev/null
+++ b/packages/components/disclosure/stories/disclosure.stories.tsx
@@ -0,0 +1,252 @@
+import React from "react";
+import {Meta} from "@storybook/react";
+import {button, disclosure} from "@heroui/theme";
+import {Avatar} from "@heroui/avatar";
+import {Button} from "@heroui/button";
+import {Input, Textarea} from "@heroui/input";
+import {MonitorMobileIcon, MoonIcon} from "@heroui/shared-icons";
+
+import {Disclosure, DisclosureProps} from "../src";
+
+export default {
+ title: "Components/Disclosure",
+ component: Disclosure,
+ argTypes: {
+ isDisabled: {
+ control: {
+ type: "boolean",
+ },
+ },
+ },
+} as Meta;
+
+const defaultProps = {
+ ...disclosure.defaultVariants,
+};
+
+const defaultContent =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamcolaboris nisi ut aliquip ex ea commodo consequat.";
+
+const Template = (args: DisclosureProps) => (
+
+ {defaultContent}
+
+);
+
+const TemplateWithStartContent = () => (
+
+ }
+ subtitle="4 unread messages"
+ title="Chung Miller"
+ >
+ {defaultContent}
+
+);
+
+const WithFormTemplate = (args: DisclosureProps) => {
+ const form = (
+
+ );
+
+ return (
+
+ {form}
+
+ );
+};
+
+const CustomAnimationTemplate = (args: DisclosureProps) => {
+ const classNames = {
+ content: "ease-soft-spring",
+ };
+
+ return (
+ <>
+
+ {defaultContent}
+
+ >
+ );
+};
+
+const CustomInidicatorTemplate = () => (
+ } title="Moon Icon Disclosure">
+ {defaultContent}
+
+);
+
+const ControlledTemplate = () => {
+ const [isExpanded, onExpandedChange] = React.useState(false);
+
+ return (
+
+
+
+
+
+ {defaultContent}
+
+
+ );
+};
+
+const CustomWithClassNamesTemplate = () => {
+ const classNames: DisclosureProps["classNames"] = {
+ base: "py-0 w-full",
+ title: "font-normal text-base",
+ trigger: "data-[hover=true]:bg-default-100 rounded-lg h-14 flex items-center border p-4",
+ indicator: "text-base",
+ content: "text-sm px-2",
+ };
+
+ return (
+ }
+ subtitle={
+
+ 2 issues to fix now
+
+ }
+ title="Connected devices"
+ >
+ {defaultContent}
+
+ );
+};
+
+export const Default = {
+ render: Template,
+ args: {
+ ...defaultProps,
+ },
+};
+
+export const WithSubtitle = {
+ render: Template,
+ args: {
+ ...defaultProps,
+ subtitle: "disclosure subtitle",
+ },
+};
+
+export const IsCompact = {
+ render: Template,
+
+ args: {
+ ...defaultProps,
+ isCompact: true,
+ },
+};
+
+export const DefaultExpanded = {
+ render: Template,
+
+ args: {
+ ...defaultProps,
+ defaultExpanded: true,
+ },
+};
+
+export const KeepContentMounted = {
+ render: Template,
+
+ args: {
+ ...defaultProps,
+ keepContentMounted: true,
+ },
+};
+
+export const isDisabled = {
+ render: Template,
+
+ args: {
+ ...defaultProps,
+ isDisabled: true,
+ },
+};
+
+export const WithStartContent = {
+ render: TemplateWithStartContent,
+
+ args: {
+ ...defaultProps,
+ },
+};
+
+export const WithForm = {
+ render: WithFormTemplate,
+
+ args: {
+ ...defaultProps,
+ },
+};
+
+export const CustomMotion = {
+ render: CustomAnimationTemplate,
+
+ args: {
+ ...defaultProps,
+ },
+};
+
+export const CustomIndicator = {
+ render: CustomInidicatorTemplate,
+
+ args: {
+ ...defaultProps,
+ },
+};
+
+export const Controlled = {
+ render: ControlledTemplate,
+
+ args: {
+ ...defaultProps,
+ },
+};
+
+export const CustomWithClassNames = {
+ render: CustomWithClassNamesTemplate,
+
+ args: {
+ ...defaultProps,
+ },
+};
diff --git a/packages/components/disclosure/tsconfig.json b/packages/components/disclosure/tsconfig.json
new file mode 100644
index 0000000000..5d012f6e61
--- /dev/null
+++ b/packages/components/disclosure/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "tailwind-variants": ["../../../node_modules/tailwind-variants"]
+ },
+ },
+ "include": ["src", "index.ts"]
+}
diff --git a/packages/components/disclosure/tsup.config.ts b/packages/components/disclosure/tsup.config.ts
new file mode 100644
index 0000000000..3e2bcff6cc
--- /dev/null
+++ b/packages/components/disclosure/tsup.config.ts
@@ -0,0 +1,8 @@
+import {defineConfig} from "tsup";
+
+export default defineConfig({
+ clean: true,
+ target: "es2019",
+ format: ["cjs", "esm"],
+ banner: {js: '"use client";'},
+});
diff --git a/packages/core/react/package.json b/packages/core/react/package.json
index 8c2dd99e31..977deb51f4 100644
--- a/packages/core/react/package.json
+++ b/packages/core/react/package.json
@@ -89,6 +89,7 @@
"@heroui/drawer": "workspace:*",
"@heroui/form": "workspace:*",
"@heroui/alert": "workspace:*",
+ "@heroui/disclosure": "workspace:*",
"@react-aria/visually-hidden": "3.8.18"
},
"peerDependencies": {
diff --git a/packages/core/react/src/index.ts b/packages/core/react/src/index.ts
index 2b29b0ac5e..048f79b3e8 100644
--- a/packages/core/react/src/index.ts
+++ b/packages/core/react/src/index.ts
@@ -47,6 +47,7 @@ export * from "@heroui/form";
export * from "@heroui/alert";
export * from "@heroui/drawer";
export * from "@heroui/input-otp";
+export * from "@heroui/disclosure";
/**
* React Aria - Exports
diff --git a/packages/core/theme/src/components/accordion.ts b/packages/core/theme/src/components/accordion.ts
index cf5e3b7ce5..85ff068848 100644
--- a/packages/core/theme/src/components/accordion.ts
+++ b/packages/core/theme/src/components/accordion.ts
@@ -1,7 +1,6 @@
import type {VariantProps} from "tailwind-variants";
import {tv} from "../utils/tv";
-import {dataFocusVisibleClasses} from "../utils";
/**
* Accordion wrapper **Tailwind Variants** component
@@ -32,100 +31,6 @@ const accordion = tv({
},
});
-/**
- * AccordionItem wrapper **Tailwind Variants** component
- *
- * const {base, heading, indicator, trigger, startContent, title, subtitle, content } = accordionItem({...})
- *
- * @example
- *
- *
- *
- *
- *
Content
- *
- */
-const accordionItem = tv({
- slots: {
- base: "",
- heading: "",
- trigger: [
- "flex py-4 w-full h-full gap-3 outline-none items-center tap-highlight-transparent",
- // focus ring
- ...dataFocusVisibleClasses,
- ],
- startContent: "flex-shrink-0",
- indicator: "text-default-400",
- titleWrapper: "flex-1 flex flex-col text-start",
- title: "text-foreground text-medium",
- subtitle: "text-small text-foreground-500 font-normal",
- content: "py-2",
- },
- variants: {
- variant: {
- splitted: {
- base: "px-4 bg-content1 shadow-medium rounded-medium",
- },
- },
- isCompact: {
- true: {
- trigger: "py-2",
- title: "text-medium",
- subtitle: "text-small",
- indicator: "text-medium",
- content: "py-1",
- },
- },
- isDisabled: {
- true: {
- base: "opacity-disabled pointer-events-none",
- },
- },
- hideIndicator: {
- true: {
- indicator: "hidden",
- },
- },
- disableAnimation: {
- true: {
- content: "hidden data-[open=true]:block",
- },
- false: {
- indicator: "transition-transform",
- trigger: "transition-opacity",
- },
- },
- disableIndicatorAnimation: {
- true: {
- indicator: "transition-none",
- },
- false: {
- indicator:
- "rotate-0 data-[open=true]:-rotate-90 rtl:-rotate-180 rtl:data-[open=true]:-rotate-90",
- },
- },
- },
- defaultVariants: {
- size: "md",
- radius: "lg",
- isDisabled: false,
- hideIndicator: false,
- disableIndicatorAnimation: false,
- },
-});
-
export type AccordionGroupVariantProps = VariantProps;
-export type AccordionItemVariantProps = VariantProps;
-export type AccordionItemSlots = keyof ReturnType;
-
-export {accordion, accordionItem};
+export {accordion};
diff --git a/packages/core/theme/src/components/disclosure.ts b/packages/core/theme/src/components/disclosure.ts
new file mode 100644
index 0000000000..8aa8a0785f
--- /dev/null
+++ b/packages/core/theme/src/components/disclosure.ts
@@ -0,0 +1,77 @@
+import {tv, VariantProps} from "tailwind-variants";
+
+import {dataFocusVisibleClasses} from "../utils";
+
+const disclosure = tv({
+ slots: {
+ base: "w-full",
+ heading: "",
+ trigger: [
+ "flex py-4 w-full h-full gap-3 outline-none items-center tap-highlight-transparent",
+ // focus ring
+ ...dataFocusVisibleClasses,
+ ],
+ startContent: "flex-shrink-0",
+ indicator: "text-default-400",
+ titleWrapper: "flex-1 flex flex-col text-start select-none",
+ title: "text-foreground text-medium",
+ subtitle: "text-small text-foreground-500 font-normal",
+ content: [
+ "overflow-hidden ease-in opacity-0 data-[expanded=true]:opacity-100 data-[expanded=true]:duration-300 data-[expanded=true]:my-2",
+ ],
+ },
+ variants: {
+ variant: {
+ splitted: {
+ base: "px-4 bg-content1 shadow-medium rounded-medium",
+ },
+ },
+ isCompact: {
+ true: {
+ trigger: "py-2",
+ title: "text-medium",
+ subtitle: "text-small",
+ indicator: "text-medium",
+ },
+ },
+ isDisabled: {
+ true: {
+ base: "opacity-disabled pointer-events-none",
+ },
+ },
+ hideIndicator: {
+ true: {
+ indicator: "hidden",
+ },
+ },
+ disableAnimation: {
+ true: {
+ content: "",
+ },
+ false: {
+ indicator: "transition-transform",
+ trigger: "transition-opacity",
+ content: "transition-all",
+ },
+ },
+ disableIndicatorAnimation: {
+ true: {
+ indicator: "transition-none",
+ },
+ false: {
+ indicator:
+ "rotate-0 data-[expanded=true]:-rotate-90 rtl:-rotate-180 rtl:data-[expanded=true]:-rotate-90",
+ },
+ },
+ },
+ defaultVariants: {
+ isDisabled: false,
+ hideIndicator: false,
+ disableIndicatorAnimation: false,
+ },
+});
+
+export type DisclosureVariantProps = VariantProps;
+export type DisclosureSlots = keyof ReturnType;
+
+export {disclosure};
diff --git a/packages/core/theme/src/components/index.ts b/packages/core/theme/src/components/index.ts
index dee45f4401..5eb308f3cc 100644
--- a/packages/core/theme/src/components/index.ts
+++ b/packages/core/theme/src/components/index.ts
@@ -41,3 +41,4 @@ export * from "./date-picker";
export * from "./alert";
export * from "./drawer";
export * from "./form";
+export * from "./disclosure";
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f8f7af6282..8626681544 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -635,6 +635,9 @@ importers:
'@heroui/aria-utils':
specifier: workspace:*
version: link:../../utilities/aria-utils
+ '@heroui/disclosure':
+ specifier: workspace:*
+ version: link:../disclosure
'@heroui/divider':
specifier: workspace:*
version: link:../divider
@@ -668,9 +671,9 @@ importers:
'@react-aria/utils':
specifier: 3.26.0
version: 3.26.0(react@18.2.0)
- '@react-stately/tree':
- specifier: 3.8.6
- version: 3.8.6(react@18.2.0)
+ '@react-stately/disclosure':
+ specifier: ^3.0.0
+ version: 3.0.1(react@18.2.0)
'@react-types/accordion':
specifier: 3.0.0-alpha.25
version: 3.0.0-alpha.25(react@18.2.0)
@@ -1475,6 +1478,58 @@ importers:
specifier: 18.2.0
version: 18.2.0(react@18.2.0)
+ packages/components/disclosure:
+ dependencies:
+ '@heroui/react-utils':
+ specifier: workspace:*
+ version: link:../../utilities/react-utils
+ '@heroui/shared-icons':
+ specifier: workspace:*
+ version: link:../../utilities/shared-icons
+ '@heroui/shared-utils':
+ specifier: workspace:*
+ version: link:../../utilities/shared-utils
+ '@react-aria/button':
+ specifier: ^3.11.0
+ version: 3.11.0(react@18.2.0)
+ '@react-aria/disclosure':
+ specifier: ^3.0.0
+ version: 3.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ '@react-aria/focus':
+ specifier: 3.19.0
+ version: 3.19.0(react@18.2.0)
+ '@react-aria/utils':
+ specifier: 3.26.0
+ version: 3.26.0(react@18.2.0)
+ '@react-stately/disclosure':
+ specifier: ^3.0.0
+ version: 3.0.1(react@18.2.0)
+ devDependencies:
+ '@heroui/avatar':
+ specifier: workspace:*
+ version: link:../avatar
+ '@heroui/button':
+ specifier: workspace:*
+ version: link:../button
+ '@heroui/input':
+ specifier: workspace:*
+ version: link:../input
+ '@heroui/system':
+ specifier: workspace:*
+ version: link:../../core/system
+ '@heroui/theme':
+ specifier: workspace:*
+ version: link:../../core/theme
+ clean-package:
+ specifier: 2.2.0
+ version: 2.2.0
+ react:
+ specifier: 18.2.0
+ version: 18.2.0
+ react-dom:
+ specifier: 18.2.0
+ version: 18.2.0(react@18.2.0)
+
packages/components/divider:
dependencies:
'@heroui/react-rsc-utils':
@@ -3142,6 +3197,9 @@ importers:
'@heroui/date-picker':
specifier: workspace:*
version: link:../../components/date-picker
+ '@heroui/disclosure':
+ specifier: workspace:*
+ version: link:../../components/disclosure
'@heroui/divider':
specifier: workspace:*
version: link:../../components/divider
@@ -6710,6 +6768,12 @@ packages:
react: 18.2.0
react-dom: 18.2.0
+ '@react-aria/disclosure@3.0.1':
+ resolution: {integrity: sha512-rNH8RFcePoAQizcqB7KuHbBOr7sPsysFKCUwbVSOXLPgvCfXKafIhjgFJVqekfsbn5zWvkcTupnzGVJj/F9p+g==}
+ peerDependencies:
+ react: 18.2.0
+ react-dom: 18.2.0
+
'@react-aria/focus@3.19.0':
resolution: {integrity: sha512-hPF9EXoUQeQl1Y21/rbV2H4FdUR2v+4/I0/vB+8U3bT1CJ+1AFj1hc/rqx2DqEwDlEwOHN+E4+mRahQmlybq0A==}
peerDependencies:
@@ -6842,6 +6906,12 @@ packages:
peerDependencies:
react: 18.2.0
+ '@react-aria/utils@3.27.0':
+ resolution: {integrity: sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==}
+ peerDependencies:
+ react: 18.2.0
+ react-dom: 18.2.0
+
'@react-aria/virtualizer@4.1.0':
resolution: {integrity: sha512-ziSq3Y7iuaAMJWGZU1RRs/TykuPapQfx8dyH2eyKPLgEjBUoXRGWE7n6jklBwal14b0lPK0wkCzRoQbkUvX3cg==}
peerDependencies:
@@ -6896,6 +6966,11 @@ packages:
peerDependencies:
react: 18.2.0
+ '@react-stately/disclosure@3.0.1':
+ resolution: {integrity: sha512-afpNy5b0UcqRGjU/W5OD0xkx4PbymvhMrgQZ4o4OdtDVMMvr9T5UqMF8/j3J591DxgQfXM872tJu0kotqT0L6Q==}
+ peerDependencies:
+ react: 18.2.0
+
'@react-stately/flags@3.0.5':
resolution: {integrity: sha512-6wks4csxUwPCp23LgJSnkBRhrWpd9jGd64DjcCTNB2AHIFu7Ab1W59pJpUL6TW7uAxVxdNKjgn6D1hlBy8qWsA==}
@@ -6999,6 +7074,11 @@ packages:
peerDependencies:
react: 18.2.0
+ '@react-types/button@3.10.2':
+ resolution: {integrity: sha512-h8SB/BLoCgoBulCpyzaoZ+miKXrolK9XC48+n1dKJXT8g4gImrficurDW6+PRTQWaRai0Q0A6bu8UibZOU4syg==}
+ peerDependencies:
+ react: 18.2.0
+
'@react-types/calendar@3.5.0':
resolution: {integrity: sha512-O3IRE7AGwAWYnvJIJ80cOy7WwoJ0m8GtX/qSmvXQAjC4qx00n+b5aFNBYAQtcyc3RM5QpW6obs9BfwGetFiI8w==}
peerDependencies:
@@ -7074,6 +7154,11 @@ packages:
peerDependencies:
react: 18.2.0
+ '@react-types/shared@3.27.0':
+ resolution: {integrity: sha512-gvznmLhi6JPEf0bsq7SwRYTHAKKq/wcmKqFez9sRdbED+SPMUmK5omfZ6w3EwUFQHbYUa4zPBYedQ7Knv70RMw==}
+ peerDependencies:
+ react: 18.2.0
+
'@react-types/slider@3.7.7':
resolution: {integrity: sha512-lYTR9zXQV2fSEm/G3gwDENWiki1IXd/oorsgf0zu1DBi2SQDbOsLsGUXiwvD24Xy6OkUuhAqjLPPexezo7+u9g==}
peerDependencies:
@@ -18865,6 +18950,16 @@ snapshots:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
+ '@react-aria/disclosure@3.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+ dependencies:
+ '@react-aria/ssr': 3.9.7(react@18.2.0)
+ '@react-aria/utils': 3.27.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ '@react-stately/disclosure': 3.0.1(react@18.2.0)
+ '@react-types/button': 3.10.2(react@18.2.0)
+ '@swc/helpers': 0.5.15
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+
'@react-aria/focus@3.19.0(react@18.2.0)':
dependencies:
'@react-aria/interactions': 3.22.5(react@18.2.0)
@@ -19151,6 +19246,16 @@ snapshots:
clsx: 2.1.1
react: 18.2.0
+ '@react-aria/utils@3.27.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+ dependencies:
+ '@react-aria/ssr': 3.9.7(react@18.2.0)
+ '@react-stately/utils': 3.10.5(react@18.2.0)
+ '@react-types/shared': 3.27.0(react@18.2.0)
+ '@swc/helpers': 0.5.15
+ clsx: 2.1.1
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+
'@react-aria/virtualizer@4.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@react-aria/i18n': 3.12.4(react@18.2.0)
@@ -19253,6 +19358,13 @@ snapshots:
'@swc/helpers': 0.5.15
react: 18.2.0
+ '@react-stately/disclosure@3.0.1(react@18.2.0)':
+ dependencies:
+ '@react-stately/utils': 3.10.5(react@18.2.0)
+ '@react-types/shared': 3.27.0(react@18.2.0)
+ '@swc/helpers': 0.5.15
+ react: 18.2.0
+
'@react-stately/flags@3.0.5':
dependencies:
'@swc/helpers': 0.5.15
@@ -19415,6 +19527,11 @@ snapshots:
'@react-types/shared': 3.26.0(react@18.2.0)
react: 18.2.0
+ '@react-types/button@3.10.2(react@18.2.0)':
+ dependencies:
+ '@react-types/shared': 3.27.0(react@18.2.0)
+ react: 18.2.0
+
'@react-types/calendar@3.5.0(react@18.2.0)':
dependencies:
'@internationalized/date': 3.6.0
@@ -19495,6 +19612,10 @@ snapshots:
dependencies:
react: 18.2.0
+ '@react-types/shared@3.27.0(react@18.2.0)':
+ dependencies:
+ react: 18.2.0
+
'@react-types/slider@3.7.7(react@18.2.0)':
dependencies:
'@react-types/shared': 3.26.0(react@18.2.0)