@campxdev/react-blueprint 0.1.23 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/.storybook/preview.tsx +0 -1
  2. package/package.json +2 -1
  3. package/src/components/DataDisplay/Avatar/Avatar.tsx +70 -0
  4. package/src/components/DataDisplay/Avatar/CircularAvatar.stories.tsx +27 -0
  5. package/src/components/DataDisplay/Avatar/SquareAvatar.stories.tsx +47 -0
  6. package/src/components/DataDisplay/SidePanel/SidePanel.stories.tsx +36 -0
  7. package/src/components/DataDisplay/SidePanel/SidePanel.tsx +9 -0
  8. package/src/components/{FeedBack → Feedback}/Spinner/Spinner.stories.tsx +2 -2
  9. package/src/components/Feedback/Tooltip/Tooltip.stories.tsx +44 -0
  10. package/src/components/Feedback/Tooltip/Tooltip.tsx +45 -0
  11. package/src/components/Icons/IconComponents/{TooltipIcon.tsx → BulbIcon.tsx} +4 -4
  12. package/src/components/Icons/IconComponents/CrossIcon.tsx +25 -0
  13. package/src/components/Icons/IconComponents/InfoIcon.tsx +1 -1
  14. package/src/components/Icons/export.ts +4 -2
  15. package/src/components/Input/Chips/Chips.stories.tsx +54 -0
  16. package/src/components/Input/Chips/Chips.tsx +44 -0
  17. package/src/components/Input/HelpButton/HelpButton.stories.tsx +21 -0
  18. package/src/components/Input/HelpButton/HelpButton.tsx +17 -0
  19. package/src/components/Input/LabelWrapper/LabelWrapper.tsx +1 -1
  20. package/src/components/Input/OtpInput/OtpInput.stories.tsx +20 -0
  21. package/src/components/Input/OtpInput/OtpInput.tsx +85 -0
  22. package/src/components/Input/RadioGroup/RadioGroup.stories.tsx +1 -3
  23. package/src/components/Input/RadioGroup/RadioGroup.tsx +2 -1
  24. package/src/components/Input/SearchBar/SearchBar.stories.tsx +51 -0
  25. package/src/components/Input/SearchBar/SearchBar.tsx +48 -0
  26. package/src/components/Input/SingleCheckBox/SIngleCheckBox.tsx +14 -1
  27. package/src/components/Input/SingleCheckBox/SingleCheckBox.stories.tsx +0 -1
  28. package/src/components/Input/SingleSelect/SingleSelect.stories.tsx +20 -1
  29. package/src/components/Input/SingleSelect/SingleSelect.tsx +86 -11
  30. package/src/components/Input/TextField/TextField.stories.tsx +20 -0
  31. package/src/components/Input/components/FetchingOptionsLoader.tsx +2 -2
  32. package/src/components/Layout/Header/AppHeader.stories.tsx +2 -1
  33. package/src/components/Layout/Header/AppHeader.tsx +6 -6
  34. package/src/components/Layout/Header/HeaderActions/HeaderActions.tsx +14 -21
  35. package/src/components/Layout/LayoutWrapper/LayoutWrapper.stories.tsx +1 -1
  36. package/src/components/Navigation/DropDownMenu/DropDownMenu.stories.tsx +3 -12
  37. package/src/components/export.ts +2 -0
  38. package/src/themes/colorTokens.ts +4 -4
  39. package/src/themes/commonTheme.ts +45 -1
  40. package/src/utils/campxAxios.ts +1 -3
  41. package/types/theme.d.ts +7 -0
  42. /package/src/components/{FeedBack → Feedback}/Spinner/Spinner.css +0 -0
  43. /package/src/components/{FeedBack → Feedback}/Spinner/Spinner.tsx +0 -0
  44. /package/src/components/{Modals → Navigation/DialogButton}/DialogButton.stories.tsx +0 -0
  45. /package/src/components/{Modals → Navigation/DialogButton}/DialogButton.tsx +0 -0
@@ -29,7 +29,6 @@ const preview: Preview = {
29
29
  decorators: [
30
30
  (Story) => {
31
31
  const isDarkMode = useDarkMode();
32
- console.log(isDarkMode);
33
32
  const theme = isDarkMode ? darkTheme : lightTheme;
34
33
  return (
35
34
  <BrowserRouter>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@campxdev/react-blueprint",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "main": "./export.ts",
5
5
  "private": false,
6
6
  "dependencies": {
@@ -20,6 +20,7 @@
20
20
  "axios": "^1.7.2",
21
21
  "framer-motion": "^11.2.9",
22
22
  "js-cookie": "^3.0.5",
23
+ "lodash": "^4.17.21",
23
24
  "pullstate": "^1.24.0",
24
25
  "react": "^18.3.1",
25
26
  "react-dom": "^18.3.1",
@@ -0,0 +1,70 @@
1
+ import {
2
+ Avatar as MuiAvatar,
3
+ AvatarProps as MuiAvatarProps,
4
+ styled,
5
+ useTheme,
6
+ } from "@mui/material";
7
+ import { Typography } from "../Typography/Typography";
8
+
9
+ export type CircularAvatarProps = {
10
+ text: string;
11
+ } & Omit<MuiAvatarProps, "sx">;
12
+
13
+ export const CircularAvatar = ({ text, ...rest }: CircularAvatarProps) => {
14
+ const stringAvatar = (text: string) => {
15
+ if (!text) return "";
16
+ return text
17
+ .split(" ")
18
+ ?.map((w) => w[0])
19
+ ?.join("")
20
+ .toUpperCase();
21
+ };
22
+ const theme = useTheme();
23
+
24
+ const StyledCircularAvatar = styled(MuiAvatar)(({ theme }) => ({
25
+ height: "42px",
26
+ width: "42px",
27
+ }));
28
+
29
+ return (
30
+ <StyledCircularAvatar variant="circular" {...rest}>
31
+ <Typography variant="subtitle3">{stringAvatar(text)}</Typography>
32
+ </StyledCircularAvatar>
33
+ );
34
+ };
35
+
36
+ export type SquareAvatarProps = {
37
+ text: string;
38
+ onlyStartLetters: boolean;
39
+ } & Omit<MuiAvatarProps, "sx">;
40
+
41
+ export const SquareAvatar = ({
42
+ onlyStartLetters = false,
43
+ text,
44
+ ...rest
45
+ }: SquareAvatarProps) => {
46
+ const stringAvatar = (text: string) => {
47
+ if (!text) return "";
48
+ return text
49
+ .split(" ")
50
+ ?.map((w) => w[0])
51
+ ?.join("")
52
+ .toUpperCase();
53
+ };
54
+
55
+ const StyledSquareAvatar = styled(MuiAvatar)(({ theme }) => ({
56
+ height: "150px",
57
+ width: "210px",
58
+ backgroundColor: theme.palette.secondary.dark,
59
+ borderRadius: "5px",
60
+ border: "none",
61
+ }));
62
+
63
+ return (
64
+ <StyledSquareAvatar variant="square" {...rest}>
65
+ <Typography variant="h6">
66
+ {onlyStartLetters ? stringAvatar(text) : text}
67
+ </Typography>
68
+ </StyledSquareAvatar>
69
+ );
70
+ };
@@ -0,0 +1,27 @@
1
+ // Import React and other necessary elements
2
+
3
+ import { Meta } from "@storybook/react";
4
+ import { CircularAvatar, CircularAvatarProps } from "./Avatar";
5
+
6
+ // Define the default export with Meta type including the component type
7
+ export default {
8
+ title: "DataDisplay/CircularAvatar",
9
+ component: CircularAvatar,
10
+ tags: ["autodocs"],
11
+ argTypes: {},
12
+ } as Meta<typeof CircularAvatar>;
13
+
14
+ // Define stories directly as objects with render function
15
+ export const withText = {
16
+ render: (args: CircularAvatarProps) => <CircularAvatar {...args} />,
17
+ args: {
18
+ text: "Sh auy",
19
+ },
20
+ };
21
+
22
+ export const withImage = {
23
+ render: (args: CircularAvatarProps) => <CircularAvatar {...args} />,
24
+ args: {
25
+ src: "https://images.unsplash.com/photo-1534528741775-53994a69daeb?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwyNzM3ODF8MHwxfGFsbHwxfHx8fHx8fHwxNjM4MjM2MDg0&ixlib=rb-1.2.1&q=80&w=400",
26
+ },
27
+ };
@@ -0,0 +1,47 @@
1
+ // Import React and other necessary elements
2
+
3
+ import { Meta } from "@storybook/react";
4
+ import { SquareAvatar, SquareAvatarProps } from "./Avatar";
5
+
6
+ // Define the default export with Meta type including the component type
7
+ export default {
8
+ title: "DataDisplay/SquareAvatar",
9
+ component: SquareAvatar,
10
+ tags: ["autodocs"],
11
+ argTypes: {
12
+ children: {
13
+ control: "text",
14
+ description: "Content inside the SquareAvatar component",
15
+ },
16
+ src: {
17
+ control: "text",
18
+ description: "Image source for the SquareAvatar component",
19
+ },
20
+ variant: {
21
+ control: {
22
+ type: "select",
23
+ options: ["circular", "rounded", "square"],
24
+ },
25
+ description: "Variant of the SquareAvatar component",
26
+ },
27
+ sx: {
28
+ control: "object",
29
+ description: "Custom styling for the SquareAvatar component",
30
+ },
31
+ },
32
+ } as Meta<typeof SquareAvatar>;
33
+
34
+ // Define stories directly as objects with render function
35
+ export const withText = {
36
+ render: (args: SquareAvatarProps) => <SquareAvatar {...args} />,
37
+ args: {
38
+ text: "SQC01",
39
+ },
40
+ };
41
+
42
+ export const withImage = {
43
+ render: (args: SquareAvatarProps) => <SquareAvatar {...args} />,
44
+ args: {
45
+ src: "https://images.unsplash.com/photo-1534528741775-53994a69daeb?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwyNzM3ODF8MHwxfGFsbHwxfHx8fHx8fHwxNjM4MjM2MDg0&ixlib=rb-1.2.1&q=80&w=400",
46
+ },
47
+ };
@@ -0,0 +1,36 @@
1
+ import { Meta } from "@storybook/react/*";
2
+ import { SidePanel, SidePanelProps } from "./SidePanel";
3
+
4
+ export default {
5
+ title: "DataDisplay/SidePanel",
6
+ component: SidePanel,
7
+ tags: ["autodocs"],
8
+ argTypes: {
9
+ children: {
10
+ control: "text",
11
+ description: "Content inside the Avatar component",
12
+ },
13
+ src: {
14
+ control: "text",
15
+ description: "Image source for the Avatar component",
16
+ },
17
+ variant: {
18
+ control: {
19
+ type: "select",
20
+ options: ["circular", "rounded", "square"],
21
+ },
22
+ description: "Variant of the Avatar component",
23
+ },
24
+ sx: {
25
+ control: "object",
26
+ description: "Custom styling for the Avatar component",
27
+ },
28
+ },
29
+ } as Meta<typeof SidePanel>;
30
+
31
+ export const withText = {
32
+ render: (args: SidePanelProps) => <SidePanel {...args} />,
33
+ args: {
34
+ children: "SS",
35
+ },
36
+ };
@@ -0,0 +1,9 @@
1
+ import { ReactNode } from "react";
2
+
3
+ export interface SidePanelProps {
4
+ children?: ReactNode;
5
+ }
6
+
7
+ export const SidePanel = ({ children }: SidePanelProps) => {
8
+ return <></>;
9
+ };
@@ -7,7 +7,7 @@ import { Spinner } from "./Spinner";
7
7
 
8
8
  // Define the default export with Meta type including the component type
9
9
  export default {
10
- title: "FeedBack/Spinner",
10
+ title: "Feedback/Spinner",
11
11
  component: Spinner,
12
12
  tags: ["autodocs"],
13
13
  argTypes: {},
@@ -17,4 +17,4 @@ export default {
17
17
  export const Primary = {
18
18
  render: (args: any) => <Spinner {...args} />,
19
19
  args: {},
20
- };
20
+ };
@@ -0,0 +1,44 @@
1
+ import { Meta, StoryObj } from "@storybook/react";
2
+ import { ToolTipContent, Tooltip } from "./Tooltip";
3
+ import { InfoIcon } from "../../Icons/IconComponents/InfoIcon";
4
+ import { Button } from "../../Input/Button/Button";
5
+
6
+ export default {
7
+ title: "Feedback/Tooltip",
8
+ component: Tooltip,
9
+ tags: ["autodocs"],
10
+ argTypes: {
11
+ icon: { control: false },
12
+ message: { control: "text" },
13
+ },
14
+ } as Meta<typeof Tooltip>;
15
+
16
+ export const IconTooltip: StoryObj<typeof Tooltip> = {
17
+ render: (args) => <Tooltip {...args} />,
18
+ args: {
19
+ title: (
20
+ <ToolTipContent message="Duis rhoncus dui venenatis consequat porttitor. Etiam aliquet congue consequat. In posuere, nunc sit amet laoreet blandit, urna sapien imperdiet lectusui." />
21
+ ),
22
+ children: <InfoIcon />,
23
+ },
24
+ };
25
+
26
+ export const TextTooltip: StoryObj<typeof Tooltip> = {
27
+ render: (args) => <Tooltip {...args} />,
28
+ args: {
29
+ title: (
30
+ <ToolTipContent message="Duis rhoncus dui venenatis consequat porttitor. Etiam aliquet congue consequat. In posuere, nunc sit amet laoreet blandit, urna sapien imperdiet lectusui." />
31
+ ),
32
+ children: <>Info</>,
33
+ },
34
+ };
35
+
36
+ export const ButtonTooltip: StoryObj<typeof Tooltip> = {
37
+ render: (args) => <Tooltip {...args} />,
38
+ args: {
39
+ title: (
40
+ <ToolTipContent message="Duis rhoncus dui venenatis consequat porttitor. Etiam aliquet congue consequat. In posuere, nunc sit amet laoreet blandit, urna sapien imperdiet lectusui." />
41
+ ),
42
+ children: <Button>Info</Button>,
43
+ },
44
+ };
@@ -0,0 +1,45 @@
1
+ import {
2
+ Stack,
3
+ Tooltip as MuiTooltip,
4
+ TooltipProps as MuiTooltipProps,
5
+ IconButton,
6
+ Button,
7
+ } from "@mui/material";
8
+ import { BulbIcon } from "../../Icons/IconComponents/BulbIcon";
9
+ import { Typography } from "../../DataDisplay/Typography/Typography";
10
+ import { InfoIcon } from "../../Icons/IconComponents/InfoIcon";
11
+ import { Children, ReactElement, useState } from "react";
12
+
13
+ export type TooltipProps = {
14
+ children: ReactElement | string;
15
+ } & MuiTooltipProps;
16
+ export type TooltipContentProps = { message: string };
17
+
18
+ export const Tooltip = ({
19
+ placement = "right",
20
+ arrow = true,
21
+ ...props
22
+ }: TooltipProps) => {
23
+ const isIconButton = Children.only(props.children).type === IconButton;
24
+ return (
25
+ <MuiTooltip {...props} placement={placement} arrow={arrow}>
26
+ {isIconButton ? (
27
+ props.children
28
+ ) : (
29
+ <IconButton>{props.children}</IconButton>
30
+ )}
31
+ </MuiTooltip>
32
+ );
33
+ };
34
+
35
+ export const ToolTipContent = ({ message }: TooltipContentProps) => {
36
+ return (
37
+ <Stack gap={1}>
38
+ <Stack direction="row" gap={1}>
39
+ <BulbIcon />
40
+ <Typography variant="subtitle3">Note:</Typography>
41
+ </Stack>
42
+ <Typography variant="caption">{message}</Typography>
43
+ </Stack>
44
+ );
45
+ };
@@ -1,14 +1,14 @@
1
1
  import { useTheme } from "@mui/material";
2
2
 
3
- export const TooltipIcon = () => {
3
+ export const BulbIcon = () => {
4
4
  const theme = useTheme();
5
- const color = theme.palette.text.primary;
5
+ const color = theme.palette.info.main;
6
6
 
7
7
  return (
8
8
  <svg
9
9
  xmlns="http://www.w3.org/2000/svg"
10
- width="20.182"
11
- height="21.03"
10
+ width="16"
11
+ height="18"
12
12
  viewBox="0 0 20.182 21.03"
13
13
  style={{
14
14
  fill: color,
@@ -0,0 +1,25 @@
1
+ import { useTheme } from "@mui/material";
2
+
3
+ export const CrossIcon = () => {
4
+ const theme = useTheme();
5
+ const color = theme.palette.text.primary;
6
+ return (
7
+ <svg
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ width="8"
10
+ height="8"
11
+ viewBox="0 0 8 8"
12
+ style={{
13
+ stroke: color,
14
+ }}
15
+ >
16
+ <path
17
+ id="cross-small_3_"
18
+ data-name="cross-small (3)"
19
+ d="M13.521,5.893h0a.636.636,0,0,0-.9,0L9.707,8.808,6.792,5.893a.636.636,0,0,0-.9,0h0a.636.636,0,0,0,0,.9L8.808,9.707,5.893,12.622a.636.636,0,0,0,0,.9h0a.636.636,0,0,0,.9,0l2.915-2.915,2.915,2.915a.636.636,0,0,0,.9,0h0a.636.636,0,0,0,0-.9L10.606,9.707l2.915-2.915A.636.636,0,0,0,13.521,5.893Z"
20
+ transform="translate(-5.707 -5.707)"
21
+ opacity="0.5"
22
+ />
23
+ </svg>
24
+ );
25
+ };
@@ -2,7 +2,7 @@ import { useTheme } from "@mui/material";
2
2
 
3
3
  export const InfoIcon = () => {
4
4
  const theme = useTheme();
5
- const color = theme.palette.text.primary;
5
+ const color = theme.palette.info.main;
6
6
 
7
7
  return (
8
8
  <svg
@@ -13,6 +13,8 @@ import { HelpIcon } from "./IconComponents/HelpIcon";
13
13
  import { HomeIcon } from "./IconComponents/HomeIcon";
14
14
  import { InfoIcon } from "./IconComponents/InfoIcon";
15
15
 
16
+ import { BulbIcon } from "./IconComponents/BulbIcon";
17
+ import { CrossIcon } from "./IconComponents/CrossIcon";
16
18
  import { InstitutionsIcon } from "./IconComponents/InstitutionsIcon";
17
19
  import { LeftIcon } from "./IconComponents/LeftIcon";
18
20
  import { LocationIcon } from "./IconComponents/LocationIcon";
@@ -22,7 +24,6 @@ import { NotificationIcon } from "./IconComponents/NotificationIcon";
22
24
  import { ProfileIcon } from "./IconComponents/ProfileIcon";
23
25
  import { RightIcon } from "./IconComponents/RightIcon";
24
26
  import { TicketsIcon } from "./IconComponents/TicketsIcon";
25
- import { TooltipIcon } from "./IconComponents/TooltipIcon";
26
27
  import { UnCheckedCheckboxIcon } from "./IconComponents/UncheckCheckBoxIcon";
27
28
  import { UnCheckedRadioIcon } from "./IconComponents/UncheckedRadioIcon";
28
29
  export const Icons = {
@@ -48,8 +49,9 @@ export const Icons = {
48
49
  CheckedCheckboxIcon,
49
50
  UnCheckedRadioIcon,
50
51
  CheckedRadioIcon,
51
- TooltipIcon,
52
+ BulbIcon,
52
53
  InfoIcon,
53
54
  LocationIcon,
54
55
  DeviceIcon,
56
+ CrossIcon,
55
57
  };
@@ -0,0 +1,54 @@
1
+ // Import React and other necessary elements
2
+ import { Meta } from "@storybook/react";
3
+ import { Chips } from "./Chips";
4
+
5
+ const sampleChips = [
6
+ {
7
+ label: "Chip1",
8
+ value: 1,
9
+ },
10
+ {
11
+ label: "Chip2",
12
+ value: 2,
13
+ },
14
+ {
15
+ label: "Chip3",
16
+ value: 3,
17
+ },
18
+ ];
19
+
20
+ // Define the default export with Meta type including the component type
21
+ export default {
22
+ title: "Input/Chips",
23
+ component: Chips,
24
+ tags: ["autodocs"],
25
+ argTypes: {},
26
+ } as Meta<typeof Chips>;
27
+
28
+ // Define stories directly as objects with render function
29
+ export const Default = {
30
+ render: () => <Chips chips={sampleChips} />,
31
+ args: {},
32
+ };
33
+
34
+ export const ClickableChip = {
35
+ render: () => (
36
+ <Chips
37
+ chips={sampleChips}
38
+ onClick={(clickedChip) => {
39
+ console.log(clickedChip);
40
+ }}
41
+ />
42
+ ),
43
+ };
44
+
45
+ export const DeletableChip = {
46
+ render: () => (
47
+ <Chips
48
+ chips={sampleChips}
49
+ onDelete={(deletedChip) => {
50
+ console.log(deletedChip);
51
+ }}
52
+ />
53
+ ),
54
+ };
@@ -0,0 +1,44 @@
1
+ import { Chip } from "@mui/material";
2
+ import { styled } from "@mui/material/styles";
3
+ import { Typography } from "../../DataDisplay/Typography/Typography";
4
+ import { Icons } from "../../export";
5
+
6
+ const ChipList = styled("ul")(({ theme }) => ({
7
+ display: "flex",
8
+ justifyContent: "center",
9
+ flexWrap: "wrap",
10
+ listStyle: "none",
11
+ }));
12
+
13
+ export type ChipsProps = {
14
+ chips: { label: string; value: any }[];
15
+ onClick?: (clickedChip: any) => void;
16
+ onDelete?: (deletedChip: any) => void;
17
+ };
18
+
19
+ export const Chips = ({ chips, onClick, onDelete }: ChipsProps) => {
20
+ return (
21
+ <ChipList>
22
+ {chips.map(({ label, value }) => (
23
+ <Chip
24
+ label={<Typography variant="body2">{label}</Typography>}
25
+ onClick={
26
+ onClick
27
+ ? () => {
28
+ onClick(value);
29
+ }
30
+ : undefined
31
+ }
32
+ onDelete={
33
+ onDelete
34
+ ? (e) => {
35
+ onDelete(value);
36
+ }
37
+ : undefined
38
+ }
39
+ deleteIcon={<Icons.CrossIcon />}
40
+ />
41
+ ))}
42
+ </ChipList>
43
+ );
44
+ };
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import { Meta } from "@storybook/react";
3
+ import HelpButton from "./HelpButton";
4
+
5
+ export default {
6
+ title: "Input/HelpButton",
7
+ component: HelpButton,
8
+ tags: ["autodocs"],
9
+ argTypes: {
10
+ href: {
11
+ control: "text",
12
+ description: "The URL the button links to",
13
+ defaultValue: "https://example.com/help",
14
+ },
15
+ },
16
+ } as Meta<typeof HelpButton>;
17
+
18
+ export const Default = (args: any) => <HelpButton {...args} />;
19
+ Default.args = {
20
+ href: "https://example.com/help",
21
+ };
@@ -0,0 +1,17 @@
1
+ // HelpButton.tsx
2
+ import React from "react";
3
+ import { IconButton } from "@mui/material";
4
+ import { Icons } from "../../export";
5
+
6
+ const HelpButton = () => {
7
+ return (
8
+ <IconButton
9
+ href={"https://campx.atlassian.net/servicedesk/customer/portal/2"}
10
+ target="_blank"
11
+ >
12
+ <Icons.HelpIcon />
13
+ </IconButton>
14
+ );
15
+ };
16
+
17
+ export default HelpButton;
@@ -16,7 +16,7 @@ export const LabelWrapper = ({
16
16
  }) => {
17
17
  const theme = useTheme();
18
18
  return (
19
- <Stack margin="15px 20px" {...containerProps} gap={1}>
19
+ <Stack margin="15px 20px" {...containerProps}>
20
20
  {typeof label === "string" ? (
21
21
  <Typography htmlFor={name} component="label" variant="label1">
22
22
  {label}
@@ -0,0 +1,20 @@
1
+ import React from "react";
2
+ import { Meta } from "@storybook/react";
3
+ import OtpInput, { OtpInputProps } from "./OtpInput";
4
+
5
+ export default {
6
+ title: "Input/OtpInput",
7
+ component: OtpInput,
8
+ tags: ["autodocs"],
9
+ argTypes: {},
10
+ } as Meta<typeof OtpInput>;
11
+
12
+ export const Default = {
13
+ render: (args: OtpInputProps) => <OtpInput {...args} />,
14
+ args: {
15
+ length: 6,
16
+ onChangeOtp: (otp: string) => {
17
+ console.log("OTP Value:", otp);
18
+ },
19
+ },
20
+ };
@@ -0,0 +1,85 @@
1
+ import React, { useState, useRef } from "react";
2
+
3
+ interface OtpInputProps {
4
+ length: number;
5
+ onChangeOtp: (otp: string) => void;
6
+ }
7
+
8
+ const OtpInput = ({ length, onChangeOtp }:OtpInputProps) => {
9
+ const [otp, setOtp] = useState<string[]>(Array(length).fill(""));
10
+ const inputRefs = useRef<HTMLInputElement[]>([]);
11
+
12
+ const handleChange = (element: HTMLInputElement, index: number) => {
13
+ const value = element.value;
14
+ if (/^[0-9]$/.test(value) || value === "") {
15
+ const newOtp = [...otp];
16
+ newOtp[index] = value;
17
+ setOtp(newOtp);
18
+ onChangeOtp(newOtp.join(""));
19
+
20
+ if (value && index < length - 1) {
21
+ inputRefs.current[index + 1].focus();
22
+ }
23
+ }
24
+ };
25
+
26
+ const handleKeyDown = (
27
+ event: React.KeyboardEvent<HTMLInputElement>,
28
+ index: number
29
+ ) => {
30
+ if (event.key === "Backspace" && !otp[index] && index > 0) {
31
+ inputRefs.current[index - 1].focus();
32
+ }
33
+ };
34
+
35
+ const handlePaste = (event: React.ClipboardEvent<HTMLInputElement>) => {
36
+ event.preventDefault();
37
+ const paste = event.clipboardData.getData("text");
38
+ const digits = paste.split("").filter((char) => /^[0-9]$/.test(char));
39
+
40
+ if (digits.length > 0) {
41
+ const newOtp = [...otp];
42
+ const startIndex = Number(event.currentTarget.dataset.index) || 0;
43
+ let focusIndex = startIndex;
44
+
45
+ for (let i = startIndex; i < length; i++) {
46
+ if (digits.length > 0) {
47
+ newOtp[i] = digits.shift()!;
48
+ focusIndex = i;
49
+ }
50
+ }
51
+
52
+ setOtp(newOtp);
53
+ onChangeOtp(newOtp.join(""));
54
+ inputRefs.current[Math.min(focusIndex + 1, length - 1)].focus();
55
+ }
56
+ };
57
+
58
+ return (
59
+ <div style={{ display: "flex", gap: "10px" }}>
60
+ {otp.map((value, index) => (
61
+ <input
62
+ className="MuiOtpInput"
63
+ key={index}
64
+ ref={(el) => (inputRefs.current[index] = el!)}
65
+ type="text"
66
+ value={value ? "•" : ""}
67
+ onChange={(e) => handleChange(e.target, index)}
68
+ onKeyDown={(e) => handleKeyDown(e, index)}
69
+ onPaste={handlePaste}
70
+ data-index={index}
71
+ maxLength={1}
72
+ style={{
73
+ width: "40px",
74
+ height: "40px",
75
+ textAlign: "center",
76
+ fontSize: "20px",
77
+ }}
78
+ />
79
+ ))}
80
+ </div>
81
+ );
82
+ };
83
+
84
+ export default OtpInput;
85
+ export type { OtpInputProps };
@@ -34,9 +34,7 @@ export const Default = {
34
34
  value: "4",
35
35
  },
36
36
  ],
37
- onChange: (event: any, value: any) => {
38
- console.log(event.target.checked, value);
39
- },
37
+ onChange: (event: any, value: any) => {},
40
38
  row: true,
41
39
  defaultValue: "3",
42
40
  },