diff --git a/.gitignore b/.gitignore index 9b69fe1..ef6d887 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ /build /dist .rpt2_cache +coverage # misc .DS_Store diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index b8ed5cf..08bfd22 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -55,7 +55,7 @@ further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at opensource@hodgef.com. All +reported by contacting the project team at it.team@keyvalue.systems. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. diff --git a/LICENSE b/LICENSE index da5eede..7508013 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Francisco Hodge +Copyright (c) 2023 Keyvalue Software Systems Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..764822e --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,76 @@ + + + +​ + +## Pull Request Checklist + +​ + +- [ ] **Read the contributing guidelines.** +- [ ] **Linked to an issue:** Fixes # (replace with the issue number, if applicable) +- [ ] **Branch is up-to-date with the base branch:** `main` +- [ ] **Changes pass tests locally:** `npm test` or `yarn test` +- [ ] **Documentation has been updated, if necessary** +- [ ] **Code follows the style guide of the project** + ​ + +## Description + +​ + + + +​ + +## Screenshots (if applicable) + +​ + + + +​ + +## Additional Notes + +​ + + + +​ + +## Related Issues or PRs + +​ + + + +​ + +## Reviewer Guidelines + +​ + + + +​ + +## Testing Instructions + +​ + + + +​ + +## Checklist for Reviewers + +​ + +- [ ] Code follows project conventions and style +- [ ] Changes do not introduce new warnings or errors +- [ ] Unit tests cover the changes +- [ ] Documentation is updated + ​ + +## By submitting this pull request, I confirm that my contribution is made under the terms of the MIT License. diff --git a/README.md b/README.md index 9e8e35c..7d496b9 100644 --- a/README.md +++ b/README.md @@ -1,75 +1,72 @@ +# React Stepper -# React Vertical Stepper -npm version +npm version -
- +
+
+
- -A fully customizable ready to use vertical stepper UI package for React. -Try tweaking a vertical stepper using this codesandbox link here +
+ +
+
+A fully customizable ready to use stepper UI package for React. +Try tweaking a stepper using this codesandbox link here ## Installation +The easiest way to use react-stepper-ui-component is to install it from npm and build it into your app with Webpack. + ```bash -npm install react-vertical-stepper +npm install @keyvaluesystems/react-stepper ``` You’ll need to install React separately since it isn't included in the package. ## Usage -React Vertical Stepper can run in a very basic mode by just providing the `steps` and `currentStepIndex` props like this: +React Stepper can run in a very basic mode by just providing the `steps` and `currentStepIndex` props like this: ```jsx -import React, { useState } from 'react'; -import Stepper from 'react-vertical-stepper'; - -function App() { - const [currentStepIndex, setCurrentStepIndex] = useState(0); - - stepsArray = [{ - label: 'Step 1', - description: 'This is Step 1', - status: 'completed' - },{ - label: 'Step 2', - description: 'This is Step 2', - status: 'visited' - },{ - label: 'Step 3', - description: 'This is Step 3', - status: 'unvisited' - }]; - - return ( - - ); -} - -export default App; + ``` -The `steps` array is an array of objects with basic keys like -- `label` - a string that can be shown as step label title to your step indicator -- `description` - a string that can be show as step description below the step label -- `status` - can be provided with any of `visited`, `unvisited`, `completed`. Will be required if you are using default styles. +The `steps` array is an array of objects with following keys: ->Note: You can also add any other keys to the step object and other statuses like `skipped` for different customizations as per requirements +- `label` - A mandatory string representing the label/title of the step. +- `description` - Optional extra information or description for the step. +- `completed` - Boolean flag for indicating step completion status. -You can customize the step indicator bubble with your own DOM element using the `renderBubble` prop +You can customize each step node with your own DOM element using the `renderNode` prop ```jsx (
{step.label}
)} + renderNode={(step, stepIndex) =>
{step.label}
} /> ``` -The `step` param provided by the `renderBubble` callback is the same object you pass as array item in `steps` prop. + +The `step` param provided by the `renderNode` callback is the same object you pass as array item in `steps` prop. ## Props @@ -90,31 +87,52 @@ Props that can be passed to the component are listed below: undefined - currentIndex: number + currentStepIndex: number The index of current active step. 0 onStepClick?: (step: object, stepIndex: number): void - A step click handler that fires each time you click on a step, its label or its description. + A step click handler that fires each time you click on a step. undefined - renderBubble?: (step: object, stepIndex: number): ReactElement + renderNode?: (step: object, stepIndex: number): ReactElement - A render function to customize your step indicator with your own element. + A render function to customize each step node with your own element. undefined - labelPosition?: 'left' | 'right' + orientation?: 'horizontal' | 'vertical' + + Determines the layout of the stepper. + + vertical + + + labelPosition?: 'left' | 'right' | 'top' | 'bottom' - Allows you to align step label and description to either left or right of step indicator + Allows you to align step label and description with respect to its node right + + showDescriptionsForAllSteps boolean + + A boolean prop specifying whether to show descriptions for all steps within the stepper. + + false + + + stepContent(step: object, stepIndex: number): ReactElement + + Prop that allows for dynamic content display when the step is active + + undefined + styles?: object @@ -127,44 +145,43 @@ Props that can be passed to the component are listed below: ## Style Customizations -All the default styles provided by this package are overridable using the `style` prop -the below code shows all the overridable styles: +All the default styles provided by this package can be overridden using the `style` prop +the below code shows all the styles that can be overridden: ```jsx -import React from 'react'; -import Stepper from 'react-vertical-stepper'; +import React from "react"; +import Stepper from "react-stepper"; function App() { - - const stylesOverride = { - LabelTitle: (step, stepIndex) => ({...styles}), - ActiveLabelTitle: (step, stepIndex) => ({...styles}), - LabelDescription: (step, stepIndex) => ({...styles}), - ActiveLabelDescription: (step, stepIndex) => ({...styles}), - LineSeparator: (step, stepIndex) => ({...styles}), - InactiveLineSeparator: (step, stepIndex) => ({...styles}), - Bubble: (step, stepIndex) => ({...styles}), - ActiveBubble: (step, stepIndex) => ({...styles}), - InActiveBubble: (step, stepIndex) => ({...styles}), - }; - return ( - ({ ...styles }), + ActiveLabelTitle: (step, stepIndex) => ({ ...styles }), + LabelDescription: (step, stepIndex) => ({ ...styles }), + ActiveLabelDescription: (step, stepIndex) => ({ ...styles }), + LineSeparator: (step, stepIndex) => ({ ...styles }), + InactiveLineSeparator: (step, stepIndex) => ({ ...styles }), + Node: (step, stepIndex) => ({ ...styles }), + ActiveNode: (step, stepIndex) => ({ ...styles }), + InActiveNode: (step, stepIndex) => ({ ...styles }), + }; + return ( + - ); + /> + ); } export default App; ``` - -- `LabelTitle` - overrides the step label style -- `ActiveLabelTitle` - overrides the step label style of current active step -- `LabelDescription` - overrides the step description style -- `ActiveLabelDescription` - overrides the step description style of current active step -- `LineSeparator` - overrides default step connector line styles -- `InactiveLineSeparator` - overrides styles of step connector line after current active step -- `Bubble` - overrides default styles of step indicator -- `ActiveBubble` - overrides default styles of step indicator of current active step -- `InActiveBubble` - overrides default styles of step indicator that has `unvisited` step status \ No newline at end of file + +- `LabelTitle` - overrides the step label style +- `ActiveLabelTitle` - overrides the step label style of current active step +- `LabelDescription` - overrides the step description style +- `ActiveLabelDescription` - overrides the step description style of current active step +- `LineSeparator` - overrides default step connector line styles +- `InactiveLineSeparator` - overrides styles of step connector line after current active step +- `Node` - overrides default styles of step indicator +- `ActiveNode` - overrides default styles of step indicator of current active step +- `InActiveNode` - overrides default styles of step indicator that is not completed and not active diff --git a/STYLE_GUIDELINES.md b/STYLE_GUIDELINES.md new file mode 100644 index 0000000..a95b384 --- /dev/null +++ b/STYLE_GUIDELINES.md @@ -0,0 +1,23 @@ +## SCSS Style Guidelines for @keyvaluesystems/react-stepper + +**Introduction** + +As an open-source project utilizing SCSS, @keyvaluesystems/react-stepper strives to maintain a consistent and well-structured codebase. These SCSS style guidelines serve as a reference for contributors, ensuring that their SCSS code adheres to established conventions and best practices. + +**SCSS Coding Conventions** + +- Organize SCSS files into a logical structure. +- Use meaningful and descriptive names for variables, mixins, and classes. +- Use SCSS nesting judiciously to organize complex styles. +- Include comments to explain non-obvious logic and complex styles. +- Utilize SCSS variables to define reusable values. +- Employ a SCSS linting tool. +- Should support devices with all resolutions +- Follow CamelCase conventions for class names that concisely convey their purpose, enhancing code organization and readability +- Adhere to the practice of reusing style classes to improve code organization and maintainability. + +**Documentation Practices** + +- Provide clear documentation for exported mixins and variables. +- Include a README file within the SCSS directory if necessary. +- Add comments to SCSS files. diff --git a/package-lock.json b/package-lock.json index 4f04c9d..bbd6e41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "@keyvaluesystems/react-vertical-stepper", + "name": "@keyvaluesystems/react-stepper", "version": "0.1.6", "lockfileVersion": 1, "requires": true, @@ -23918,6 +23918,23 @@ "dev": true, "requires": { "loose-envify": "^1.1.0" + }, + "dependencies": { + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + } } }, "react-app-polyfill": { diff --git a/package.json b/package.json index 9f3adca..8ae120e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "@keyvaluesystems/react-vertical-stepper", + "name": "@keyvaluesystems/react-stepper", "version": "0.1.6", - "description": "A fully customizable vertical stepper component", + "description": "A fully customizable stepper component", "main": "build/index.js", "source": "src/index.tsx", "types": "build/types/index.d.ts", @@ -20,11 +20,11 @@ }, "repository": { "type": "github", - "url": "git+https://github.com/KeyValueSoftwareSystems/react-vertical-stepper.git" + "url": "git+https://github.com/KeyValueSoftwareSystems/react-stepper.git" }, "author": "Keyvalue", - "license": "ISC", - "homepage": "https://github.com/KeyValueSoftwareSystems/react-vertical-stepper", + "license": "MIT", + "homepage": "https://github.com/KeyValueSoftwareSystems/react-stepper", "keywords": [ "library", "starter", @@ -35,7 +35,8 @@ "steps", "stepper", "vertical-stepper", - " steps-ui", + "horizontal stepper", + "steps-ui", "workflow-stepper", "progress-ui" ], @@ -114,7 +115,7 @@ ] }, "bugs": { - "url": "https://github.com/KeyValueSoftwareSystems/react-vertical-stepper/issues" + "url": "https://github.com/KeyValueSoftwareSystems/react-stepper/issues" }, "dependencies": {} } diff --git a/src/assets/horizontal-stepper-example.png b/src/assets/horizontal-stepper-example.png new file mode 100644 index 0000000..43ee223 Binary files /dev/null and b/src/assets/horizontal-stepper-example.png differ diff --git a/src/assets/vertical-stepper-example.png b/src/assets/vertical-stepper-example.png index 509793c..cd49c33 100644 Binary files a/src/assets/vertical-stepper-example.png and b/src/assets/vertical-stepper-example.png differ diff --git a/src/bubble/bubble.tsx b/src/bubble/bubble.tsx deleted file mode 100644 index a712d82..0000000 --- a/src/bubble/bubble.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React, { FC } from "react"; -import type { IBubbleProps } from "./types"; -import { Elements } from "../constants"; -import whiteTick from '../assets/white-tick.svg'; -import { STEP_STATUSES, LABEL_POSITION } from '../constants'; -import styles from './styles.module.scss'; - -const Bubble: FC = (props) => { - const { - step, - renderAdornment, - index, - currentStepIndex, - handleStepClick = null, - showCursor, - getStyles, - labelPosition - } = props; - - return ( -
handleStepClick && handleStepClick()} - role="presentation" - id="stepper-bubble" - > - {(renderAdornment && renderAdornment(step, index)) - || ( - <> - {step?.status === STEP_STATUSES.COMPLETED && ( - ) - || index + 1} - - )} -
- {step?.label && ( - handleStepClick && handleStepClick()} - role="presentation" - id={`stepper-label-${index}`} - > - {step.label} - - )} - {step?.description && ( - handleStepClick && handleStepClick()} - role="presentation" - id={`stepper-desc-${index}`} - > - {step.description} - - )} -
-
- ); -}; - -export default Bubble; \ No newline at end of file diff --git a/src/bubble/index.ts b/src/bubble/index.ts deleted file mode 100644 index 9d927cf..0000000 --- a/src/bubble/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import Bubble from "./bubble"; - -export default Bubble; \ No newline at end of file diff --git a/src/bubble/types.d.ts b/src/bubble/types.d.ts deleted file mode 100644 index 9fd9c0d..0000000 --- a/src/bubble/types.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ReactElement } from "react"; -import { IStep } from "../stepper-component/types"; -import { Elements } from "../constants"; - -export type IBubbleProps = { - step: IStep, - renderAdornment?(step: IStep, index: number): ReactElement, - index: number, - currentStepIndex?: number, - handleStepClick(): void, - showCursor: boolean, - getStyles(element: Elements): object, - labelPosition: 'left' | 'right' -} \ No newline at end of file diff --git a/src/constants.ts b/src/constants.ts index feb7ab8..eee7a96 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,22 +1,24 @@ -export enum STEP_STATUSES { - VISITED = 'visited', - UNVISITED = 'unvisited', - COMPLETED = 'completed' -} export enum LABEL_POSITION { - LEFT = 'left', - RIGHT = 'right' + LEFT = "left", + RIGHT = "right", + TOP = "top", + BOTTOM = "bottom", +} + +export enum ORIENTATION { + HORIZONTAL = "horizontal", + VERTICAL = "vertical", } export enum Elements { - LabelDescription = "LabelDescription", - LabelTitle = "LabelTitle", - ActiveLabelTitle = "ActiveLabelTitle", - ActiveLabelDescription = "ActiveLabelDescription", - LineSeparator = "LineSeparator", - InactiveLineSeparator = "InactiveLineSeparator", - Bubble = "Bubble", - ActiveBubble = "ActiveBubble", - InActiveBubble = "InActiveBubble" - } \ No newline at end of file + LabelDescription = "LabelDescription", + LabelTitle = "LabelTitle", + ActiveLabelTitle = "ActiveLabelTitle", + ActiveLabelDescription = "ActiveLabelDescription", + LineSeparator = "LineSeparator", + InactiveLineSeparator = "InactiveLineSeparator", + Node = "Node", + ActiveNode = "ActiveNode", + InActiveNode = "InActiveNode", +} diff --git a/src/index.tsx b/src/index.tsx index a42f26e..d2a09f4 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,3 +1,3 @@ -import Stepper from "./stepper-component"; +import Stepper from "./stepper"; export default Stepper; diff --git a/src/node/index.ts b/src/node/index.ts new file mode 100644 index 0000000..94710a4 --- /dev/null +++ b/src/node/index.ts @@ -0,0 +1,3 @@ +import Node from "./node"; + +export default Node; diff --git a/src/node/node.tsx b/src/node/node.tsx new file mode 100644 index 0000000..a9b70ae --- /dev/null +++ b/src/node/node.tsx @@ -0,0 +1,52 @@ +import React, { FC } from "react"; +import type { INodeProps } from "./types"; +import { Elements } from "../constants"; +import whiteTick from "../assets/white-tick.svg"; +import styles from "./styles.module.scss"; + +const Node: FC = (props) => { + const { + step, + renderNode, + index, + currentStepIndex, + handleStepClick, + showCursor, + getStyles + } = props; + + return ( +
handleStepClick && handleStepClick()} + role="presentation" + id="stepper-node" + > + {(renderNode && renderNode(step, index)) + || ( + <> + {step?.completed && ( + ) + || index + 1} + + )} +
+ ); +}; + +export default Node; diff --git a/src/bubble/styles.module.scss b/src/node/styles.module.scss similarity index 84% rename from src/bubble/styles.module.scss rename to src/node/styles.module.scss index 9e18a5b..09da43e 100644 --- a/src/bubble/styles.module.scss +++ b/src/node/styles.module.scss @@ -1,8 +1,8 @@ -.eachBubble { +.eachNode { border-radius: 50%; height: 24px; width: 24px; - background: #312ec0; + background: #7b7b84; color: white; display: flex; align-items: center; @@ -11,16 +11,19 @@ font-weight: 400; font-size: 12px; line-height: 16px; - margin: 7px; + margin-top: 7px; + margin-bottom: 7px; position: relative; } - .activeStepBubble { - border: 7px solid #CBCBEF; - margin: 0; + .activeStepNode { + background: #312ec0; } - .inactiveStepBubble { + .inactiveStepNode { opacity: 0.4; } + .completedStepNode { + background: #312ec0; + } .whiteTickImg { object-fit: cover; width: 10px; diff --git a/src/node/types.d.ts b/src/node/types.d.ts new file mode 100644 index 0000000..9ac608b --- /dev/null +++ b/src/node/types.d.ts @@ -0,0 +1,13 @@ +import { ReactElement } from "react"; +import { IStep } from "../stepper/types"; +import { Elements } from "../constants"; + +export type INodeProps = { + step: IStep; + renderNode?(step: IStep, index: number): ReactElement; + index: number; + currentStepIndex?: number; + handleStepClick(): void; + showCursor: boolean; + getStyles(element: Elements): object; +}; diff --git a/src/stepper-component/index.ts b/src/stepper-component/index.ts deleted file mode 100644 index 39ac37d..0000000 --- a/src/stepper-component/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import Stepper from './stepperComponent'; - -export default Stepper; diff --git a/src/stepper-component/stepperComponent.tsx b/src/stepper-component/stepperComponent.tsx deleted file mode 100644 index 4f067ee..0000000 --- a/src/stepper-component/stepperComponent.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React, { ReactElement, FC } from 'react'; -import classes from './styles.module.scss'; -import type { IStep, IStepperProps } from './types'; -import Bubble from '../bubble'; -import { LABEL_POSITION, Elements } from '../constants'; - -const Stepper: FC = (props) => { - const { - steps, - currentStepIndex = 0, - onStepClick, - renderBubble, - styles = {}, - labelPosition = LABEL_POSITION.RIGHT - } = props; - - const getStyles = (element: Elements, step: IStep, index: number): object => { - const getElementStyle = styles[element]; - if (getElementStyle) { - return getElementStyle(step, index); - } - return {}; - }; - - return ( -
- {steps?.map((step: IStep, stepIndex: number): ReactElement => ( -
-
- onStepClick && onStepClick(step, stepIndex)} - showCursor={!!onStepClick} - renderAdornment={renderBubble} - getStyles={(element: Elements): object => getStyles(element, step, stepIndex)} - labelPosition={labelPosition} - /> - {stepIndex < steps?.length - 1 && ( -
currentStepIndex - 1 && classes.inactiveStepLineSeparator}`} - style={{ - ...((getStyles(Elements.LineSeparator, step, stepIndex)) || {}), - ...((stepIndex > currentStepIndex - 1 - && getStyles(Elements.InactiveLineSeparator, step, stepIndex)) || {}) - }} - /> - )} -
-
- ))} -
- ); -}; - -export default Stepper; \ No newline at end of file diff --git a/src/stepper-component/styles.module.scss b/src/stepper-component/styles.module.scss deleted file mode 100644 index 89d7a96..0000000 --- a/src/stepper-component/styles.module.scss +++ /dev/null @@ -1,32 +0,0 @@ -.stepperContainer { - display: flex; - width: 100%; - height: 100%; - flex-direction: column; - margin-left: 10px; - align-items: center; - .eachStep { - display: flex; - flex-direction: column; - align-items: center; - position: relative; - .bubbleLineWrapper { - display: flex; - flex-direction: column; - align-items: center; - width: fit-content; - .lineSeparator { - height: 22px; - width: 1px; - border-right: 2px solid #dfdff2; - margin: 4px 0; - } - .inactiveStepLineSeparator { - border-right: 2px dashed #dfdff2; - } - } - } -} -.cursorPointer { - cursor: pointer; -} diff --git a/src/stepper-component/types.d.ts b/src/stepper-component/types.d.ts deleted file mode 100644 index 2790645..0000000 --- a/src/stepper-component/types.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ReactElement } from "react" -import { LABEL_POSITION } from "../constants" -import { Elements } from "../constants" - -export type IStep = { - label: string, - description?: string, - status: string -} - -export type IStepperProps = { - steps: IStep[], - currentStepIndex?: number, - onStepClick?(step: IStep, stepIndex: number): void, - renderBubble?(step: IStep, stepIndex: number): ReactElement, - styles?: { [key in Elements]: IStyleFunction }, - labelPosition?: LABEL_POSITION.LEFT | LABEL_POSITION.RIGHT -} - -export type IStyleFunction = (step: IStep, stepIndex: number) => object diff --git a/src/stepper/index.ts b/src/stepper/index.ts new file mode 100644 index 0000000..808a9e7 --- /dev/null +++ b/src/stepper/index.ts @@ -0,0 +1,3 @@ +import Stepper from "./stepperComponent"; + +export default Stepper; diff --git a/src/stepper/step.tsx b/src/stepper/step.tsx new file mode 100644 index 0000000..78f06e6 --- /dev/null +++ b/src/stepper/step.tsx @@ -0,0 +1,124 @@ +import React, { useRef, useEffect, useState } from "react"; +import "./styles.scss"; +import type { IStepProps } from "../stepper/types"; +import { LABEL_POSITION, ORIENTATION } from "../constants"; +import StepContent from "./stepContent"; +import StepInfo from "./stepInfo"; + +// Each step consists of a node, a label, and connectors to the previous and next steps. +const Step: (props: IStepProps) => JSX.Element = ({ + stepperProps, + step, + index +}: IStepProps) => { + const { + steps, + currentStepIndex = 0, + styles = {}, + labelPosition = LABEL_POSITION.RIGHT, + orientation = ORIENTATION.VERTICAL, + showDescriptionsForAllSteps = false, + stepContent, + onStepClick, + renderNode + } = stepperProps; + const [nodeWidth, setNodeWidth] = useState(0); + + const isVertical = orientation === ORIENTATION.VERTICAL; + + /* isInlineLabelsAndSteps = true means label and steps are in the same axis (eg: Horizontal stepper with label direction left/right and + vertical stepper with label direction top/bottom) */ + const isInlineLabelsAndSteps = + (isVertical && + [LABEL_POSITION.TOP, LABEL_POSITION.BOTTOM].includes(labelPosition)) || + (!isVertical && + [LABEL_POSITION.LEFT, LABEL_POSITION.RIGHT].includes(labelPosition)); + + const nodeRef = useRef(null); + + useEffect(() => { + const node = nodeRef.current; + if (node) { + const width = node.getBoundingClientRect().width; + setNodeWidth(width); + } + }, [steps, nodeRef]); + + // prevConnector represents the connector line from the current step's node (nth node) to the preceding step's node (n-1 th node). + const prevConnectorClassName = `stepConnector leftConnector ${ + currentStepIndex >= index ? "activeConnector" : "" + } ${index === 0 ? "hiddenConnector" : ""}`; + + // nextConnector represents the connector line from the current step's node (nth node) to the preceding step's node (n-1 th node). + + const nextConnectorClassName = `stepConnector rightConnector ${ + currentStepIndex > index ? "activeConnector" : "" + } ${index === steps.length - 1 ? "hiddenConnector" : ""}`; + + /* middleConnector connects the current step nextConnector to (n+1th) step prevConnector, + allowing the display of descriptions or content between the two steps when necessary. */ + + const middleConnectorClassName = `middleStepConnector ${ + currentStepIndex > index ? "activeConnector" : "" + } ${index === steps.length - 1 ? "hiddenConnector" : ""}`; + + return orientation === ORIENTATION.HORIZONTAL && + labelPosition === LABEL_POSITION.TOP ? ( + + ) : ( +
+ + +
+ ); +}; + +export default Step; diff --git a/src/stepper/stepContent.tsx b/src/stepper/stepContent.tsx new file mode 100644 index 0000000..0cf958f --- /dev/null +++ b/src/stepper/stepContent.tsx @@ -0,0 +1,80 @@ +import React from "react"; +import "./styles.scss"; +import { LABEL_POSITION, Elements } from "../constants"; +import getStyles from "../utils/getStyles"; +import { IStepContentProps } from "./types"; + +const StepContent: (props: IStepContentProps) => JSX.Element = ({ + labelPosition, + isVertical, + currentStepIndex, + index, + styles, + step, + showDescriptionsForAllSteps, + middleConnectorClassName, + stepContent, + nodeWidth +}: IStepContentProps) => ( +
+ {isVertical && ( + /* In a vertical stepper, utilize an extra middle connector to dynamically adjust the length based on the height of step descriptions. + This ensures a visually balanced layout by accommodating varying content heights. */ +
+
index + ? getStyles(styles, Elements.LineSeparator, step, index) || {} + : getStyles( + styles, + Elements.InactiveLineSeparator, + step, + index + ) || {}) + }} + /> +
+ )} +
+ {(showDescriptionsForAllSteps || index === currentStepIndex) && ( +
+ {step.stepDescription} +
+ )} + {isVertical && + index === currentStepIndex && + stepContent && + stepContent(step, index)} +
+
+); + +export default StepContent; diff --git a/src/stepper/stepInfo.tsx b/src/stepper/stepInfo.tsx new file mode 100644 index 0000000..ad296bc --- /dev/null +++ b/src/stepper/stepInfo.tsx @@ -0,0 +1,126 @@ +import React from "react"; +import "./styles.scss"; +import type { IStepInfoProps } from "./types"; +import Node from "../node"; +import { LABEL_POSITION, Elements, ORIENTATION } from "../constants"; +import getStyles from "../utils/getStyles"; +import getLabelStyle from "../utils/getLabelStyle"; + +const StepInfo: (props: IStepInfoProps) => JSX.Element = ({ + orientation, + labelPosition, + isVertical, + isInlineLabelsAndSteps, + index, + currentStepIndex, + step, + showDescriptionsForAllSteps, + onStepClick, + renderNode, + styles, + nodeRef, + prevConnectorClassName, + nextConnectorClassName +}: IStepInfoProps) => ( +
+ {!isInlineLabelsAndSteps && ( +
+
+ {step.stepLabel} +
+ {(showDescriptionsForAllSteps || index === currentStepIndex) && + orientation === ORIENTATION.HORIZONTAL && + labelPosition === LABEL_POSITION.TOP && ( +
+ {step.stepDescription} +
+ )} +
+ )} +
+
= index + ? getStyles(styles, Elements.LineSeparator, step, index) || {} + : getStyles(styles, Elements.InactiveLineSeparator, step, index) || {}) + }} + /> +
+ + onStepClick && onStepClick(step, index) + } + showCursor={!!onStepClick} + renderNode={renderNode} + getStyles={(element: Elements): object => + getStyles(styles, element, step, index) + } + /> +
+ {isInlineLabelsAndSteps && ( +
+
+ {step.stepLabel} +
+
+ )} +
index + ? getStyles(styles, Elements.LineSeparator, step, index) || {} + : getStyles(styles, Elements.InactiveLineSeparator, step, index) || {}) + }} + /> +
+
+); + +export default StepInfo; diff --git a/src/stepper/stepperComponent.tsx b/src/stepper/stepperComponent.tsx new file mode 100644 index 0000000..b6a89a3 --- /dev/null +++ b/src/stepper/stepperComponent.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import "./styles.scss"; +import type { IStepperProps } from "./types"; +import { ORIENTATION } from "../constants"; +import Step from "./step"; + +const Stepper = (props: IStepperProps): JSX.Element => { + const { + steps, + currentStepIndex = 0, + orientation = ORIENTATION.VERTICAL, + stepContent + } = props; + + const isVertical = orientation === ORIENTATION.VERTICAL; + + return ( + <> +
    + {steps.map((step, index) => Step({ stepperProps: props, step, index }))} +
+ {!isVertical && // For horizontal stepper, the content is displayed below the stepper with full width + stepContent && + stepContent(steps[currentStepIndex], currentStepIndex)} + + ); +}; + +export default Stepper; diff --git a/src/stepper/styles.scss b/src/stepper/styles.scss new file mode 100644 index 0000000..990fd73 --- /dev/null +++ b/src/stepper/styles.scss @@ -0,0 +1,205 @@ +$grey-color: #e1e1e1; +$active-color: #312ec0; +$completed-color: #47aed6; + +.stepper { + margin: 0; + padding: 1em; + display: flex; + font-family: inherit; + list-style: none; +} + +.horizontalStepperWrapper { + display: flex; + flex-direction: column; +} + +.verticalStepperWrapper { + display: flex; + flex-direction: row; + align-items: center; +} + +.labelLeft { + justify-content: flex-end; +} + +.horizontalStepper { + flex-flow: row nowrap; + width: 100%; + height: fit-content; + justify-content: center; + .stepContainer { + display: flex; + flex-direction: row; + height: auto; + width: 100%; + flex-wrap: wrap; + align-items: center; + justify-content: center; + } + .stepConnector { + margin: 0; + display: flex; + flex: 1; + background-color: $grey-color; + overflow: hidden; + width: 100px; + min-width: 0; + height: 2px; + } + .activeConnector { + background-color: #312ec0; + } + .descriptionContainer { + display: flex; + justify-content: center; + } + +} + +.verticalStepper { + flex-flow: column nowrap; + width: fit-content; + height: 100%; + .stepContainer { + display: flex; + flex-direction: column; + height: 100%; + width: auto; + flex-wrap: wrap; + align-items: center; + justify-content: center; + } + .stepConnector { + margin: 0; + display: flex; + flex: 1; + background-color: $grey-color; + overflow: hidden; + height: auto; + min-height: 10px; + width: 2px; + } + .middleStepConnector { + margin: 0; + display: flex; + background-color: $grey-color; + overflow: hidden; + height: auto; + min-height: 10px; + width: 2px; + } + .activeConnector { + background-color: #312ec0; + } + .descriptionContainer { + display: flex; + } + +} + + +.hiddenConnector { + visibility: hidden; +} + +.node { + display: flex; + justify-content: center; + align-items: center; + order: 2; + padding: 3px; +} +.leftConnector { + order: 1; +} +.rightConnector { + order: 4; +} + +.labelContainer { + display: flex; + word-wrap: break-word; + justify-content: center; + order: 3; +} + +.label { + font-size: 0.9em; + white-space: pre-wrap; + word-wrap: break-word; + padding: 3px; + text-align: center; + font-weight: bold; + display: flex; + max-width: 400px; +} + +.verticalStepperInlineLabel { + display: flex; + width: 100px; + justify-content: center; +} + +.reversedLabelContainer { + order: 2; +} + +.reversedNode { + order: 3; +} + +.leftDescription { + flex-direction: row-reverse; +} +.verticalTextLeftContainer { + display: flex; + flex-direction: column; + align-items: flex-end; + width: 100%; +} + +.horizontalLabelTop { + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: center; + flex: 2 100%; +} + +.horizontalLabelBottom { + display: flex; + justify-content: center; + order: 1; +} + +.verticalLabelRight { + order: 1; +} + +.description { + color: #4e4b4b; + padding-bottom: 5px; +} + +.middleConnectorWrapper { + display: flex; + justify-content: flex-end;; +} + +.leftContentMiddleConnectorWrapper { + display: flex; + justify-content: flex-start; +} + +.verticalContentWrapper { + padding-left: 10px; + padding-right: 10px; +} + +.horizontalStepperDescription { + display: flex; + justify-content: center;; +} diff --git a/src/stepper/types.d.ts b/src/stepper/types.d.ts new file mode 100644 index 0000000..c78ee74 --- /dev/null +++ b/src/stepper/types.d.ts @@ -0,0 +1,59 @@ +import { LegacyRef, ReactElement } from "react"; +import { LABEL_POSITION, ORIENTATION } from "../constants"; +import { Elements } from "../constants"; + +export type IStep = { + stepLabel: string; + stepDescription?: string; + completed?: boolean; +}; + +export type IStepperProps = { + steps: IStep[]; + currentStepIndex?: number; + orientation?: ORIENTATION.HORIZONTAL | ORIENTATION.VERTICAL; + styles?: { [key in Elements]: IStyleFunction }; + labelPosition?: LABEL_POSITION.LEFT | LABEL_POSITION.RIGHT | LABEL_POSITION.TOP | LABEL_POSITION.BOTTOM; + showDescriptionsForAllSteps?: boolean; + stepContent?(step: IStep, stepIndex: number): ReactElement; + onStepClick?(step: IStep, stepIndex: number): void; + renderNode?(step: IStep, stepIndex: number): ReactElement; +}; + +export type IStyleFunction = (step: IStep, stepIndex: number) => object; + +export type IStepProps = { + stepperProps: IStepperProps; + step: IStep; + index: number; +} + +export type IStepInfoProps = { + orientation: ORIENTATION.HORIZONTAL | ORIENTATION.VERTICAL; + labelPosition:LABEL_POSITION.LEFT | LABEL_POSITION.RIGHT | LABEL_POSITION.TOP | LABEL_POSITION.BOTTOM; + isVertical: boolean; + isInlineLabelsAndSteps: boolean; + index: number; + currentStepIndex: number; + step: IStep; + showDescriptionsForAllSteps: boolean; + onStepClick?(step: IStep, stepIndex: number): void; + renderNode?(step: IStep, stepIndex: number): ReactElement; + styles: { [key in Elements]?: IStyleFunction }; + nodeRef: LegacyRef | undefined + prevConnectorClassName: string; + nextConnectorClassName: string; +} + +export type IStepContentProps = { + labelPosition: LABEL_POSITION.LEFT | LABEL_POSITION.RIGHT | LABEL_POSITION.TOP | LABEL_POSITION.BOTTOM; + isVertical: boolean; + currentStepIndex: number; + index: number; + styles: { [key in Elements]?: IStyleFunction }; + step: IStep + showDescriptionsForAllSteps: boolean; + middleConnectorClassName: string; + stepContent?(step: IStep, stepIndex: number): ReactElement; + nodeWidth: number; +} diff --git a/src/stories/StepperComponent.stories.tsx b/src/stories/StepperComponent.stories.tsx index c4e816b..c834956 100644 --- a/src/stories/StepperComponent.stories.tsx +++ b/src/stories/StepperComponent.stories.tsx @@ -1,51 +1,116 @@ -import React from 'react'; -import { ComponentStory, ComponentMeta } from '@storybook/react'; -import Stepper from '../stepper-component'; -import { IStep } from '../stepper-component/types'; +import React from "react"; +import { + ComponentStory, + ComponentMeta, +} from "@storybook/react"; +import Stepper from "../stepper"; export default { - title: 'Example/Stepper', - component: Stepper, - parameters: { - // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout - layout: 'fullscreen', - }, - } as ComponentMeta; - - - const Template: ComponentStory = (args) => ; - -export const VerticalStepper = Template.bind({}); -VerticalStepper.args = { - steps: [{ - label: 'Step 1', - description: 'The quick brown fox jumps over the lazy dog' + title: "Example/Stepper", + component: Stepper, + parameters: { + layout: "fullscreen", + }, +} as ComponentMeta; + +const Template: ComponentStory = (props) => ( + +); + +const steps = [ + { + stepLabel: "Step 1", + stepDescription: "The quick brown fox jumps over the lazy dog", + completed: true, }, { - label: 'Step 2', - description: 'The quick brown fox jumps over the lazy dog' + stepLabel: "Step 2", + stepDescription: "The quick brown fox jumps over the lazy dog", + completed: true, }, { - label: 'Step 3', - description: 'The quick brown fox jumps over the lazy dog' + stepLabel: "Step 3", + stepDescription: "The quick brown fox jumps over the lazy dog", + completed: false, }, { - label: 'Step 4', - description: 'The quick brown fox jumps over the lazy dog' - }], - currentStepIndex: 2, - // onStepClick: (stepIndex: number) => console.log("🚀 ~ file: StepperComponent.stories.tsx:37 ~ stepIndex", stepIndex) - // renderBubble: (step, index) => (<>), - // labelPosition: 'right', - // styles: { - // Bubble: () => ({ background: 'yellow'}), - // LineSeparator: (step: IStep, index: number) => (index === 2 ? { borderRight: '1px solid red' } : {}), - // InactiveLineSeparator: (step: IStep, index: number) => (index === 2 ? { borderRight: '1px dashed red' } : {}), - // LabelTitle: () => ({ background: 'red'}), - // ActiveLabelTitle: () => ({ background: 'green'}), - // LabelDescription: () => ({ background: 'red'}), - // ActiveLabelDescription: () => ({ background: 'green'}), - // ActiveBubble: () => ({ background: 'orange'}), - // InActiveBubble: () => ({ background: 'grey'}) - // } -}; \ No newline at end of file + stepLabel: "Step 4", + stepDescription: "The quick brown fox jumps over the lazy dog", + completed: false, + }, +]; + +export const HorizontalStepperWithLabelOnLeft = Template.bind({}); +HorizontalStepperWithLabelOnLeft.args = { + orientation: "horizontal", + labelPosition: "left", + currentStepIndex: 2, + steps, + showDescriptionsForAllSteps: false, +}; + +export const HorizontalStepperWithLabelOnRight = Template.bind({}); +HorizontalStepperWithLabelOnRight.args = { + orientation: "horizontal", + labelPosition: "right", + currentStepIndex: 2, + steps, + showDescriptionsForAllSteps: false, +}; + +export const HorizontalStepperWithLabelOnTop = Template.bind({}); +HorizontalStepperWithLabelOnTop.args = { + orientation: "horizontal", + labelPosition: "top", + currentStepIndex: 2, + steps, + showDescriptionsForAllSteps: false, + stepContent: () => { + return (
Test
) + } +}; + +export const HorizontalStepperWithLabelOnBottom = Template.bind({}); +HorizontalStepperWithLabelOnBottom.args = { + orientation: "horizontal", + labelPosition: "bottom", + currentStepIndex: 2, + steps, + showDescriptionsForAllSteps: false, +}; + +export const VerticalStepperWithLabelOnLeft = Template.bind({}); +VerticalStepperWithLabelOnLeft.args = { + orientation: "vertical", + labelPosition: "left", + currentStepIndex: 2, + steps, + showDescriptionsForAllSteps: false, +}; + +export const VerticalStepperWithLabelOnRight = Template.bind({}); +VerticalStepperWithLabelOnRight.args = { + orientation: "vertical", + labelPosition: "right", + currentStepIndex: 2, + steps, + showDescriptionsForAllSteps: false, +}; + +export const VerticalStepperWithLabelOnTop = Template.bind({}); +VerticalStepperWithLabelOnTop.args = { + orientation: "vertical", + labelPosition: "top", + currentStepIndex: 2, + steps, + showDescriptionsForAllSteps: false, +}; + +export const VerticalStepperWithLabelOnBottom = Template.bind({}); +VerticalStepperWithLabelOnBottom.args = { + orientation: "vertical", + labelPosition: "bottom", + currentStepIndex: 2, + steps, + showDescriptionsForAllSteps: false, +}; diff --git a/src/tests/stepperComponent.test.tsx b/src/tests/stepperComponent.test.tsx index 8750fb3..b878b47 100644 --- a/src/tests/stepperComponent.test.tsx +++ b/src/tests/stepperComponent.test.tsx @@ -1,70 +1,227 @@ -import React from 'react'; +import React from "react"; import { - render, - fireEvent, - queryByAttribute, - queryAllByAttribute + render, + fireEvent, + queryByAttribute, + queryAllByAttribute, } from "@testing-library/react"; -import { IStep } from '../stepper-component/types'; -import Stepper from "../stepper-component/stepperComponent"; +import { IStep } from "../stepper/types"; +import Stepper from "../stepper/stepperComponent"; +import { LABEL_POSITION, ORIENTATION } from "../constants"; + +const getById = queryByAttribute.bind(null, "id"); +const getAllById = queryAllByAttribute.bind(null, "id"); -const getById = queryByAttribute.bind(null, 'id'); -const getAllById = queryAllByAttribute.bind(null, 'id'); test("Stepper Component - Label and description", async () => { - const steps: IStep[] = [{ - label: 'Step 1', - description: 'Demo description', - status: 'completed' - }] - const dom = render() - const label = await getById(dom.container, "stepper-label-0"); - expect(label.innerHTML).toBe('Step 1'); - const description = await getById(dom.container, "stepper-desc-0"); - expect(description.innerHTML).toBe('Demo description'); + const steps: IStep[] = [ + { + stepLabel: "Step 1", + stepDescription: "Demo description", + completed: true, + }, + ]; + const dom = render(); + const label = await getById(dom.container, "step-label-0"); + expect(label.innerHTML).toBe("Step 1"); + const description = await getById(dom.container, "step-description-0"); + expect(description.innerHTML).toBe("Demo description"); }); test("Stepper Component - No description", async () => { - const steps: IStep[] = [{ - label: 'Step 1', - status: 'completed' - }]; - const dom = render() - try { - const val = await getById(dom.container, "stepper-desc-0"); - if (val === null) throw Error(); - } catch (err){ - return; + const steps: IStep[] = [ + { + stepLabel: "Step 1", + completed: true, + }, + ]; + const dom = render(); + try { + const val = await getById(dom.container, "step-description-0"); + if (val === null) { + throw Error("Description found"); } - throw Error("Description found"); -}) + } catch (err) { + return; + } +}); -test("Stepper Component - Number of steps", async () => { - const steps: IStep[] = [{ - label: 'Step 1', - status: 'completed' - },{ - label: 'Step 2', - status: 'visited' - }]; - const dom = render(); - const elements = await getAllById(dom.container, "stepper-steps"); - expect(elements?.length).toBe(2); -}) +test("Stepper Component - with multiple steps and currentStepIndex passed", async () => { + const steps: IStep[] = [ + { + stepLabel: "Step 1", + stepDescription: "Step 1 description", + completed: true, + }, + { + stepLabel: "Step 2", + stepDescription: "Step 2 description", + completed: false, + }, + { + stepLabel: "Step 3", + stepDescription: "Step 3 description", + completed: false, + }, + ]; + const dom = render(); + const elements = await getAllById(dom.container, "stepper-step"); + expect(elements?.length).toBe(3); +}); test("Stepper Component - On Click function", async () => { - const steps: IStep[] = [{ - label: 'Step 1', - description: 'Demo description', - status: 'completed' - }]; - const onClick = jest.fn(); - const dom = render( - - ) - const bubble = await getById(dom.container, "stepper-bubble"); - fireEvent.click(bubble); - expect(onClick).toBeCalled(); -}) \ No newline at end of file + const steps: IStep[] = [ + { + stepLabel: "Step 1", + stepDescription: "Demo description", + completed: true, + }, + ]; + const onClick = jest.fn(); + const dom = render(); + const node = await getById(dom.container, "stepper-node"); + fireEvent.click(node); + expect(onClick).toBeCalled(); +}); + +test("Stepper Component - customized node", async () => { + const steps: IStep[] = [ + { + stepLabel: "Step 1", + stepDescription: "Demo description", + completed: true, + }, + ]; + const renderNode = jest.fn(); + const dom = render(); + const node = await getById(dom.container, "stepper-node"); + fireEvent.click(node); + expect(renderNode).toBeCalled(); +}); + +test("Stepper Component - custom style", async () => { + const steps: IStep[] = [ + { + stepLabel: "Step 1", + stepDescription: "Demo description 1", + completed: true, + }, + { + stepLabel: "Step 2", + stepDescription: "Demo description 2", + completed: false, + }, + { + stepLabel: "Step 1", + stepDescription: "Demo description 3", + completed: false, + }, + ]; + const styles = { + LineSeparator: () => ({ + minHeight: "20px", + }), + InactiveLineSeparator: () => ({ + backgroundColor: "black", + }), + LabelDescription: () => ({ + color: "black", + }), + LabelTitle: () => ({ + color: "black", + }), + ActiveLabelTitle: () => ({ + color: "blue", + }), + ActiveLabelDescription: () => ({ + color: "red", + }), + Node: () => ({ + backgroundColor: "red", + }), + ActiveNode: () => ({ + backgroundColor: "blue", + }), + InActiveNode: () => ({ + backgroundColor: "black", + }), + }; + const renderNode = jest.fn(); + const dom = render( + + ); + const label1 = await getById(dom.container, "step-label-1"); + expect(label1.innerHTML).toBe("Step 2"); + const description1 = await getById(dom.container, "step-description-1"); + expect(description1.innerHTML).toBe("Demo description 2"); +}); + +test("Stepper Component - orientation:vertical and labelPosition: top", async () => { + const steps: IStep[] = [ + { + stepLabel: "Step 1", + stepDescription: "Demo description", + completed: true, + }, + ]; + const dom = render( + + ); + const label = await getById(dom.container, "step-label-0"); + expect(label.innerHTML).toBe("Step 1"); + const description = await getById( + dom.container, + "step-horizontal-top-description-0" + ); + expect(description.innerHTML).toBe("Demo description"); +}); + +test("Stepper Component - orientation:vertical and labelPosition: bottom", async () => { + const steps: IStep[] = [ + { + stepLabel: "Step 1", + stepDescription: "Demo description", + completed: true, + }, + ]; + const dom = render( + + ); + const label = await getById(dom.container, "step-label-0"); + expect(label.innerHTML).toBe("Step 1"); + const description = await getById(dom.container, "step-description-0"); + expect(description.innerHTML).toBe("Demo description"); +}); + +test("Stepper Component - orientation:vertical and labelPosition: bottom", async () => { + const steps: IStep[] = [ + { + stepLabel: "Step 1", + stepDescription: "Demo description", + completed: true, + }, + ]; + const dom = render( + + ); + const label = await getById(dom.container, "step-inline-label-0"); + expect(label.innerHTML).toBe("Step 1"); + const description = await getById(dom.container, "step-description-0"); + expect(description.innerHTML).toBe("Demo description"); +}); diff --git a/src/utils/getLabelStyle.ts b/src/utils/getLabelStyle.ts new file mode 100644 index 0000000..0461f19 --- /dev/null +++ b/src/utils/getLabelStyle.ts @@ -0,0 +1,12 @@ +import { LABEL_POSITION, ORIENTATION } from "../constants"; + +const getLabelStyle: (orientation?: string, labelPosition?: string) => string | undefined = (orientation, labelPosition) => { + if (orientation === ORIENTATION.HORIZONTAL) { + if (labelPosition === LABEL_POSITION.TOP) return "horizontalLabelTop"; + else if (labelPosition === LABEL_POSITION.BOTTOM) + return "horizontalLabelBottom"; + } else if (labelPosition === LABEL_POSITION.RIGHT) + return "verticalLabelRight"; +}; + +export default getLabelStyle; diff --git a/src/utils/getStyles.ts b/src/utils/getStyles.ts new file mode 100644 index 0000000..a856f02 --- /dev/null +++ b/src/utils/getStyles.ts @@ -0,0 +1,13 @@ +import { Elements } from "../constants"; +import { IStep, IStyleFunction } from "../stepper/types"; + + +const getStyles = (styles: { [key in Elements]?: IStyleFunction }, element: Elements, step: IStep, index: number): object => { + const getElementStyle = styles[element]; + if (getElementStyle) { + return getElementStyle(step, index); + } + return {}; +}; + +export default getStyles; diff --git a/tsconfig.json b/tsconfig.json index 068c8b7..9b69018 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ "noEmit": false, "declaration": true, "suppressImplicitAnyIndexErrors": true, + "ignoreDeprecations": "5.0", "allowSyntheticDefaultImports": true, "lib": ["es2018", "dom"], "moduleResolution": "node",