mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
feat(accordion): stories improved
This commit is contained in:
parent
7e1b49bad8
commit
976a3d52aa
@ -1,11 +1,15 @@
|
||||
import * as React from "react";
|
||||
import {render} from "@testing-library/react";
|
||||
|
||||
import {Accordion} from "../src";
|
||||
import {Accordion, AccordionItem} from "../src";
|
||||
|
||||
describe("Accordion", () => {
|
||||
it("should render correctly", () => {
|
||||
const wrapper = render(<Accordion />);
|
||||
const wrapper = render(
|
||||
<Accordion>
|
||||
<AccordionItem>Accordion Item</AccordionItem>
|
||||
</Accordion>,
|
||||
);
|
||||
|
||||
expect(() => wrapper.unmount()).not.toThrow();
|
||||
});
|
||||
@ -13,7 +17,11 @@ describe("Accordion", () => {
|
||||
it("ref should be forwarded", () => {
|
||||
const ref = React.createRef<HTMLDivElement>();
|
||||
|
||||
render(<Accordion ref={ref} />);
|
||||
render(
|
||||
<Accordion ref={ref}>
|
||||
<AccordionItem>Accordion Item</AccordionItem>
|
||||
</Accordion>,
|
||||
);
|
||||
expect(ref.current).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@ -26,12 +26,16 @@ export type AccordionItemIndicatorProps = {
|
||||
};
|
||||
|
||||
export interface Props<T extends object = {}>
|
||||
extends Omit<ItemProps<"button", T>, "children" | keyof FocusableProps>,
|
||||
extends Omit<ItemProps<"button", T>, "children" | "title" | keyof FocusableProps>,
|
||||
FocusableProps {
|
||||
/**
|
||||
* The content of the component.
|
||||
*/
|
||||
children?: ReactNode | null;
|
||||
/**
|
||||
* The accordion item title.
|
||||
*/
|
||||
title?: ReactNode | string;
|
||||
/**
|
||||
* The accordion item subtitle.
|
||||
*/
|
||||
|
||||
@ -6,6 +6,7 @@ export type {AccordionProps} from "./accordion";
|
||||
export type {AccordionItemProps} from "./accordion-item";
|
||||
export type {AccordionItemIndicatorProps} from "./base/accordion-item-base";
|
||||
export type {AccordionItemBaseProps} from "./base/accordion-item-base";
|
||||
export type {Selection} from "@react-types/shared";
|
||||
|
||||
// export hooks
|
||||
export {useAccordionItem} from "./use-accordion-item";
|
||||
|
||||
@ -128,17 +128,20 @@ export function useAccordionItem<T extends object = {}>(props: UseAccordionItemP
|
||||
const getBaseProps = useCallback<PropGetter>(
|
||||
(props = {}) => {
|
||||
return {
|
||||
"data-open": dataAttr(isOpen),
|
||||
"data-disabled": dataAttr(isDisabled),
|
||||
className: slots.base({class: baseStyles}),
|
||||
...mergeProps(otherProps, props),
|
||||
};
|
||||
},
|
||||
[baseStyles, otherProps],
|
||||
[baseStyles, otherProps, slots, isOpen, isDisabled],
|
||||
);
|
||||
|
||||
const getButtonProps = useCallback<PropGetter>(
|
||||
(props = {}) => {
|
||||
return {
|
||||
ref: domRef,
|
||||
"data-open": dataAttr(isOpen),
|
||||
"data-focus-visible": dataAttr(isFocusVisible),
|
||||
"data-focused": dataAttr(isFocused),
|
||||
"data-disabled": dataAttr(isDisabled),
|
||||
@ -160,7 +163,7 @@ export function useAccordionItem<T extends object = {}>(props: UseAccordionItemP
|
||||
...mergeProps(buttonProps, props),
|
||||
};
|
||||
},
|
||||
[domRef, isFocusVisible, isDisabled, isFocused, buttonProps, focusProps, slots, styles],
|
||||
[domRef, isOpen, isFocusVisible, isDisabled, isFocused, buttonProps, focusProps, slots, styles],
|
||||
);
|
||||
|
||||
const getContentProps = useCallback<PropGetter>(
|
||||
@ -191,31 +194,37 @@ export function useAccordionItem<T extends object = {}>(props: UseAccordionItemP
|
||||
const getHeadingProps = useCallback<PropGetter>(
|
||||
(props = {}) => {
|
||||
return {
|
||||
"data-open": dataAttr(isOpen),
|
||||
"data-disabled": dataAttr(isDisabled),
|
||||
className: slots.heading({class: styles?.heading}),
|
||||
...props,
|
||||
};
|
||||
},
|
||||
[slots, styles],
|
||||
[slots, styles, isOpen, isDisabled],
|
||||
);
|
||||
|
||||
const getTitleProps = useCallback<PropGetter>(
|
||||
(props = {}) => {
|
||||
return {
|
||||
"data-open": dataAttr(isOpen),
|
||||
"data-disabled": dataAttr(isDisabled),
|
||||
className: slots.title({class: styles?.title}),
|
||||
...props,
|
||||
};
|
||||
},
|
||||
[slots, styles],
|
||||
[slots, styles, isOpen, isDisabled],
|
||||
);
|
||||
|
||||
const getSubtitleProps = useCallback<PropGetter>(
|
||||
(props = {}) => {
|
||||
return {
|
||||
"data-open": dataAttr(isOpen),
|
||||
"data-disabled": dataAttr(isDisabled),
|
||||
className: slots.subtitle({class: styles?.subtitle}),
|
||||
...props,
|
||||
};
|
||||
},
|
||||
[slots, styles],
|
||||
[slots, styles, isOpen, isDisabled],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@ -1,10 +1,19 @@
|
||||
import React from "react";
|
||||
import {ComponentStory, ComponentMeta} from "@storybook/react";
|
||||
import {accordionItem, link} from "@nextui-org/theme";
|
||||
import {AnchorIcon, MoonIcon, SunIcon} from "@nextui-org/shared-icons";
|
||||
import {
|
||||
AnchorIcon,
|
||||
MoonIcon,
|
||||
SunIcon,
|
||||
InfoIcon,
|
||||
ShieldSecurityIcon,
|
||||
MonitorMobileIcon,
|
||||
InvalidCardIcon,
|
||||
} from "@nextui-org/shared-icons";
|
||||
import {Avatar} from "@nextui-org/avatar";
|
||||
|
||||
import {Accordion, AccordionProps, AccordionItem} from "../src";
|
||||
import {Accordion, AccordionProps, AccordionItem, Selection} from "../src";
|
||||
import {AccordionItemProps} from "../src";
|
||||
|
||||
export default {
|
||||
title: "Components/Accordion",
|
||||
@ -21,6 +30,12 @@ export default {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
selectionMode: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: ["single", "multiple"],
|
||||
},
|
||||
},
|
||||
disableAnimation: {
|
||||
control: {
|
||||
type: "boolean",
|
||||
@ -118,7 +133,10 @@ const TemplateWithLeftIndicator: ComponentStory<typeof Accordion> = (args: Accor
|
||||
subtitle={
|
||||
<p>
|
||||
2 issues to{" "}
|
||||
<a className={link()} href="/?path=/story/components-accordion--with-left-indicator">
|
||||
<a
|
||||
className={link({size: "sm"})}
|
||||
href="/?path=/story/components-accordion--with-left-indicator"
|
||||
>
|
||||
fix now
|
||||
</a>
|
||||
</p>
|
||||
@ -205,6 +223,98 @@ const CustomInidicatorTemplate: ComponentStory<typeof Accordion> = (args: Accord
|
||||
</Accordion>
|
||||
);
|
||||
|
||||
const ControlledTemplate: ComponentStory<typeof Accordion> = (args: AccordionProps) => {
|
||||
const [selectedKeys, setSelectedKeys] = React.useState<Selection>(new Set(["1"]));
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(selectedKeys);
|
||||
|
||||
return (
|
||||
<Accordion {...args} selectedKeys={selectedKeys} onSelectionChange={setSelectedKeys}>
|
||||
<AccordionItem key="1" title="Accordion 1">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="2" title="Accordion 2">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem key="3" title="Accordion 3">
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomWithStylesTemplate: ComponentStory<typeof Accordion> = (args: AccordionProps) => {
|
||||
const itemStyles: AccordionItemProps["styles"] = {
|
||||
base: "py-0 w-full",
|
||||
title: "font-normal text-base",
|
||||
trigger: "px-2 py-0 hover:bg-neutral-100 rounded-lg h-14 flex items-center",
|
||||
indicator: "text-base",
|
||||
content: "text-sm px-2",
|
||||
};
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
{...args}
|
||||
hideDivider
|
||||
className="p-2 flex flex-col gap-1 w-full max-w-[300px]"
|
||||
variant="shadow"
|
||||
>
|
||||
<AccordionItem
|
||||
key="1"
|
||||
leftIndicator={<MonitorMobileIcon className="text-primary" />}
|
||||
styles={itemStyles}
|
||||
subtitle={
|
||||
<p>
|
||||
2 issues to{" "}
|
||||
<a
|
||||
className={link({size: "sm"})}
|
||||
href="/?path=/story/components-accordion--custom-with-styles"
|
||||
>
|
||||
fix now
|
||||
</a>
|
||||
</p>
|
||||
}
|
||||
title="Connected devices"
|
||||
>
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem
|
||||
key="2"
|
||||
leftIndicator={<ShieldSecurityIcon />}
|
||||
styles={itemStyles}
|
||||
subtitle="3 apps have read permissions"
|
||||
title="Apps Permissions"
|
||||
>
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem
|
||||
key="3"
|
||||
leftIndicator={<InfoIcon className="text-warning" />}
|
||||
styles={{...itemStyles, subtitle: "text-warning"}}
|
||||
subtitle="Complete your profile"
|
||||
title="Pending tasks"
|
||||
>
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
<AccordionItem
|
||||
key="4"
|
||||
leftIndicator={<InvalidCardIcon className="text-danger" />}
|
||||
styles={{...itemStyles, subtitle: "text-danger"}}
|
||||
subtitle="Please, update now"
|
||||
title={
|
||||
<p className="flex gap-1 items-center">
|
||||
Card expired
|
||||
<p className="text-neutral-400 text-sm">*4812</p>
|
||||
</p>
|
||||
}
|
||||
>
|
||||
{defaultContent}
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
...defaultProps,
|
||||
@ -271,3 +381,13 @@ export const CustomIndicator = CustomInidicatorTemplate.bind({});
|
||||
CustomIndicator.args = {
|
||||
...defaultProps,
|
||||
};
|
||||
|
||||
export const Controlled = ControlledTemplate.bind({});
|
||||
Controlled.args = {
|
||||
...defaultProps,
|
||||
};
|
||||
|
||||
export const CustomWithStyles = CustomWithStylesTemplate.bind({});
|
||||
CustomWithStyles.args = {
|
||||
...defaultProps,
|
||||
};
|
||||
|
||||
@ -14,3 +14,7 @@ export * from "./moon";
|
||||
export * from "./moon-filled";
|
||||
export * from "./headphones";
|
||||
export * from "./anchor";
|
||||
export * from "./info";
|
||||
export * from "./shield-security";
|
||||
export * from "./monitor-mobile";
|
||||
export * from "./invalid-card";
|
||||
|
||||
36
packages/utilities/shared-icons/src/info.tsx
Normal file
36
packages/utilities/shared-icons/src/info.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import {IconSvgProps} from "./types";
|
||||
|
||||
export const InfoIcon = (props: IconSvgProps) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M12 22C17.5 22 22 17.5 22 12C22 6.5 17.5 2 12 2C6.5 2 2 6.5 2 12C2 17.5 6.5 22 12 22Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M12 8V13"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M11.9945 16H12.0035"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
57
packages/utilities/shared-icons/src/invalid-card.tsx
Normal file
57
packages/utilities/shared-icons/src/invalid-card.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import {IconSvgProps} from "./types";
|
||||
|
||||
export const InvalidCardIcon = (props: IconSvgProps) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M10 16.95H6.21C2.84 16.95 2 16.11 2 12.74V6.74003C2 3.37003 2.84 2.53003 6.21 2.53003H16.74C20.11 2.53003 20.95 3.37003 20.95 6.74003"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M10 21.4699V16.95"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M2 12.95H10"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M6.73999 21.47H9.99999"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M22 12.8V18.51C22 20.88 21.41 21.47 19.04 21.47H15.49C13.12 21.47 12.53 20.88 12.53 18.51V12.8C12.53 10.43 13.12 9.83997 15.49 9.83997H19.04C21.41 9.83997 22 10.43 22 12.8Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M17.2445 18.25H17.2535"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
57
packages/utilities/shared-icons/src/monitor-mobile.tsx
Normal file
57
packages/utilities/shared-icons/src/monitor-mobile.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import {IconSvgProps} from "./types";
|
||||
|
||||
export const MonitorMobileIcon = (props: IconSvgProps) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M10 16.95H6.21C2.84 16.95 2 16.11 2 12.74V6.74003C2 3.37003 2.84 2.53003 6.21 2.53003H16.74C20.11 2.53003 20.95 3.37003 20.95 6.74003"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M10 21.4699V16.95"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M2 12.95H10"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M6.73999 21.47H9.99999"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M22 12.8V18.51C22 20.88 21.41 21.47 19.04 21.47H15.49C13.12 21.47 12.53 20.88 12.53 18.51V12.8C12.53 10.43 13.12 9.83997 15.49 9.83997H19.04C21.41 9.83997 22 10.43 22 12.8Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M17.2445 18.25H17.2535"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
38
packages/utilities/shared-icons/src/shield-security.tsx
Normal file
38
packages/utilities/shared-icons/src/shield-security.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import {IconSvgProps} from "./types";
|
||||
|
||||
export const ShieldSecurityIcon = (props: IconSvgProps) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
role="presentation"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M10.49 2.23006L5.49997 4.11006C4.34997 4.54006 3.40997 5.90006 3.40997 7.12006V14.5501C3.40997 15.7301 4.18997 17.2801 5.13997 17.9901L9.43997 21.2001C10.85 22.2601 13.17 22.2601 14.58 21.2001L18.88 17.9901C19.83 17.2801 20.61 15.7301 20.61 14.5501V7.12006C20.61 5.89006 19.67 4.53006 18.52 4.10006L13.53 2.23006C12.68 1.92006 11.32 1.92006 10.49 2.23006Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M12 12.5C13.1046 12.5 14 11.6046 14 10.5C14 9.39543 13.1046 8.5 12 8.5C10.8954 8.5 10 9.39543 10 10.5C10 11.6046 10.8954 12.5 12 12.5Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit="10"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M12 12.5V15.5"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit="10"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
Loading…
x
Reference in New Issue
Block a user