Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 196 additions & 0 deletions packages/material/src/Accordion/Accordion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import Collapse from "../Collapse";
import Paper from "../Paper";
import styled from "../styles/styled";
import AccordionContext from "./AccordionContext";
import { AccordionTypeMap } from "./AccordionProps";
import accordionClasses, { getAccordionUtilityClass } from "./accordionClasses";
import createComponentFactory from "@suid/base/createComponentFactory";
import { ChangeEvent } from "@suid/types";
import clsx from "clsx";
import { children, createSignal } from "solid-js";

const $ = createComponentFactory<AccordionTypeMap>()({
name: "MuiAccordion",
selfPropNames: [
"children",
"classes",
"defaultExpanded",
"disabled",
"disableGutters",
"expanded",
"onChange",
"square",
],
propDefaults: ({ set }) =>
set({
defaultExpanded: false,
disabled: false,
disableGutters: false,
square: false,
}),
utilityClass: getAccordionUtilityClass,
slotClasses: (o) => ({
root: [
"root",
!o.square && "rounded",
o.expanded && "expanded",
o.disabled && "disabled",
!o.disableGutters && "gutters",
],
heading: ["heading"],
region: ["region"],
}),
});

const AccordionRoot = styled(Paper, {
name: "MuiAccordion",
slot: "Root",
})(({ theme }) => {
const transition = {
duration: theme.transitions.duration.shortest,
};

return {
position: "relative",
transition: theme.transitions.create(["margin"], transition),
overflowAnchor: "none",
"&::before": {
position: "absolute",
left: 0,
top: -1,
right: 0,
height: 1,
content: '""',
opacity: 1,
backgroundColor: theme.palette.divider,
transition: theme.transitions.create(
["opacity", "background-color"],
transition
),
},
"&:first-of-type": {
"&::before": {
display: "none",
},
},
[`&.${accordionClasses.expanded}`]: {
"&::before": {
opacity: 0,
},
"&:first-of-type": {
marginTop: 0,
},
"&:last-of-type": {
marginBottom: 0,
},
"& + &": {
"&::before": {
display: "none",
},
},
},
[`&.${accordionClasses.disabled}`]: {
backgroundColor: theme.palette.action.disabledBackground,
},
[`&.${accordionClasses.gutters}`]: {
[`&.${accordionClasses.expanded}`]: {
margin: "16px 0",
"&:first-of-type": {
marginTop: 0,
},
"&:last-of-type": {
marginBottom: 0,
},
},
},
};
});

const AccordionHeading = styled("h3", {
name: "MuiAccordion",
slot: "Heading",
})({
all: "unset",
});

const AccordionRegion = styled("div", {
name: "MuiAccordion",
slot: "Region",
})({});

/**
*
* Demos:
*
* - [Accordion](https://mui.com/components/accordion/)
*
* API:
*
* - [Accordion API](https://mui.com/api/accordion/)
* - inherits [Paper API](https://mui.com/api/paper/)
*/
const Accordion = $.component(function Accordion({
allProps,
props,
otherProps,
classes,
}) {
const [expandedState, setExpandedState] = createSignal(props.defaultExpanded);
const expanded = () =>
props.expanded !== undefined ? props.expanded : expandedState();

const handleChange = (event: Event) => {
const newExpanded = !expanded();
setExpandedState(newExpanded);
props.onChange?.(event as ChangeEvent<HTMLDivElement>, newExpanded);
};

// Create owner state object that includes the expanded state
const ownerState = () => ({
...allProps,
expanded: expanded(),
});

return (
<AccordionRoot
{...otherProps}
class={clsx($.useClasses(ownerState()).root, allProps.class)}
square={props.square}
>
<AccordionHeading class={$.useClasses(ownerState()).heading}>
<AccordionContext.Provider
value={{
get expanded() {
return expanded();
},
get disabled() {
return props.disabled;
},
toggle: handleChange,
}}
>
{(() => {
const c = children(() => props.children);
const kids = c.toArray();
return kids[0];
})()}
</AccordionContext.Provider>
</AccordionHeading>
<Collapse in={expanded()} timeout="auto">
<AccordionRegion
class={$.useClasses(ownerState()).region}
role="region"
aria-labelledby={otherProps.id}
>
{(() => {
const c = children(() => props.children);
const kids = c.toArray();
return kids.slice(1);
})()}
</AccordionRegion>
</Collapse>
</AccordionRoot>
);
});

export default Accordion;
13 changes: 13 additions & 0 deletions packages/material/src/Accordion/AccordionContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createContext } from "solid-js";

export interface AccordionContextValue {
expanded: boolean;
disabled: boolean;
toggle: (event: Event) => void;
}

const AccordionContext = createContext<AccordionContextValue | undefined>(
undefined
);

export default AccordionContext;
64 changes: 64 additions & 0 deletions packages/material/src/Accordion/AccordionProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { PaperProps } from "../Paper/PaperProps";
import { AccordionClasses } from "./accordionClasses";
import { OverrideProps, ElementType, ChangeEventHandler } from "@suid/types";
import { JSXElement } from "solid-js";

export interface AccordionOwnProps {
/**
* The content of the component.
*/
children: JSXElement;
/**
* Override or extend the styles applied to the component.
*/
classes?: Partial<AccordionClasses>;
/**
* If `true`, expands the accordion by default.
* @default false
*/
defaultExpanded?: boolean;
/**
* If `true`, the component is disabled.
* @default false
*/
disabled?: boolean;
/**
* If `true`, it removes the margin between two expanded accordion items and the increase of height.
* @default false
*/
disableGutters?: boolean;
/**
* If `true`, expands the accordion, otherwise collapse it.
* Setting this prop enables control over the accordion.
*/
expanded?: boolean;
/**
* Callback fired when the expand/collapse state is changed.
*
* @param {React.SyntheticEvent} event The event source of the callback. **Warning**: This is a generic event not a change event.
* @param {boolean} expanded The `expanded` state of the accordion.
*/
onChange?: ChangeEventHandler<HTMLDivElement, boolean>;
/**
* If `true`, rounded corners are disabled.
* @default false
*/
square?: boolean;
}

export interface AccordionTypeMap<P = {}, D extends ElementType = "div"> {
name: "MuiAccordion";
defaultPropNames:
| "defaultExpanded"
| "disabled"
| "disableGutters"
| "square";
selfProps: AccordionOwnProps;
props: P & AccordionOwnProps & Omit<PaperProps, "component" | "onChange">;
defaultComponent: D;
}

export type AccordionProps<
D extends ElementType = AccordionTypeMap["defaultComponent"],
P = {},
> = OverrideProps<AccordionTypeMap<P, D>, D>;
31 changes: 31 additions & 0 deletions packages/material/src/Accordion/accordionClasses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { generateUtilityClass, generateUtilityClasses } from "@suid/base";

export interface AccordionClasses {
/** Styles applied to the root element. */
root: string;
/** State class applied to the root element if `rounded={true}`. */
rounded: string;
/** State class applied to the root element if `expanded={true}`. */
expanded: string;
/** State class applied to the root element if `disabled={true}`. */
disabled: string;
/** State class applied to the root element unless `disableGutters={true}`. */
gutters: string;
/** Styles applied to the region element, the container of the children. */
region: string;
/** Styles applied to the heading element. */
heading: string;
}

export type AccordionClassKey = keyof AccordionClasses;

export function getAccordionUtilityClass(slot: string): string {
return generateUtilityClass("MuiAccordion", slot);
}

const accordionClasses: AccordionClasses = generateUtilityClasses(
"MuiAccordion",
["root", "rounded", "expanded", "disabled", "gutters", "region", "heading"]
);

export default accordionClasses;
4 changes: 4 additions & 0 deletions packages/material/src/Accordion/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { default } from "./Accordion";
export * from "./AccordionProps";
export { default as accordionClasses } from "./accordionClasses";
export * from "./accordionClasses";
70 changes: 70 additions & 0 deletions packages/material/src/AccordionActions/AccordionActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import styled from "../styles/styled";
import { AccordionActionsTypeMap } from "./AccordionActionsProps";
import { getAccordionActionsUtilityClass } from "./accordionActionsClasses";
import createComponentFactory from "@suid/base/createComponentFactory";
import clsx from "clsx";

type OwnerState = {
disableSpacing: boolean;
};

const $ = createComponentFactory<AccordionActionsTypeMap>()({
name: "MuiAccordionActions",
selfPropNames: ["children", "classes", "disableSpacing"],
propDefaults: ({ set }) =>
set({
disableSpacing: false,
}),
utilityClass: getAccordionActionsUtilityClass,
slotClasses: (ownerState: OwnerState) => ({
root: ["root", !ownerState.disableSpacing && "spacing"],
}),
});

const AccordionActionsRoot = styled("div", {
name: "MuiAccordionActions",
slot: "Root",
})<OwnerState>(({ theme, ownerState }) => ({
display: "flex",
alignItems: "center",
padding: theme.spacing(1),
justifyContent: "flex-end",
...(!ownerState.disableSpacing && {
"& > :not(style) ~ :not(style)": {
marginLeft: theme.spacing(1),
},
}),
}));

/**
*
* Demos:
*
* - [Accordion](https://mui.com/components/accordion/)
*
* API:
*
* - [AccordionActions API](https://mui.com/api/accordion-actions/)
*/
const AccordionActions = $.component(function AccordionActions({
allProps,
props,
otherProps,
classes,
}) {
const ownerState: OwnerState = {
disableSpacing: props.disableSpacing,
};

return (
<AccordionActionsRoot
{...otherProps}
class={clsx(classes.root, allProps.class)}
ownerState={ownerState}
>
{props.children}
</AccordionActionsRoot>
);
});

export default AccordionActions;
Loading