Fix Accordion per-item disabled

- Add disabled accessor to AccordionItemContextValue and itemCtx so per-item disabled prop disables the trigger button
- Use getter for itemCtx.value to stay reactive with the local proxy
- Guard onClick toggle against itemCtx.disabled() in addition to rootCtx.disabled()
- Add one-liner JSDoc to all six exported accordion interfaces
This commit is contained in:
Mats Bosson 2026-03-29 08:13:45 +07:00
parent 94822186c2
commit 30ee5c877e
6 changed files with 14 additions and 3 deletions

View File

@ -2,6 +2,7 @@ import type { JSX } from "solid-js";
import { splitProps } from "solid-js"; import { splitProps } from "solid-js";
import { useAccordionItemContext } from "./accordion-context"; import { useAccordionItemContext } from "./accordion-context";
/** Props for the collapsible content panel of an Accordion item. */
export interface AccordionContentProps extends JSX.HTMLAttributes<HTMLDivElement> { export interface AccordionContentProps extends JSX.HTMLAttributes<HTMLDivElement> {
children?: JSX.Element; children?: JSX.Element;
} }

View File

@ -1,17 +1,20 @@
import type { Accessor } from "solid-js"; import type { Accessor } from "solid-js";
import { createContext, useContext } from "solid-js"; import { createContext, useContext } from "solid-js";
/** Shared state provided by AccordionRoot to all descendant accordion parts. */
export interface AccordionRootContextValue { export interface AccordionRootContextValue {
isExpanded: (value: string) => boolean; isExpanded: (value: string) => boolean;
toggleItem: (value: string) => void; toggleItem: (value: string) => void;
disabled: Accessor<boolean>; disabled: Accessor<boolean>;
} }
/** Per-item state provided by AccordionItem to its trigger and content. */
export interface AccordionItemContextValue { export interface AccordionItemContextValue {
value: string; value: string;
isExpanded: Accessor<boolean>; isExpanded: Accessor<boolean>;
triggerId: string; triggerId: string;
contentId: string; contentId: string;
disabled: Accessor<boolean>;
} }
const AccordionRootContext = createContext<AccordionRootContextValue>(); const AccordionRootContext = createContext<AccordionRootContextValue>();

View File

@ -2,6 +2,7 @@ import type { JSX } from "solid-js";
import { splitProps } from "solid-js"; import { splitProps } from "solid-js";
import { Dynamic } from "solid-js/web"; import { Dynamic } from "solid-js/web";
/** Props for the heading wrapper rendered around an Accordion trigger. */
export interface AccordionHeaderProps extends JSX.HTMLAttributes<HTMLHeadingElement> { export interface AccordionHeaderProps extends JSX.HTMLAttributes<HTMLHeadingElement> {
/** Heading level element. @default "h3" */ /** Heading level element. @default "h3" */
as?: string; as?: string;

View File

@ -2,6 +2,7 @@ import type { JSX } from "solid-js";
import { createUniqueId, splitProps } from "solid-js"; import { createUniqueId, splitProps } from "solid-js";
import { AccordionItemContextProvider, useAccordionRootContext } from "./accordion-context"; import { AccordionItemContextProvider, useAccordionRootContext } from "./accordion-context";
/** Props for a single collapsible item within an Accordion. */
export interface AccordionItemProps extends JSX.HTMLAttributes<HTMLDivElement> { export interface AccordionItemProps extends JSX.HTMLAttributes<HTMLDivElement> {
value: string; value: string;
disabled?: boolean; disabled?: boolean;
@ -16,10 +17,13 @@ export function AccordionItem(props: AccordionItemProps): JSX.Element {
const contentId = createUniqueId(); const contentId = createUniqueId();
const itemCtx = { const itemCtx = {
value: local.value, get value() {
return local.value;
},
isExpanded: () => rootCtx.isExpanded(local.value), isExpanded: () => rootCtx.isExpanded(local.value),
triggerId, triggerId,
contentId, contentId,
disabled: () => local.disabled ?? false,
}; };
return ( return (

View File

@ -2,6 +2,7 @@ import type { JSX } from "solid-js";
import { createSignal, splitProps } from "solid-js"; import { createSignal, splitProps } from "solid-js";
import { AccordionRootContextProvider, type AccordionRootContextValue } from "./accordion-context"; import { AccordionRootContextProvider, type AccordionRootContextValue } from "./accordion-context";
/** Props for the root accordion container that manages expanded state. */
export interface AccordionRootProps extends JSX.HTMLAttributes<HTMLDivElement> { export interface AccordionRootProps extends JSX.HTMLAttributes<HTMLDivElement> {
/** "single" allows one item open at a time; "multiple" allows any number. @default "single" */ /** "single" allows one item open at a time; "multiple" allows any number. @default "single" */
type?: "single" | "multiple"; type?: "single" | "multiple";

View File

@ -2,6 +2,7 @@ import type { JSX } from "solid-js";
import { splitProps } from "solid-js"; import { splitProps } from "solid-js";
import { useAccordionItemContext, useAccordionRootContext } from "./accordion-context"; import { useAccordionItemContext, useAccordionRootContext } from "./accordion-context";
/** Props for the button that toggles an Accordion item open/closed. */
export interface AccordionTriggerProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> { export interface AccordionTriggerProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
children?: JSX.Element; children?: JSX.Element;
} }
@ -20,11 +21,11 @@ export function AccordionTrigger(props: AccordionTriggerProps): JSX.Element {
aria-controls={itemCtx.contentId} aria-controls={itemCtx.contentId}
data-state={itemCtx.isExpanded() ? "open" : "closed"} data-state={itemCtx.isExpanded() ? "open" : "closed"}
data-accordion-trigger data-accordion-trigger
disabled={rootCtx.disabled()} disabled={rootCtx.disabled() || itemCtx.disabled()}
{...rest} {...rest}
onClick={(e) => { onClick={(e) => {
if (typeof rest.onClick === "function") rest.onClick(e); if (typeof rest.onClick === "function") rest.onClick(e);
if (!rootCtx.disabled()) rootCtx.toggleItem(itemCtx.value); if (!rootCtx.disabled() && !itemCtx.disabled()) rootCtx.toggleItem(itemCtx.value);
}} }}
onKeyDown={(e) => { onKeyDown={(e) => {
if (typeof rest.onKeyDown === "function") rest.onKeyDown(e); if (typeof rest.onKeyDown === "function") rest.onKeyDown(e);