Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: accordion types improvements #143

Merged
merged 19 commits into from
Nov 12, 2020
Merged
Show file tree
Hide file tree
Changes from 18 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
50 changes: 50 additions & 0 deletions @types-tests/types.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* eslint-disable react-hooks/rules-of-hooks */
import React from "react";
import { expectError, expectType } from "tsd";
import {
MultiOverloadReturn,
SingleOverloadReturn,
} from "../src/accordion/types";
import { useAccordionState } from "../src/accordion/AccordionState";

const multi = useAccordionState({ allowMultiple: true });
expectType<(string | null)[] | undefined>(multi.selectedIds);
expectType<React.Dispatch<React.SetStateAction<(string | null)[]>>>(
multi.setSelectedIds,
);
expectType<MultiOverloadReturn>(multi);

const single = useAccordionState({ allowMultiple: false });
expectType<string | null | undefined>(single.selectedId);
expectType<React.Dispatch<React.SetStateAction<string | null>>>(
single.setSelectedId,
);
expectType<SingleOverloadReturn>(single);

expectType<MultiOverloadReturn>(
useAccordionState({
allowMultiple: true,
defaultSelectedIds: [],
}),
);

expectError(
useAccordionState({
allowMultiple: true,
defaultSelectedId: "",
}),
);

expectError(
useAccordionState({
allowMultiple: false,
defaultSelectedIds: [],
}),
);

expectType<SingleOverloadReturn>(
useAccordionState({
allowMultiple: false,
defaultSelectedId: "",
}),
);
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"build:cjs": "cross-env BABEL_ENV=cjs babel src --extensions .ts,.tsx -d dist/cjs --source-maps",
"build:esm": "cross-env BABEL_ENV=esm babel src --extensions .ts,.tsx -d dist/esm --source-maps",
"build:types": "tsc --emitDeclarationOnly --project tsconfig.prod.json",
"check-types": "tsc --noEmit --project tsconfig.prod.json",
"check-types": "yarn build:types && concurrently \"tsc --noEmit --project tsconfig.prod.json\" \"yarn tsd\"",
"commit": "gacp",
"format": "prettier --write \"./**/*.{js,ts,css,less,json,md,html,yml,yaml,pcss,jsx,tsx}\"",
"generatejs": "node scripts/generate-js",
Expand All @@ -43,7 +43,8 @@
"release:tags": "git push --follow-tags origin master",
"storybook": "yarn generatejs && start-storybook -p 6006 --no-dll",
"storybook-build": "yarn generatejs && build-storybook --no-dll",
"test": "jest --config ./jest.config.ts --no-cache"
"test": "jest --config ./jest.config.ts --no-cache",
"tsd": "tsd"
},
"dependencies": {
"@chakra-ui/counter": "1.0.0-rc.7",
Expand Down Expand Up @@ -137,6 +138,7 @@
"ts-jest": "26.4.3",
"ts-morph": "8.1.2",
"ts-node": "^9.0.0",
"tsd": "^0.13.1",
"typescript": "4.0.5"
},
"peerDependencies": {
Expand All @@ -145,5 +147,8 @@
},
"publishConfig": {
"access": "public"
},
"tsd": {
"directory": "@types-tests"
}
}
4 changes: 2 additions & 2 deletions src/accordion/AccordionPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ export type AccordionPanelOptions = DisclosureContentOptions &
unstable_IdOptions &
Pick<
AccordionStateReturn,
| "selectedId"
| "selectedIds"
| "registerPanel"
| "unregisterPanel"
| "panels"
| "items"
| "allowMultiple"
| "selectedId"
| "selectedIds"
> & {
/**
* Accordion's id
Expand Down
190 changes: 75 additions & 115 deletions src/accordion/AccordionState.ts
Original file line number Diff line number Diff line change
@@ -1,117 +1,68 @@
import {
useCompositeState,
CompositeState,
CompositeActions,
CompositeInitialState,
} from "reakit";
import * as React from "react";
import { useCompositeState } from "reakit";
import { useControllableState } from "@chakra-ui/hooks";
import {
AccordionState,
AccordionActions,
AccordionReturns,
MultiOverloadReturn,
SingleOverloadReturn,
AccordionInitialState,
AccordionInitialStateMulti,
AccordionInitialStateSingle,
SelectedIdPair,
} from "./types";

export type AccordionStateReturn = AccordionActions &
AccordionState &
AccordionReturns &
SelectedIdPair;

export type AccordionState = CompositeState & {
/**
* The current selected(controlled) accordion's `id`.
*/
selectedId: string | null;
/**
* The current selected(controlled) accordion's `id`.
*/
selectedIds: (string | null)[];
/**
* Whether the accodion selection should be manual.
*
* @default true
*/
manual: boolean;
/**
* Allow to open multiple accordion items
*
* @default false
*/
allowMultiple: boolean;
/**
* Allow to toggle accordion items
*
* @default false
*/
allowToggle: boolean;
/**
* Lists all the panels.
*/
panels: CompositeState["items"];
};

export type AccordionActions = CompositeActions & {
/**
* Moves into and selects an accordion by its `id`.
*/
select: CompositeActions["move"];
/**
* Moves into and unSelects an accordion by its `id` if it's already selected.
*/
unSelect: CompositeActions["move"];
/**
* Sets `selectedId`.
*/
setSelectedId: React.Dispatch<React.SetStateAction<string | null>>;
/**
* Sets `selectedIds`.
*/
setSelectedIds: React.Dispatch<React.SetStateAction<(string | null)[]>>;
/**
* Registers a accordion panel.
*/
registerPanel: CompositeActions["registerItem"];
/**
* Unregisters a accordion panel.
*/
unregisterPanel: CompositeActions["unregisterItem"];
};

export type AccordionInitialState = CompositeInitialState &
Pick<
Partial<AccordionState>,
"selectedId" | "selectedIds" | "manual" | "allowMultiple" | "allowToggle"
> & {
/**
* Set default selected id(uncontrolled)
*
* @default null
*/
defaultSelectedId?: string | null;
/**
* Handler that is called when the selectedId changes.
*/
onSelectedIdChange?: (value: string | null) => void;
/**
* Set default selected ids(uncontrolled)
*
* @default []
*/
defaultSelectedIds?: (string | null)[];
/**
* Handler that is called when the selectedIds changes.
*/
onSelectedIdsChange?: (value: (string | null)[]) => void;
};
export function useAccordionState(
props: AccordionInitialStateSingle,
): SingleOverloadReturn;

export type AccordionStateReturn = AccordionState & AccordionActions;
export function useAccordionState(
props: AccordionInitialStateMulti,
): MultiOverloadReturn;

export function useAccordionState(
props: AccordionInitialState,
): SingleOverloadReturn;

export function useAccordionState(
props: AccordionInitialState,
): MultiOverloadReturn;

export function useAccordionState(
props: AccordionInitialState = {},
): AccordionStateReturn {
const {
selectedId: selectedIdProp,
defaultSelectedId = null,
onSelectedIdChange,
selectedIds: selectedIdsProp,
defaultSelectedIds = [],
onSelectedIdsChange,
allowMultiple = false,
allowToggle: allowToggleProp = false,
manual = true,
...rest
} = props;
const allowToggle = allowMultiple ? allowMultiple : allowToggleProp;
const { manual = true, ...rest } = props;

const allowToggle = props.allowMultiple
? props.allowMultiple
: props.allowToggle || false;

let selectedIdProp;
let defaultSelectedId;
let onSelectedIdChange;
if (props.allowMultiple === false || !props.allowMultiple) {
// @ts-ignore
selectedIdProp = props.selectedId;
// @ts-ignore
defaultSelectedId = props.defaultSelectedId || null;
// @ts-ignore
onSelectedIdChange = props.onSelectedIdChange;
}

let selectedIdsProp;
let defaultSelectedIds;
let onSelectedIdsChange;
if (props.allowMultiple === true) {
selectedIdsProp = props.selectedIds;
defaultSelectedIds = props.defaultSelectedIds || [];
onSelectedIdsChange = props.onSelectedIdsChange;
}

// Single toggle accordion State
const [selectedId, setSelectedId] = useControllableState({
Expand All @@ -138,7 +89,7 @@ export function useAccordionState(
(id: string | null) => {
composite.move(id);

if (!allowMultiple) {
if (!props.allowMultiple) {
if (allowToggle && id === selectedId) {
setSelectedId(null);
return;
Expand All @@ -153,12 +104,12 @@ export function useAccordionState(
},

// eslint-disable-next-line react-hooks/exhaustive-deps
[allowMultiple, allowToggle, composite.move, selectedId],
[props.allowMultiple, allowToggle, composite.move, selectedId],
);

const unSelect = React.useCallback(
(id: string | null) => {
if (!allowMultiple && id === null) return;
if (!props.allowMultiple && id === null) return;

composite.move(id);
setSelectedIds(prevIds => prevIds?.filter(pId => pId !== id));
Expand All @@ -170,19 +121,28 @@ export function useAccordionState(

const panels = useCompositeState();

return {
const common = {
manual,
allowMultiple,
allowToggle,
selectedId,
setSelectedId,
selectedIds,
setSelectedIds,
select,
unSelect,
panels: panels.items,
registerPanel: panels.registerItem,
unregisterPanel: panels.unregisterItem,
...composite,
};

return props.allowMultiple === true
? {
allowMultiple: true,
selectedIds,
setSelectedIds,
...common,
}
: {
allowMultiple: false,
selectedId,
setSelectedId,
...common,
};
}
4 changes: 2 additions & 2 deletions src/accordion/AccordionTrigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ export type AccordionTriggerOptions = ButtonOptions &
Pick<
AccordionStateReturn,
| "panels"
| "selectedId"
| "selectedIds"
| "select"
| "unSelect"
| "allowMultiple"
| "allowToggle"
| "selectedId"
| "selectedIds"
>;

export type AccordionTriggerHTMLProps = ButtonHTMLProps &
Expand Down
Loading