@rovula/ui 0.0.10 → 0.0.11
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.
- package/dist/cjs/bundle.css +14 -0
- package/dist/cjs/bundle.js +1 -1
- package/dist/cjs/bundle.js.map +1 -1
- package/dist/cjs/types/components/Button/Button.d.ts +14 -3
- package/dist/cjs/types/components/Button/Buttons.stories.d.ts +8 -6
- package/dist/cjs/types/components/DataTable/DataTable.d.ts +14 -0
- package/dist/cjs/types/components/DataTable/DataTable.stories.d.ts +19 -0
- package/dist/cjs/types/components/Dropdown/Dropdown.d.ts +29 -3
- package/dist/cjs/types/components/Dropdown/Dropdown.stories.d.ts +31 -30
- package/dist/cjs/types/components/Label/Label.stories.d.ts +1 -1
- package/dist/cjs/types/components/RadioGroup/RadioGroup.stories.d.ts +1 -1
- package/dist/cjs/types/components/Text/Text.d.ts +3 -3
- package/dist/cjs/types/components/Text/Text.stories.d.ts +3 -9
- package/dist/cjs/types/components/TextInput/TextInput.d.ts +20 -2
- package/dist/cjs/types/components/TextInput/TextInput.stories.d.ts +28 -1
- package/dist/cjs/types/components/ui/table.d.ts +10 -0
- package/dist/cjs/types/index.d.ts +3 -0
- package/dist/components/Button/Button.js +4 -3
- package/dist/components/DataTable/DataTable.js +32 -0
- package/dist/components/DataTable/DataTable.stories.js +66 -0
- package/dist/components/Dropdown/Dropdown.js +15 -5
- package/dist/components/Dropdown/Dropdown.stories.js +48 -0
- package/dist/components/Text/Text.js +3 -2
- package/dist/components/TextInput/TextInput.js +5 -7
- package/dist/components/TextInput/TextInput.stories.js +22 -0
- package/dist/components/ui/table.js +66 -0
- package/dist/esm/bundle.css +14 -0
- package/dist/esm/bundle.js +1 -1
- package/dist/esm/bundle.js.map +1 -1
- package/dist/esm/types/components/Button/Button.d.ts +14 -3
- package/dist/esm/types/components/Button/Buttons.stories.d.ts +8 -6
- package/dist/esm/types/components/DataTable/DataTable.d.ts +14 -0
- package/dist/esm/types/components/DataTable/DataTable.stories.d.ts +19 -0
- package/dist/esm/types/components/Dropdown/Dropdown.d.ts +29 -3
- package/dist/esm/types/components/Dropdown/Dropdown.stories.d.ts +31 -30
- package/dist/esm/types/components/Label/Label.stories.d.ts +1 -1
- package/dist/esm/types/components/RadioGroup/RadioGroup.stories.d.ts +1 -1
- package/dist/esm/types/components/Text/Text.d.ts +3 -3
- package/dist/esm/types/components/Text/Text.stories.d.ts +3 -9
- package/dist/esm/types/components/TextInput/TextInput.d.ts +20 -2
- package/dist/esm/types/components/TextInput/TextInput.stories.d.ts +28 -1
- package/dist/esm/types/components/ui/table.d.ts +10 -0
- package/dist/esm/types/index.d.ts +3 -0
- package/dist/index.d.ts +61 -7
- package/dist/src/theme/global.css +18 -0
- package/package.json +2 -1
- package/src/components/Button/Button.tsx +47 -39
- package/src/components/DataTable/DataTable.stories.tsx +76 -0
- package/src/components/DataTable/DataTable.tsx +105 -0
- package/src/components/Dropdown/Dropdown.stories.tsx +87 -3
- package/src/components/Dropdown/Dropdown.tsx +147 -109
- package/src/components/Text/Text.tsx +21 -19
- package/src/components/TextInput/TextInput.stories.tsx +46 -1
- package/src/components/TextInput/TextInput.tsx +7 -7
- package/src/components/ui/table.tsx +117 -0
- package/src/index.ts +5 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import React, { FC, ReactElement } from "react";
|
|
1
|
+
import React, { FC, ReactElement, forwardRef } from "react";
|
|
2
2
|
import { buttonVariants } from "./Button.styles";
|
|
3
3
|
import { cn } from "@/utils/cn";
|
|
4
|
+
import { title } from "process";
|
|
5
|
+
import { ref } from "yup";
|
|
4
6
|
|
|
5
|
-
type ButtonProps = {
|
|
7
|
+
export type ButtonProps = {
|
|
6
8
|
title?: string;
|
|
7
9
|
size?: "sm" | "md" | "lg";
|
|
8
10
|
color?:
|
|
@@ -22,43 +24,49 @@ type ButtonProps = {
|
|
|
22
24
|
endIcon?: ReactElement;
|
|
23
25
|
} & React.ButtonHTMLAttributes<HTMLButtonElement>;
|
|
24
26
|
|
|
25
|
-
const Button
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
27
|
+
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
28
|
+
(
|
|
29
|
+
{
|
|
30
|
+
size = "md",
|
|
31
|
+
color = "primary",
|
|
32
|
+
variant = "solid",
|
|
33
|
+
title,
|
|
34
|
+
children,
|
|
35
|
+
startIcon,
|
|
36
|
+
endIcon,
|
|
37
|
+
disabled = false,
|
|
38
|
+
fullwidth = false,
|
|
39
|
+
isLoading = false,
|
|
40
|
+
className,
|
|
41
|
+
...props
|
|
42
|
+
},
|
|
43
|
+
ref
|
|
44
|
+
) => {
|
|
45
|
+
const isDisabled = disabled || isLoading;
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
47
|
+
return (
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
{...props}
|
|
51
|
+
ref={ref}
|
|
52
|
+
aria-disabled={isDisabled || undefined}
|
|
53
|
+
tabIndex={isDisabled ? -1 : 0}
|
|
54
|
+
className={cn(
|
|
55
|
+
buttonVariants({ size, color, variant, disabled, fullwidth }),
|
|
56
|
+
className
|
|
57
|
+
)}
|
|
58
|
+
disabled={isDisabled}
|
|
59
|
+
>
|
|
60
|
+
{
|
|
61
|
+
<>
|
|
62
|
+
{startIcon}
|
|
63
|
+
{children || title}
|
|
64
|
+
{endIcon}
|
|
65
|
+
</>
|
|
66
|
+
}
|
|
67
|
+
</button>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
);
|
|
63
71
|
|
|
64
72
|
export default Button;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
|
|
4
|
+
import Button from "../Button/Button";
|
|
5
|
+
import { Label } from "../Label/Label";
|
|
6
|
+
import { Input } from "../Input/Input";
|
|
7
|
+
import { DataTable } from "./DataTable";
|
|
8
|
+
import { ColumnDef } from "@tanstack/react-table";
|
|
9
|
+
|
|
10
|
+
const meta = {
|
|
11
|
+
title: "Components/DataTable",
|
|
12
|
+
component: DataTable,
|
|
13
|
+
tags: ["autodocs"],
|
|
14
|
+
parameters: {
|
|
15
|
+
layout: "fullscreen",
|
|
16
|
+
},
|
|
17
|
+
decorators: [
|
|
18
|
+
(Story) => (
|
|
19
|
+
<div className="p-5 flex w-full">
|
|
20
|
+
<Story />
|
|
21
|
+
</div>
|
|
22
|
+
),
|
|
23
|
+
],
|
|
24
|
+
} satisfies Meta<typeof DataTable>;
|
|
25
|
+
|
|
26
|
+
export default meta;
|
|
27
|
+
|
|
28
|
+
const columns: ColumnDef<any>[] = [
|
|
29
|
+
{
|
|
30
|
+
accessorKey: "amount",
|
|
31
|
+
header: () => <div className="text-right">Amount</div>,
|
|
32
|
+
cell: ({ row }: any) => {
|
|
33
|
+
const amount = parseFloat(row.getValue("amount"));
|
|
34
|
+
const formatted = new Intl.NumberFormat("en-US", {
|
|
35
|
+
style: "currency",
|
|
36
|
+
currency: "USD",
|
|
37
|
+
}).format(amount);
|
|
38
|
+
|
|
39
|
+
return <div className="text-right font-medium">{formatted}</div>;
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
accessorKey: "status",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
accessorKey: "email",
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
export const Default = {
|
|
51
|
+
args: {
|
|
52
|
+
// label: "Lorem Ipsum",
|
|
53
|
+
// value: "Lorem Ipsum",
|
|
54
|
+
// fullwidth: true,
|
|
55
|
+
},
|
|
56
|
+
render: (args) => {
|
|
57
|
+
console.log("args ", args);
|
|
58
|
+
const props: typeof args = {
|
|
59
|
+
...args,
|
|
60
|
+
};
|
|
61
|
+
const data = [
|
|
62
|
+
{
|
|
63
|
+
id: "728ed52f",
|
|
64
|
+
amount: 100,
|
|
65
|
+
status: "pending",
|
|
66
|
+
email: "m@example.com",
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div className="flex flex-row gap-4 w-full">
|
|
72
|
+
<DataTable columns={columns} data={data} />
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
},
|
|
76
|
+
} satisfies StoryObj;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import {
|
|
5
|
+
ColumnDef,
|
|
6
|
+
flexRender,
|
|
7
|
+
getCoreRowModel,
|
|
8
|
+
useReactTable,
|
|
9
|
+
} from "@tanstack/react-table";
|
|
10
|
+
|
|
11
|
+
// This type is used to define the shape of our data.
|
|
12
|
+
// You can use a Zod schema here if you want.
|
|
13
|
+
export type Payment = {
|
|
14
|
+
id: string;
|
|
15
|
+
amount: number;
|
|
16
|
+
status: "pending" | "processing" | "success" | "failed";
|
|
17
|
+
email: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const columns: ColumnDef<Payment>[] = [
|
|
21
|
+
{
|
|
22
|
+
accessorKey: "status",
|
|
23
|
+
header: "Status",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
accessorKey: "email",
|
|
27
|
+
header: "Email",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
accessorKey: "amount",
|
|
31
|
+
header: "Amount",
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
import {
|
|
36
|
+
Table,
|
|
37
|
+
TableBody,
|
|
38
|
+
TableCell,
|
|
39
|
+
TableHead,
|
|
40
|
+
TableHeader,
|
|
41
|
+
TableRow,
|
|
42
|
+
} from "@/components/ui/table";
|
|
43
|
+
|
|
44
|
+
interface DataTableProps<TData, TValue> {
|
|
45
|
+
columns: ColumnDef<TData, TValue>[];
|
|
46
|
+
data: TData[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function DataTable<TData, TValue>({
|
|
50
|
+
columns,
|
|
51
|
+
data,
|
|
52
|
+
}: DataTableProps<TData, TValue>) {
|
|
53
|
+
const table = useReactTable({
|
|
54
|
+
data,
|
|
55
|
+
columns,
|
|
56
|
+
getCoreRowModel: getCoreRowModel(),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className="rounded-md border">
|
|
61
|
+
<Table>
|
|
62
|
+
<TableHeader>
|
|
63
|
+
{table.getHeaderGroups().map((headerGroup) => (
|
|
64
|
+
<TableRow key={headerGroup.id}>
|
|
65
|
+
{headerGroup.headers.map((header) => {
|
|
66
|
+
return (
|
|
67
|
+
<TableHead key={header.id}>
|
|
68
|
+
{header.isPlaceholder
|
|
69
|
+
? null
|
|
70
|
+
: flexRender(
|
|
71
|
+
header.column.columnDef.header,
|
|
72
|
+
header.getContext()
|
|
73
|
+
)}
|
|
74
|
+
</TableHead>
|
|
75
|
+
);
|
|
76
|
+
})}
|
|
77
|
+
</TableRow>
|
|
78
|
+
))}
|
|
79
|
+
</TableHeader>
|
|
80
|
+
<TableBody>
|
|
81
|
+
{table.getRowModel().rows?.length ? (
|
|
82
|
+
table.getRowModel().rows.map((row) => (
|
|
83
|
+
<TableRow
|
|
84
|
+
key={row.id}
|
|
85
|
+
data-state={row.getIsSelected() && "selected"}
|
|
86
|
+
>
|
|
87
|
+
{row.getVisibleCells().map((cell) => (
|
|
88
|
+
<TableCell key={cell.id}>
|
|
89
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
90
|
+
</TableCell>
|
|
91
|
+
))}
|
|
92
|
+
</TableRow>
|
|
93
|
+
))
|
|
94
|
+
) : (
|
|
95
|
+
<TableRow>
|
|
96
|
+
<TableCell colSpan={columns.length} className="h-24 text-center">
|
|
97
|
+
No results.
|
|
98
|
+
</TableCell>
|
|
99
|
+
</TableRow>
|
|
100
|
+
)}
|
|
101
|
+
</TableBody>
|
|
102
|
+
</Table>
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useRef } from "react";
|
|
2
2
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
-
import Dropdown from "./Dropdown";
|
|
3
|
+
import Dropdown, { Options } from "./Dropdown";
|
|
4
|
+
import Button from "../Button/Button";
|
|
5
|
+
import { cn } from "@/utils/cn";
|
|
4
6
|
|
|
5
7
|
// More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction
|
|
6
8
|
const meta = {
|
|
@@ -22,7 +24,7 @@ const meta = {
|
|
|
22
24
|
|
|
23
25
|
export default meta;
|
|
24
26
|
|
|
25
|
-
const options = new Array(100).fill("").map((__, index) => ({
|
|
27
|
+
const options: Options[] = new Array(100).fill("").map((__, index) => ({
|
|
26
28
|
value: `option${index + 1}`,
|
|
27
29
|
label: `Option ${index + 1}`,
|
|
28
30
|
}));
|
|
@@ -47,3 +49,85 @@ export const Default = {
|
|
|
47
49
|
);
|
|
48
50
|
},
|
|
49
51
|
} satisfies StoryObj;
|
|
52
|
+
|
|
53
|
+
const DropdownWithRef = (props: any) => {
|
|
54
|
+
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<Dropdown
|
|
58
|
+
id="1"
|
|
59
|
+
size="lg"
|
|
60
|
+
{...props}
|
|
61
|
+
ref={inputRef}
|
|
62
|
+
labelClassName="peer-focus:bg-red-500"
|
|
63
|
+
onKeyDown={(e) => {
|
|
64
|
+
if (e.code === "Enter") {
|
|
65
|
+
inputRef.current?.blur?.();
|
|
66
|
+
}
|
|
67
|
+
}}
|
|
68
|
+
/>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const WithRef = {
|
|
73
|
+
args: {
|
|
74
|
+
label: "Choose an option:",
|
|
75
|
+
fullwidth: true,
|
|
76
|
+
options,
|
|
77
|
+
filterMode: true,
|
|
78
|
+
},
|
|
79
|
+
render: (args) => {
|
|
80
|
+
console.log("args ", args);
|
|
81
|
+
const props: typeof args = {
|
|
82
|
+
...args,
|
|
83
|
+
};
|
|
84
|
+
return (
|
|
85
|
+
<div className="flex flex-row gap-4 w-full">
|
|
86
|
+
<DropdownWithRef id="1" size="lg" options={options} {...args} />\
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
},
|
|
90
|
+
} satisfies StoryObj;
|
|
91
|
+
|
|
92
|
+
const customOptions: Options[] = new Array(100).fill("").map((__, index) => ({
|
|
93
|
+
value: `option${index + 1}`,
|
|
94
|
+
label: `Option ${index + 1}`,
|
|
95
|
+
renderLabel(config) {
|
|
96
|
+
return (
|
|
97
|
+
<div
|
|
98
|
+
className={cn(config, "w-full flex justify-between")}
|
|
99
|
+
onMouseDown={config.handleOnClick}
|
|
100
|
+
>
|
|
101
|
+
<span>Test custom</span>
|
|
102
|
+
<Button
|
|
103
|
+
onMouseDown={(e) => {
|
|
104
|
+
// e.stopPropagation();
|
|
105
|
+
alert("SSS");
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
Add action
|
|
109
|
+
</Button>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
},
|
|
113
|
+
}));
|
|
114
|
+
|
|
115
|
+
export const CustomOption = {
|
|
116
|
+
args: {
|
|
117
|
+
label: "Choose an option:",
|
|
118
|
+
fullwidth: true,
|
|
119
|
+
options: customOptions,
|
|
120
|
+
filterMode: true,
|
|
121
|
+
},
|
|
122
|
+
render: (args) => {
|
|
123
|
+
console.log("args ", args);
|
|
124
|
+
const props: typeof args = {
|
|
125
|
+
...args,
|
|
126
|
+
};
|
|
127
|
+
return (
|
|
128
|
+
<div className="flex flex-row gap-4 w-full">
|
|
129
|
+
<DropdownWithRef id="1" size="lg" options={options} {...args} />\
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
},
|
|
133
|
+
} satisfies StoryObj;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
Fragment,
|
|
3
|
+
ReactNode,
|
|
4
|
+
forwardRef,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
2
10
|
|
|
3
11
|
import TextInput, { InputProps } from "../TextInput/TextInput";
|
|
4
12
|
import {
|
|
@@ -9,12 +17,20 @@ import {
|
|
|
9
17
|
|
|
10
18
|
import { ChevronDownIcon } from "@heroicons/react/16/solid";
|
|
11
19
|
|
|
12
|
-
type
|
|
20
|
+
type RenderLabelCallbackArg = {
|
|
13
21
|
value: string;
|
|
14
22
|
label: string;
|
|
23
|
+
handleOnClick: () => void;
|
|
24
|
+
className: string;
|
|
15
25
|
};
|
|
16
26
|
|
|
17
|
-
type
|
|
27
|
+
export type Options = {
|
|
28
|
+
value: string;
|
|
29
|
+
label: string;
|
|
30
|
+
renderLabel?: (config: RenderLabelCallbackArg) => ReactNode;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type DropdownProps = {
|
|
18
34
|
id?: string;
|
|
19
35
|
label?: string;
|
|
20
36
|
size?: "sm" | "md" | "lg";
|
|
@@ -34,118 +50,140 @@ type DropdownProps = {
|
|
|
34
50
|
onSelect?: (value: Options) => void;
|
|
35
51
|
} & Omit<InputProps, "value">;
|
|
36
52
|
|
|
37
|
-
const Dropdown = (
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
>(null);
|
|
62
|
-
const [textValue, setTextValue] = useState("");
|
|
53
|
+
const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
|
|
54
|
+
(
|
|
55
|
+
{
|
|
56
|
+
id,
|
|
57
|
+
options,
|
|
58
|
+
value,
|
|
59
|
+
label,
|
|
60
|
+
size = "md",
|
|
61
|
+
rounded = "normal",
|
|
62
|
+
variant = "outline",
|
|
63
|
+
helperText,
|
|
64
|
+
errorMessage,
|
|
65
|
+
fullwidth = true,
|
|
66
|
+
disabled = false,
|
|
67
|
+
error = false,
|
|
68
|
+
filterMode = false,
|
|
69
|
+
required = true,
|
|
70
|
+
onChangeText,
|
|
71
|
+
onSelect,
|
|
72
|
+
...props
|
|
73
|
+
},
|
|
74
|
+
ref
|
|
75
|
+
) => {
|
|
76
|
+
const _id = id || `${label}-select`;
|
|
63
77
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
78
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
79
|
+
const [selectedOption, setSelectedOption] = useState<
|
|
80
|
+
Options | null | undefined
|
|
81
|
+
>(null);
|
|
82
|
+
const [textValue, setTextValue] = useState("");
|
|
69
83
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
},
|
|
75
|
-
[onChangeText]
|
|
76
|
-
);
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (value && !selectedOption) {
|
|
86
|
+
setSelectedOption(value);
|
|
87
|
+
}
|
|
88
|
+
}, [value, selectedOption]);
|
|
77
89
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
);
|
|
90
|
+
const handleOnChangeText = useCallback(
|
|
91
|
+
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
92
|
+
onChangeText?.(event);
|
|
93
|
+
setTextValue(event.target.value);
|
|
94
|
+
},
|
|
95
|
+
[onChangeText]
|
|
96
|
+
);
|
|
86
97
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
98
|
+
const handleOptionClick = useCallback(
|
|
99
|
+
(option: Options) => {
|
|
100
|
+
setSelectedOption(option);
|
|
101
|
+
setTextValue(option.label);
|
|
102
|
+
onSelect?.(option);
|
|
103
|
+
},
|
|
104
|
+
[onSelect]
|
|
92
105
|
);
|
|
93
|
-
}, [options, filterMode, textValue]);
|
|
94
106
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
selectedOption?.value === option.value ? " bg-gray-200" : ""
|
|
103
|
-
}`}
|
|
104
|
-
>
|
|
105
|
-
{option.label}
|
|
106
|
-
</li>
|
|
107
|
-
))}
|
|
108
|
-
{optionsFiltered.length === 0 && (
|
|
109
|
-
<li className="px-4 py-14 text-center text-input-text">Not found</li>
|
|
110
|
-
)}
|
|
111
|
-
</ul>
|
|
112
|
-
);
|
|
107
|
+
const optionsFiltered = useMemo(() => {
|
|
108
|
+
return options.filter(
|
|
109
|
+
(option) =>
|
|
110
|
+
!filterMode ||
|
|
111
|
+
option.label?.toLowerCase().includes(textValue?.toLowerCase())
|
|
112
|
+
);
|
|
113
|
+
}, [options, filterMode, textValue]);
|
|
113
114
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
115
|
+
const renderOptions = () => (
|
|
116
|
+
<ul className="absolute mt-1 w-full bg-white border border-gray-300 rounded-md shadow-md z-10 max-h-60 overflow-y-auto">
|
|
117
|
+
{optionsFiltered.map((option) => {
|
|
118
|
+
if (option.renderLabel) {
|
|
119
|
+
return (
|
|
120
|
+
<Fragment key={option.value}>
|
|
121
|
+
{option.renderLabel({
|
|
122
|
+
value: option.value,
|
|
123
|
+
label: option.label,
|
|
124
|
+
handleOnClick: () => handleOptionClick(option),
|
|
125
|
+
className: `px-4 py-2 hover:bg-gray-100 cursor-pointer ${
|
|
126
|
+
selectedOption?.value === option.value ? " bg-gray-200" : ""
|
|
127
|
+
}`,
|
|
128
|
+
})}
|
|
129
|
+
</Fragment>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
return (
|
|
133
|
+
<li
|
|
134
|
+
key={option.value}
|
|
135
|
+
onMouseDown={() => handleOptionClick(option)}
|
|
136
|
+
className={`px-4 py-2 hover:bg-gray-100 cursor-pointer ${
|
|
137
|
+
selectedOption?.value === option.value ? " bg-gray-200" : ""
|
|
138
|
+
}`}
|
|
139
|
+
>
|
|
140
|
+
{option.label}
|
|
141
|
+
</li>
|
|
142
|
+
);
|
|
143
|
+
})}
|
|
144
|
+
{optionsFiltered.length === 0 && (
|
|
145
|
+
<li className="px-4 py-14 text-center text-input-text">Not found</li>
|
|
146
|
+
)}
|
|
147
|
+
</ul>
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div className={`relative ${fullwidth ? "w-full" : ""}`}>
|
|
152
|
+
<TextInput
|
|
153
|
+
{...props}
|
|
154
|
+
ref={ref}
|
|
155
|
+
readOnly={!filterMode}
|
|
156
|
+
value={textValue}
|
|
157
|
+
onChange={handleOnChangeText}
|
|
158
|
+
label={label}
|
|
159
|
+
placeholder=" "
|
|
160
|
+
type="text"
|
|
161
|
+
rounded={rounded}
|
|
162
|
+
variant={variant}
|
|
163
|
+
helperText={helperText}
|
|
164
|
+
errorMessage={errorMessage}
|
|
165
|
+
fullwidth={fullwidth}
|
|
166
|
+
error={error}
|
|
167
|
+
required={required}
|
|
168
|
+
id={_id}
|
|
169
|
+
disabled={disabled}
|
|
170
|
+
hasClearIcon={false}
|
|
171
|
+
size={size}
|
|
172
|
+
className={customInputVariant({ size })}
|
|
173
|
+
onFocus={() => setIsFocused(true)}
|
|
174
|
+
onBlur={() => setIsFocused(false)}
|
|
175
|
+
endIcon={
|
|
176
|
+
<div className={iconWrapperVariant({ size })}>
|
|
177
|
+
<ChevronDownIcon
|
|
178
|
+
className={dropdownIconVariant({ size, isFocus: isFocused })}
|
|
179
|
+
/>
|
|
180
|
+
</div>
|
|
181
|
+
}
|
|
182
|
+
/>
|
|
183
|
+
{isFocused && renderOptions()}
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
);
|
|
150
188
|
|
|
151
189
|
export default Dropdown;
|