feat(components): arrow icon added to the collapse item, new package added

This commit is contained in:
Junior Garcia 2022-10-23 22:11:48 -03:00
parent 3213853e4a
commit 4fe96c16d5
19 changed files with 321 additions and 35 deletions

View File

@ -37,11 +37,12 @@
"react": ">=16.8.0"
},
"dependencies": {
"@nextui-org/system": "workspace:*",
"@nextui-org/dom-utils": "workspace:*",
"@nextui-org/shared-css": "workspace:*",
"@nextui-org/shared-utils": "workspace:*",
"@nextui-org/aria-utils": "workspace:*",
"@nextui-org/system": "workspace:*",
"@nextui-org/react-utils": "workspace:*",
"@react-aria/accordion": "3.0.0-alpha.12",
"@react-aria/focus": "^3.9.0",
"@react-aria/utils": "^3.14.0",

View File

@ -1,13 +1,66 @@
import {BaseItem, ItemProps} from "@nextui-org/aria-utils";
import {CSS} from "@nextui-org/system";
import {FocusableProps} from "@react-types/shared";
import {ReactNode} from "react";
export type CollapseItemBaseProps<T extends object = {}> = Omit<
ItemProps<"button", T>,
"children"
> & {
type RenderIndicatorProps = {
indicator?: ReactNode;
isOpen?: boolean;
isDisabled?: boolean;
};
export interface CollapseItemBaseProps<T extends object = {}>
extends Omit<ItemProps<"button", T>, "children" | keyof FocusableProps>,
FocusableProps {
/**
* The content of the component.
*/
children?: ReactNode | null;
} & FocusableProps;
/**
* The collapse item `expanded` indicator, it's usually an arrow icon.
*/
indicator?: ReactNode | null;
/**
* The collapse item subtitle.
*/
subtitle?: ReactNode | string;
/**
* The collapse item variation.
* @default "default"
*/
variant?: "default" | "shadow" | "bordered" | "splitted";
/**
* The border weight for bordered collapse item variation.
* @default "normal"
*/
borderWeight?: CSS["borderWidth"];
/**
* The border weight for the collapse item divider.
* @default "light"
*/
dividerWeight?: CSS["borderWidth"];
/**
* Whether the collapse item have a bottom border.
* @default true
*/
withDivider?: boolean;
/**
* Whether the collapse item open/close animation should be disabled.
* @default false
*/
disableAnimation?: boolean;
/**
* If you pass this function NextUI will expose the current indicator and the open status, In case you want to use a custom indicator or muodify the current one.
* @param indicator The current indicator
* @param isOpen The current open status.
* @param isDisabled The current disabled status.
*
* @returns The indicator to be used.
*/
renderIndicator?: (props: RenderIndicatorProps) => ReactNode;
}
const CollapseItem = BaseItem as (props: CollapseItemBaseProps) => JSX.Element;

View File

@ -0,0 +1,39 @@
import {forwardRef, HTMLNextUIProps, NextUI} from "@nextui-org/system";
import {clsx, __DEV__} from "@nextui-org/shared-utils";
export interface CollapseIconProps extends HTMLNextUIProps<"svg"> {}
const CollapseIcon = forwardRef<CollapseIconProps, "svg">((props, ref) => {
const {className, css, ...otherProps} = props;
return (
<NextUI.Svg
ref={ref}
aria-hidden="true"
className={clsx("nextui-collapse-icon", className)}
css={{
opacity: 0.7,
...css,
}}
fill="none"
focusable="false"
height="20"
role="presentation"
stroke="currentColor"
viewBox="0 0 24 24"
width="20"
xmlns="http://www.w3.org/2000/svg"
{...otherProps}
>
<path d="M15.5 19l-7-7 7-7" strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} />
</NextUI.Svg>
);
});
if (__DEV__) {
CollapseIcon.displayName = "NextUI.CollapseIcon";
}
CollapseIcon.toString = () => ".nextui-collapse-icon";
export default CollapseIcon;

View File

@ -1,5 +1,7 @@
import {forwardRef} from "@nextui-org/system";
import {clsx, __DEV__, Expand} from "@nextui-org/shared-utils";
import {useMemo} from "react";
import {forwardRef, NextUI} from "@nextui-org/system";
import {Expand} from "@nextui-org/react-utils";
import {clsx, __DEV__} from "@nextui-org/shared-utils";
import {mergeProps} from "@react-aria/utils";
import {
@ -18,6 +20,7 @@ const CollapseItem = forwardRef<CollapseItemProps, "div">((props, ref) => {
const {
className,
domRef,
indicator,
item,
isOpen,
isDisabled,
@ -31,6 +34,28 @@ const CollapseItem = forwardRef<CollapseItemProps, "div">((props, ref) => {
...props,
});
const indicatorWrapper = useMemo(() => {
return (
<StyledCollapseItemIndicator
aria-hidden="true"
aria-label="collapse item indicator"
className="nextui-collapse-item-indicator"
isOpen={isOpen}
role="img"
>
{indicator}
</StyledCollapseItemIndicator>
);
}, [indicator]);
const indicatorComponent = useMemo(() => {
if (typeof item.props?.renderIndicator === "function") {
return item.props?.renderIndicator({indicator, isOpen, isDisabled});
}
return indicatorWrapper;
}, [item.props?.renderIndicator, indicator, indicatorWrapper, isOpen, isDisabled]);
return (
<StyledCollapseItem
className={clsx("nextui-collapse-item", className)}
@ -47,22 +72,15 @@ const CollapseItem = forwardRef<CollapseItemProps, "div">((props, ref) => {
isFocusVisible={isFocusVisible}
>
<StyledCollapseItemTitle className="nextui-collapse-item-title">
{item.props.title}
{item.props?.title}
</StyledCollapseItemTitle>
<StyledCollapseItemIndicator
aria-hidden="true"
aria-label="collapse item indicator"
className="nextui-collapse-item-indicator"
role="img"
>
{isOpen ? "🔽" : "▶️"}
</StyledCollapseItemIndicator>
{indicatorComponent}
</StyledCollapseItemButton>
</StyledCollapseItemHeading>
<Expand isExpanded={isOpen}>
<div {...regionProps} className="nextui-collapse-item-content">
{item.props.children}
</div>
<NextUI.Div {...regionProps} className="nextui-collapse-item-content">
{item.props?.children}
</NextUI.Div>
</Expand>
</StyledCollapseItem>
);

View File

@ -71,4 +71,18 @@ export const StyledCollapseItemTitle = styled("div", {
export const StyledCollapseItemIndicator = styled("span", {
flexShrink: 0,
variants: {
isOpen: {
true: {
svg: {
transform: "rotateZ(-90deg)",
},
},
false: {
svg: {
transform: "rotateZ(0deg)",
},
},
},
},
});

View File

@ -1,12 +1,13 @@
import type {CollapseItemBaseProps} from "./base/collapse-item-base";
import {Node} from "@react-types/shared";
import {useFocusRing} from "@react-aria/focus";
import {TreeState} from "@react-stately/tree";
import {callAllHandlers, ReactRef} from "@nextui-org/shared-utils";
import {useAccordionItem} from "@nextui-org/aria-utils";
import {useAccordionItem, NodeWithProps} from "@nextui-org/aria-utils";
import {useDOMRef} from "@nextui-org/dom-utils";
import {Key} from "react";
import {Key, useMemo} from "react";
import CollapseIcon from "./collapse-icon";
export interface UseCollapseItemProps<T extends object> extends CollapseItemBaseProps<T> {
/**
@ -23,24 +24,50 @@ export interface UseCollapseItemProps<T extends object> extends CollapseItemBase
/**
* The item node.
*/
item: Node<T>;
item: NodeWithProps<T, CollapseItemBaseProps<T>>;
/**
* The tree state.
*/
state: TreeState<T>;
/**
* Whether the collapse item is disabled.
* @default false
*/
isDisabled?: boolean;
}
// subtitle?: ReactNode | string; //TODO: unique per item
// borderWeight?: CSS["borderWidth"];
// dividerWeight?: CSS["borderWidth"];
// withDivider?: boolean;
// disableAnimation?: boolean;
export function useCollapseItem<T extends object>(props: UseCollapseItemProps<T>) {
const {ref, state, item, focusedKey, onFocusChange, ...otherProps} = props;
const {item, ...propsWithoutItem} = props;
const {
ref,
css = item.props?.css ?? {},
as = item.props?.as,
state,
focusedKey,
indicator = item.props?.indicator ?? <CollapseIcon />,
variant = item.props?.variant ?? "default",
borderWeight = item.props?.borderWeight ?? "normal",
dividerWeight = item.props?.dividerWeight ?? "light",
isDisabled: groupDisabled = false,
onFocusChange,
...otherProps
} = propsWithoutItem;
const domRef = useDOMRef(ref);
const {buttonProps, regionProps} = useAccordionItem({item}, {...state, focusedKey}, domRef);
const {isFocusVisible, focusProps} = useFocusRing({
autoFocus: item.props.autoFocus,
autoFocus: item.props?.autoFocus,
});
const isDisabled = state.disabledKeys.has(item.key);
const isDisabled = state.disabledKeys.has(item.key) || groupDisabled;
const isOpen = state.selectionManager.isSelected(item.key);
const handleFocus = () => {
@ -51,9 +78,27 @@ export function useCollapseItem<T extends object>(props: UseCollapseItemProps<T>
onFocusChange?.(false);
};
const scopeTokens = useMemo(() => {
return {
$$collapseItemBorderWeight: `$borderWeights${borderWeight}`,
$$collapseItemDividerWeight: `$borderWeights${dividerWeight}`,
};
}, [borderWeight, dividerWeight]);
const itemCss = useMemo(() => {
return {
...scopeTokens,
...css,
};
}, [css, scopeTokens]);
return {
as,
domRef,
item,
css: itemCss,
indicator,
variant,
isDisabled,
isOpen,
isFocusVisible,
@ -64,9 +109,9 @@ export function useCollapseItem<T extends object>(props: UseCollapseItemProps<T>
handleFocus,
focusProps.onFocus,
otherProps.onFocus,
item.props.onFocus,
item.props?.onFocus,
),
onBlur: callAllHandlers(handleBlur, focusProps.onBlur, otherProps.onBlur, item.props.onBlur),
onBlur: callAllHandlers(handleBlur, focusProps.onBlur, otherProps.onBlur, item.props?.onBlur),
...otherProps,
};
}

View File

@ -25,6 +25,29 @@ export const Default = () => (
</Collapse>
);
export const CustomIndicator = () => {
const renderIndicator = ({isOpen}) => {
return isOpen ? <span>🔽</span> : <span>🚀</span>;
};
return (
<Collapse selectionMode="single">
<Collapse.Item key="1" renderIndicator={renderIndicator} title="Your files">
file
</Collapse.Item>
<Collapse.Item key="2" title="Shared with you">
shared
</Collapse.Item>
<Collapse.Item key="3" title="Last item">
last
</Collapse.Item>
<Collapse.Item key="4" title="Four item">
four
</Collapse.Item>
</Collapse>
);
};
export const Controlled = () => {
const [expandedKeys, setExpandedKeys] = React.useState<Set<React.Key>>(new Set([]));

View File

@ -2,3 +2,4 @@ export * from "./accordion";
export * from "./collections";
export * from "./utils";
export * from "./interactions";
export * from "./type-utils";

View File

@ -0,0 +1,3 @@
import {Node} from "@react-types/shared";
export type NodeWithProps<T extends object, P = {}> = Omit<Node<T>, "props"> & {props?: P};

View File

@ -0,0 +1,24 @@
# @nextui-org/react-utils
A Quick description of the component
> This is an internal utility, not intended for public usage.
## Installation
```sh
yarn add @nextui-org/react-utils
# or
npm i @nextui-org/react-utils
```
## Contribution
Yes please! See the
[contributing guidelines](https://github.com/nextui-org/nextui/blob/master/CONTRIBUTING.md)
for details.
## Licence
This project is licensed under the terms of the
[MIT license](https://github.com/nextui-org/nextui/blob/master/LICENSE).

View File

@ -0,0 +1,3 @@
{ "replace": { "main": "dist/index.cjs.js", "module": "dist/index.esm.js",
"types": "dist/index.d.ts", "exports": { ".": { "import": "./dist/index.esm.js",
"require": "./dist/index.cjs.js" }, "./package.json": "./package.json" } } }

View File

@ -0,0 +1,48 @@
{
"name": "@nextui-org/react-utils",
"version": "1.0.0-beta.11",
"description": "A package for sharing react components and utilities",
"keywords": [
"react-utils"
],
"author": "Junior Garcia <jrgarciadev@gmail.com>",
"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/utilities/react-utils"
},
"bugs": {
"url": "https://github.com/nextui-org/nextui/issues"
},
"scripts": {
"build": "tsup src/index.ts --format=esm,cjs --dts",
"dev": "yarn build:fast -- --watch",
"clean": "rimraf dist .turbo",
"typecheck": "tsc --noEmit",
"build:fast": "tsup src/index.ts --format=esm,cjs",
"prepack": "clean-package",
"postpack": "clean-package restore"
},
"dependencies": {
"@nextui-org/system": "workspace:*",
"@nextui-org/use-real-shape": "workspace:*",
"@nextui-org/shared-utils": "workspace:*"
},
"peerDependencies": {
"react": ">=16.8.0"
},
"devDependencies": {
"clean-package": "2.1.1",
"react": "^17.0.2"
}
}

View File

@ -1,6 +1,5 @@
import React, {useEffect, useState} from "react";
import {clsx} from "./clsx";
import {clsx} from "@nextui-org/shared-utils";
interface CSSTransitionProps {
visible?: boolean;

View File

@ -0,0 +1,2 @@
export * from "./css-transition";
export * from "./expand";

View File

@ -0,0 +1,4 @@
{
"extends": "../../../tsconfig.json",
"include": ["src", "index.ts"]
}

View File

@ -0,0 +1,13 @@
import {defineConfig} from "tsup";
import {findUpSync} from "find-up";
export default defineConfig({
clean: true,
minify: false,
treeshake: true,
format: ["cjs", "esm"],
outExtension(ctx) {
return {js: `.${ctx.format}.js`};
},
inject: process.env.JSX ? [findUpSync("react-shim.js")!] : undefined,
});

View File

@ -34,8 +34,6 @@
"postpack": "clean-package restore"
},
"dependencies": {
"@nextui-org/system": "workspace:*",
"@nextui-org/use-real-shape": "workspace:*",
"use-sync-external-store": "^1.2.0",
"deepmerge": "4.2.2"
},

View File

@ -8,10 +8,8 @@ export * from "./color";
export * from "./object";
export * from "./text";
export * from "./dimensions";
export * from "./css-transition";
export * from "./functions";
export * from "./context";
export * from "./numbers";
export * from "./console";
export * from "./expand";
export * from "./fast-context";