@g4rcez/components 0.0.1 → 0.0.2

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 (72) hide show
  1. package/package.json +11 -2
  2. package/.idea/bigweld.iml +0 -12
  3. package/.idea/codeStyles/Project.xml +0 -72
  4. package/.idea/codeStyles/codeStyleConfig.xml +0 -5
  5. package/.idea/inspectionProfiles/Project_Default.xml +0 -30
  6. package/.idea/jsLibraryMappings.xml +0 -6
  7. package/.idea/modules.xml +0 -8
  8. package/.idea/prettier.xml +0 -7
  9. package/.idea/reason.xml +0 -6
  10. package/.idea/vcs.xml +0 -6
  11. package/.prettierrc.json +0 -13
  12. package/app/client-table.tsx +0 -35
  13. package/app/favicon.ico +0 -0
  14. package/app/layout.tsx +0 -39
  15. package/app/page.tsx +0 -72
  16. package/docs/README.md +0 -36
  17. package/docs/next.config.mjs +0 -4
  18. package/docs/package.json +0 -28
  19. package/docs/pnpm-lock.yaml +0 -1030
  20. package/docs/postcss.config.mjs +0 -8
  21. package/docs/public/next.svg +0 -1
  22. package/docs/public/vercel.svg +0 -1
  23. package/docs/src/app/favicon.ico +0 -0
  24. package/docs/src/app/globals.css +0 -33
  25. package/docs/src/app/layout.tsx +0 -22
  26. package/docs/src/app/page.tsx +0 -10
  27. package/docs/tailwind.config.ts +0 -15
  28. package/docs/tsconfig.json +0 -26
  29. package/next-env.d.ts +0 -5
  30. package/next.config.mjs +0 -4
  31. package/postcss.config.mjs +0 -8
  32. package/public/next.svg +0 -1
  33. package/public/vercel.svg +0 -1
  34. package/src/components/core/button.tsx +0 -91
  35. package/src/components/core/polymorph.tsx +0 -17
  36. package/src/components/display/card.tsx +0 -8
  37. package/src/components/floating/dropdown.tsx +0 -93
  38. package/src/components/floating/tooltip.tsx +0 -67
  39. package/src/components/form/autocomplete.tsx +0 -222
  40. package/src/components/form/file-upload.tsx +0 -129
  41. package/src/components/form/form.tsx +0 -28
  42. package/src/components/form/input-field.tsx +0 -105
  43. package/src/components/form/input.tsx +0 -73
  44. package/src/components/form/select.tsx +0 -58
  45. package/src/components/form/switch.tsx +0 -40
  46. package/src/components/index.ts +0 -14
  47. package/src/components/table/filter.tsx +0 -186
  48. package/src/components/table/group.tsx +0 -123
  49. package/src/components/table/index.tsx +0 -207
  50. package/src/components/table/metadata.tsx +0 -55
  51. package/src/components/table/sort.tsx +0 -141
  52. package/src/components/table/table-lib.ts +0 -130
  53. package/src/components/table/thead.tsx +0 -108
  54. package/src/hooks/use-form.ts +0 -155
  55. package/src/hooks/use-previous.ts +0 -9
  56. package/src/hooks/use-reactive.ts +0 -10
  57. package/src/index.css +0 -37
  58. package/src/index.ts +0 -6
  59. package/src/lib/dom.ts +0 -27
  60. package/src/lib/fns.ts +0 -23
  61. package/src/styles/dark.json +0 -66
  62. package/src/styles/design-tokens.ts +0 -57
  63. package/src/styles/light.json +0 -49
  64. package/src/types.ts +0 -11
  65. package/styles.config.ts +0 -42
  66. package/tailwind.config.ts +0 -11
  67. package/tsconfig.json +0 -55
  68. package/tsconfig.lib.json +0 -50
  69. package/tsconfig.lib.tsbuildinfo +0 -1
  70. package/tsconfig.tailwind.json +0 -32
  71. package/tsconfig.tsbuildinfo +0 -1
  72. package/vite.config.mts +0 -39
@@ -1,129 +0,0 @@
1
- import { FileIcon, Trash2Icon, UploadIcon } from "lucide-react";
2
- import prettyBytes from "pretty-bytes";
3
- import React, { Fragment, useEffect, useState } from "react";
4
- import { DropzoneProps, useDropzone } from "react-dropzone";
5
- import { Override } from "sidekicker";
6
- import { Button } from "~/components/core/button";
7
-
8
- type Props = Override<React.ComponentProps<"input">, DropzoneProps> & {
9
- onDeleteFile?: (file: File) => void;
10
- files?: File[];
11
- idle?: React.ReactElement;
12
- onDrop?: (file: File[]) => void;
13
- };
14
-
15
- const mime = {
16
- isImage: (file: File) => file.type.includes("image"),
17
- };
18
-
19
- const FileViewer = (props: { file: File; onDeleteFile?: (file: File) => void }) => {
20
- const [info, setInfo] = useState({ url: "", type: "", size: "" });
21
- useEffect(() => {
22
- if (mime.isImage(props.file)) {
23
- const url = URL.createObjectURL(props.file);
24
- setInfo({ url, type: "img", size: prettyBytes(props.file.size) });
25
- return () => {
26
- URL.revokeObjectURL(url);
27
- };
28
- }
29
- setInfo({ url: "", type: props.file.type, size: prettyBytes(props.file.size) });
30
- }, [props.file]);
31
-
32
- if (info.type === "img") {
33
- return (
34
- <div className="flex flex-row gap-jade-200 items-center justify-between w-full">
35
- <header className="flex flex-row gap-jade-200 items-center">
36
- <img src={info.url} className="size-jade-500 rounded-jade-xsmall" alt={`Miniatura do arquivo ${props.file.name}`} />
37
- <div className="flex flex-col">
38
- <span>{props.file.name}</span>
39
- <span>{info.size}</span>
40
- </div>
41
- </header>
42
- <Button
43
- className="isolate"
44
- type="button"
45
- theme="raw"
46
- onClick={(e) => {
47
- e.stopPropagation();
48
- props.onDeleteFile?.(props.file);
49
- }}
50
- >
51
- <Trash2Icon />
52
- </Button>
53
- </div>
54
- );
55
- }
56
- return (
57
- <div className="flex flex-row gap-jade-200 items-center justify-between w-full">
58
- <header className="flex flex-row gap-4 items-center">
59
- <FileIcon size={48} />
60
- <div className="flex flex-col text-left justify-start items-start">
61
- <span>{props.file.name}</span>
62
- <span>{info.size}</span>
63
- </div>
64
- </header>
65
- <Button
66
- className="isolate"
67
- type="button"
68
- theme="raw"
69
- onClick={(e) => {
70
- e.stopPropagation();
71
- props.onDeleteFile?.(props.file);
72
- }}
73
- >
74
- <Trash2Icon className="text-danger" />
75
- </Button>
76
- </div>
77
- );
78
- };
79
-
80
- const DefaultViewer = (props: { files: File[]; onDeleteFile?: (file: File) => void }) => {
81
- return (
82
- <ul className="w-full space-y-jade-200">
83
- {props.files.map((file) => {
84
- return <FileViewer onDeleteFile={props.onDeleteFile} key={file.name} file={file} />;
85
- })}
86
- </ul>
87
- );
88
- };
89
-
90
- const InteractiveArea = (props: { isDragActive: boolean; idle: React.ReactElement; files: File[]; onDeleteFile?: (file: File) => void }) => {
91
- if (props.isDragActive) {
92
- return <p>Solte os arquivos selecionados</p>;
93
- }
94
- if (props.files.length > 0) {
95
- return <DefaultViewer onDeleteFile={props.onDeleteFile} files={props.files} />;
96
- }
97
- return <Fragment>{props.idle}</Fragment>;
98
- };
99
-
100
- const DefaultIdle = (
101
- <div className="flex flex-col gap-4 justify-center items-center">
102
- <UploadIcon size={64} />
103
- <p>
104
- You can drag your files or{" "}
105
- <button className="text-primary underline" type="button">
106
- drag to here
107
- </button>
108
- </p>
109
- </div>
110
- );
111
-
112
- export const FileUpload = ({ idle = DefaultIdle, onDeleteFile, onDrop, ...props }: Props) => {
113
- const [files, setFiles] = useState<File[]>([]);
114
- const drop = (x: File[]) => {
115
- onDrop?.(x);
116
- setFiles(x);
117
- };
118
- const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop: drop });
119
- return (
120
- <div
121
- {...getRootProps()}
122
- data-active={props.files?.length ? props.files.length > 0 : false}
123
- className="flex text-foreground flex-col items-center justify-center border-2 rounded-lg p-6 border-card-border data-[active=true]:bg-card-background data-[active=true]:border-solid data-[active=false]:border-dashed"
124
- >
125
- <input {...getInputProps(props as any)} name={props.name} id={props.name} />
126
- <InteractiveArea onDeleteFile={onDeleteFile} isDragActive={isDragActive} idle={idle} files={props.files ?? files} />
127
- </div>
128
- );
129
- };
@@ -1,28 +0,0 @@
1
- "use client";
2
- import React from "react";
3
-
4
- const inputFields = ["INPUT", "SELECT"];
5
-
6
- export const formReset = (form?: HTMLFormElement | null) => {
7
- if (!form) return;
8
- const elements = Array.from(form.elements);
9
- elements.forEach((field) => {
10
- if (!inputFields.includes(field.tagName)) return;
11
- if (field.tagName === "INPUT") {
12
- (field as HTMLInputElement).value = (field as HTMLInputElement).defaultValue;
13
- }
14
- if (field.tagName === "SELECT") {
15
- (field as HTMLSelectElement).value = "";
16
- }
17
- field.setAttribute("data-initialized", "false");
18
- });
19
- };
20
-
21
- export const Form = (props: React.ComponentProps<"form">) => {
22
- const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
23
- e.persist();
24
- e.preventDefault();
25
- props.onSubmit?.(e);
26
- };
27
- return <form {...props} onSubmit={onSubmit} />;
28
- };
@@ -1,105 +0,0 @@
1
- "use client";
2
- import { CheckCircle, XCircle } from "lucide-react";
3
- import React, { Fragment, PropsWithChildren } from "react";
4
- import { PolymorphicProps } from "~/components/core/polymorph";
5
- import { css } from "~/lib/dom";
6
- import { Label } from "~/types";
7
-
8
- export type FeedbackProps = React.PropsWithChildren<
9
- Partial<{
10
- title: string | React.ReactElement | React.ReactNode;
11
- hideLeft?: boolean;
12
- className?: string;
13
- placeholder: string;
14
- reportStatus: boolean;
15
- }>
16
- >;
17
- export const InputFeedback = ({ reportStatus, hideLeft = false, className, children, title }: FeedbackProps) => (
18
- <div className={css("w-full justify-between", hideLeft && children === null ? "hidden" : "flex", className)}>
19
- {hideLeft ? null : (
20
- <span className="flex items-center gap-1 group-hover:text-primary group-focus-within:text-primary transition-colors group-error:text-danger">
21
- {title}
22
- {reportStatus ? (
23
- <span className="flex aspect-square h-4 w-4 items-center justify-center">
24
- <CheckCircle
25
- className="hidden aspect-square h-3 w-3 opacity-0 transition-opacity group-assert:block group-assert:text-success group-assert:opacity-100"
26
- aria-hidden="true"
27
- size={16}
28
- strokeWidth={1}
29
- absoluteStrokeWidth
30
- />
31
- <XCircle
32
- className="hidden aspect-square h-3 w-3 opacity-0 transition-opacity group-error:block group-error:opacity-100"
33
- aria-hidden="true"
34
- size={16}
35
- strokeWidth={1}
36
- absoluteStrokeWidth
37
- />
38
- </span>
39
- ) : null}
40
- </span>
41
- )}
42
- {children}
43
- </div>
44
- );
45
-
46
- export type InputFieldProps<T extends "input" | "select"> = PolymorphicProps<
47
- Partial<{
48
- error?: string;
49
- hideLeft: boolean;
50
- container: string;
51
- left: Label;
52
- optionalText: string;
53
- right: Label;
54
- rightLabel: Label;
55
- id: string;
56
- name: string;
57
- placeholder: string;
58
- }>,
59
- T
60
- >;
61
-
62
- export const InputField = <T extends "input" | "select">({
63
- optionalText = "Optional",
64
- left,
65
- rightLabel,
66
- container,
67
- right,
68
- children,
69
- error,
70
- form,
71
- id,
72
- name,
73
- title,
74
- placeholder,
75
- hideLeft,
76
- required,
77
- }: PropsWithChildren<InputFieldProps<T>>) => {
78
- const ID = id ?? name;
79
- return (
80
- <fieldset data-error={!!error} form={form} className={css("group inline-block w-full", container)}>
81
- <label
82
- form={form}
83
- htmlFor={ID}
84
- className="inline-flex w-full cursor-text flex-row flex-wrap justify-between gap-1 text-sm transition-colors empty:hidden group-error:text-danger group-hover:border-primary"
85
- >
86
- {!hideLeft && !rightLabel ? (
87
- <InputFeedback hideLeft={hideLeft} reportStatus title={title} placeholder={placeholder}>
88
- {optionalText || rightLabel ? (
89
- <Fragment>
90
- {!required ? <span className="text-opacity-70">{optionalText}</span> : null}
91
- {rightLabel ? <Fragment>{rightLabel}</Fragment> : null}
92
- </Fragment>
93
- ) : null}
94
- </InputFeedback>
95
- ) : null}
96
- <div className="relative group flex w-full flex-row flex-nowrap items-center gap-x-2 gap-y-1 rounded-md border border-input-border bg-transparent transition-colors group-focus-within:border-primary group-hover:border-primary group-error:border-danger">
97
- {left ? <span className="absolute left-0 flex flex-nowrap gap-1 whitespace-nowrap pl-2">{left}</span> : null}
98
- {children}
99
- {right ? <span className="absolute right-0 flex flex-nowrap gap-2 whitespace-nowrap pr-1">{right}</span> : null}
100
- </div>
101
- </label>
102
- <p className="mt-1 text-xs group-error:block group-error:text-danger">{error}</p>
103
- </fieldset>
104
- );
105
- };
@@ -1,73 +0,0 @@
1
- "use client";
2
- import React, { forwardRef, useEffect, useRef } from "react";
3
- import MaskInput, { TheMaskProps } from "the-mask-input";
4
- import { FeedbackProps, InputField, InputFieldProps } from "~/components/form/input-field";
5
- import { css, mergeRefs } from "~/lib/dom";
6
- import { Override } from "~/types";
7
-
8
- export type InputProps = Override<
9
- InputFieldProps<"input">,
10
- TheMaskProps &
11
- FeedbackProps & {
12
- next?: string;
13
- }
14
- >;
15
-
16
- export const Input: React.FC<InputProps> = forwardRef<HTMLInputElement, InputProps>(
17
- ({ type = "text", container, next, rightLabel, optionalText, hideLeft = false, right, left, ...props }: InputProps, ref): any => {
18
- const id = props.id ?? props.name;
19
- const inputRef = useRef<HTMLInputElement>(null);
20
-
21
- useEffect(() => {
22
- if (inputRef.current === null) return;
23
- const input = inputRef.current;
24
- const focus = () => input.setAttribute("data-initialized", "true");
25
- const goNextInputImpl = (e: Event) => {
26
- const event = e as KeyboardEvent;
27
- if (event.key === "Enter" && input.enterKeyHint === "next") {
28
- const focusNext = input.getAttribute("data-next");
29
- if (focusNext) {
30
- const el = document.getElementById(focusNext);
31
- if (el) {
32
- el.focus();
33
- return void event.preventDefault();
34
- }
35
- }
36
- }
37
- };
38
- input.addEventListener("keydown", goNextInputImpl);
39
- input.addEventListener("focus", focus);
40
- return () => {
41
- input.removeEventListener("keydown", goNextInputImpl);
42
- input.removeEventListener("focus", focus);
43
- };
44
- }, []);
45
-
46
- return (
47
- <InputField<"input">
48
- {...(props as any)}
49
- right={right}
50
- left={left}
51
- hideLeft={hideLeft}
52
- rightLabel={rightLabel}
53
- optionalText={optionalText}
54
- container={css("group inline-block w-full", container)}
55
- >
56
- <MaskInput
57
- {...props}
58
- type={type}
59
- data-next={next}
60
- ref={mergeRefs(ref, inputRef)}
61
- id={id}
62
- name={id}
63
- className={css(
64
- "input text-foreground group h-10 w-full flex-1 rounded-md bg-transparent p-2 placeholder-input-mask outline-none transition-colors group-error:text-danger group-error:placeholder-input-mask-error",
65
- !!right ? "pe-4" : "",
66
- !!left ? "ps-4" : "",
67
- props.className
68
- )}
69
- />
70
- </InputField>
71
- );
72
- }
73
- ) as any;
@@ -1,58 +0,0 @@
1
- "use client";
2
- import { ChevronDown } from "lucide-react";
3
- import React, { forwardRef, useEffect, useRef } from "react";
4
- import { InputField, InputFieldProps } from "~/components/form/input-field";
5
- import { css, mergeRefs } from "~/lib/dom";
6
- import { Override } from "~/types";
7
-
8
- export type OptionProps = Override<React.ComponentProps<"option">, { value: string }>;
9
-
10
- export type SelectProps = Override<InputFieldProps<"select">, { options: OptionProps[] }>;
11
-
12
- export const Select = forwardRef<HTMLSelectElement, SelectProps>(({ container, required = true, options, ...props }: SelectProps, ref) => {
13
- const inputRef = useRef<HTMLSelectElement>(null);
14
- const id = props.id ?? props.name;
15
-
16
- useEffect(() => {
17
- if (inputRef.current === null) return;
18
- const input = inputRef.current;
19
- const focus = () => input.setAttribute("data-initialized", "true");
20
- const change = () => input.setAttribute("data-selected", "true");
21
- input.addEventListener("focus", focus);
22
- input.addEventListener("change", change);
23
- return () => {
24
- input.removeEventListener("focus", focus);
25
- input.removeEventListener("change", change);
26
- };
27
- }, []);
28
-
29
- return (
30
- <InputField<"select">
31
- {...(props as any)}
32
- required={required}
33
- container={css("group inline-block w-full", container)}
34
- right={<ChevronDown size={20} />}
35
- >
36
- <select
37
- {...props}
38
- ref={mergeRefs(ref, inputRef)}
39
- id={id}
40
- name={id}
41
- required={required}
42
- data-selected={!!props.value || false}
43
- className={css(
44
- "input bg-transparent text-foreground select group h-10 w-full flex-1 rounded-md p-2 placeholder-input-placeholder outline-none transition-colors group-error:text-danger group-error:placeholder-input-mask-error",
45
- "data-[selected=false]:text-input-placeholder",
46
- props.className
47
- )}
48
- >
49
- <option value="" hidden disabled>
50
- {props.placeholder}
51
- </option>
52
- {options.map((option) => (
53
- <option key={`${id}-select-option-${option.value}`} {...option} children={option.label ?? option.value} />
54
- ))}
55
- </select>
56
- </InputField>
57
- );
58
- });
@@ -1,40 +0,0 @@
1
- import React, { useId, useState } from "react";
2
-
3
- type SwitchProps = Omit<React.ComponentProps<"input">, "onChange"> & { onCheck?: (nextValue: boolean) => void };
4
-
5
- export const Switch = ({ children, ...props }: SwitchProps) => {
6
- const id = useId();
7
- const [innerChecked, setInnerChecked] = useState(false);
8
- const checked = props.checked ?? innerChecked;
9
-
10
- const onCheck = (e: React.MouseEvent<HTMLButtonElement>) => {
11
- const button = e.target as HTMLButtonElement;
12
- const checked = !(button.dataset.checked === "true");
13
- setInnerChecked(checked);
14
- props?.onCheck?.(checked);
15
- };
16
-
17
- return (
18
- <div className="flex items-center">
19
- <input {...props} hidden type="checkbox" checked={checked} onChange={(e) => setInnerChecked(e.target.checked)} />
20
- <button
21
- type="button"
22
- role="switch"
23
- onClick={onCheck}
24
- aria-checked={checked}
25
- data-checked={checked}
26
- aria-labelledby={`${id}-label`}
27
- className="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent data-[checked=false]:bg-input-switch-bg data-[checked=true]:bg-primary transition-colors ease-in-out focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
28
- >
29
- <span
30
- aria-hidden="true"
31
- data-checked={checked}
32
- className="data-[checked=false]:bg-disabled data-[checked=true]:bg-input-switch pointer-events-none inline-block size-5 aspect-square data-[checked=false]translate-x-0 data-[checked=true]:translate-x-5 transform rounded-full shadow ring-0 transition ease-in-out"
33
- />
34
- </button>
35
- <span className="ml-3 text-sm" id={`${id}-label`}>
36
- <span className="font-medium text-foreground">{children}</span>
37
- </span>
38
- </div>
39
- );
40
- };
@@ -1,14 +0,0 @@
1
- export * from "./form/autocomplete";
2
- export * from "./form/input";
3
- export * from "./form/select";
4
- export * from "./form/file-upload";
5
- export * from "./form/form";
6
- export * from "./form/select";
7
- export * from "./form/switch";
8
- export * from "./form/input-field";
9
- export * from "./core/button";
10
- export * from "./core/polymorph";
11
- export * from "./display/card";
12
- export * from "./floating/dropdown";
13
- export * from "./floating/tooltip";
14
- export * from "./table/index";
@@ -1,186 +0,0 @@
1
- import { Symbols } from "linq-arrays";
2
- import { PlusIcon, SearchIcon, Trash2Icon } from "lucide-react";
3
- import React, { Fragment } from "react";
4
- import { Dropdown } from "~/components/floating/dropdown";
5
- import { Input } from "~/components/form/input";
6
- import { OptionProps, Select } from "~/components/form/select";
7
- import { uuid } from "~/lib/fns";
8
- import { Label } from "~/types";
9
- import { Col, ColType, getLabel, TableConfiguration, valueFromType } from "./table-lib";
10
-
11
- const operators = {
12
- contains: { value: "contains", label: "Contains", symbol: "includes" },
13
- is: { value: "is", label: "Is", symbol: "is" },
14
- isNot: { value: "isNot", label: "Is not", symbol: "!==" },
15
- notContains: { value: "notContains", label: "Does not contains", symbol: "notIncludes" },
16
- lessThan: { value: "lessThan", label: "Less Than", symbol: "<=" },
17
- greaterThan: { value: "greaterThan", label: "Greater than", symbol: ">=" },
18
- startsWith: { value: "startsWith", label: "Starts with", symbol: "startsWith" },
19
- endsWith: { value: "endsWith", label: "Ends with", symbol: "endsWith" },
20
- } satisfies Record<string, OptionProps & { symbol: Symbols }>;
21
-
22
- type Operator = keyof typeof operators;
23
-
24
- type Operators = (typeof operators)[Operator];
25
-
26
- const operatorOptions: Partial<Record<ColType, OptionProps[]>> = {
27
- [ColType.Text]: [operators.is, operators.isNot, operators.contains, operators.notContains, operators.startsWith, operators.endsWith],
28
- [ColType.Boolean]: [operators.is, operators.isNot],
29
- [ColType.Number]: [operators.is, operators.isNot, operators.greaterThan, operators.lessThan],
30
- };
31
-
32
- type FilterValue = string | number | string[] | boolean;
33
-
34
- export type FilterConfig<T extends {} = {}> = {
35
- id: string;
36
- label: Label;
37
- name: keyof T;
38
- type: ColType;
39
- operation: Operators;
40
- value: FilterValue;
41
- };
42
-
43
- type Props<T extends {}> = TableConfiguration<
44
- T,
45
- {
46
- cols: Col<T>[];
47
- filters: FilterConfig<T>[];
48
- set: React.Dispatch<React.SetStateAction<FilterConfig<T>[]>>;
49
- }
50
- >;
51
-
52
- export const createFilterFromCol = <T extends {}>(f: Col<T>, rest: Partial<FilterConfig<T>> = {}): FilterConfig<T> => {
53
- const name = f.id;
54
- const type = f.type ?? ColType.Text;
55
- const operatorId = operatorOptions[type]?.[0]!.value as Operator;
56
- const operation = operators[operatorId];
57
- return { id: uuid(), operation, label: getLabel(f), name, type, value: "", ...rest };
58
- };
59
-
60
- export const Filter = <T extends {}>(props: Props<T>) => {
61
- const onAddFilter = () => {
62
- const col = props.cols.at(0)!;
63
- props.set((prev) => [...prev, createFilterFromCol(col)]);
64
- };
65
-
66
- const onSelectProperty = (e: React.ChangeEvent<HTMLSelectElement>) => {
67
- const changedId = e.target.dataset.id || "";
68
- const newId = e.target.value;
69
- props.set((prev) =>
70
- prev.map((x) => {
71
- if (changedId !== x.id) return x;
72
- const col = props.cols.find((x) => newId === x.id)!;
73
- return createFilterFromCol(col, { value: "" });
74
- })
75
- );
76
- };
77
-
78
- const onSelectOperation = (e: React.ChangeEvent<HTMLSelectElement>) => {
79
- const id = e.target.dataset.id || "";
80
- const operator = e.target.value;
81
- props.set((prev) => prev.map((x) => (x.id === id ? { ...x, operation: operators[operator as Operator] } : x)));
82
- };
83
-
84
- const onDelete = (e: React.MouseEvent<HTMLButtonElement>) => {
85
- const id = e.currentTarget.dataset.id || "";
86
- console.log({ id, f: props.filters });
87
- props.set((prev) => prev.filter((x) => x.id !== id));
88
- };
89
-
90
- const onChangeValue = (e: React.ChangeEvent<HTMLInputElement>) => {
91
- const id = e.target.dataset.id || "";
92
- const value = valueFromType(e.target);
93
- props.set((prev) => prev.map((x) => (x.id === id ? { ...x, value } : x)));
94
- };
95
-
96
- return (
97
- <Fragment>
98
- <Dropdown
99
- arrow={false}
100
- title="Filters"
101
- trigger={
102
- <span className="flex items-center gap-1 proportional-nums">
103
- <SearchIcon size={14} />
104
- Filtros {props.filters.length === 0 ? "" : ` (${props.filters.length})`}
105
- </span>
106
- }
107
- >
108
- <ul className="mt-4 space-y-2">
109
- {props.filters.map((filter) => {
110
- const operators = operatorOptions[filter.type]!;
111
- return (
112
- <li key={`filter-select-${filter.id}`} className="flex flex-nowrap gap-3">
113
- <Select
114
- title="Filtro"
115
- options={props.options}
116
- placeholder="Seleciona um campo..."
117
- value={filter.name as string}
118
- data-id={filter.id}
119
- onChange={onSelectProperty}
120
- />
121
- <Select
122
- title="Tipo do filtro"
123
- data-id={filter.id}
124
- onChange={onSelectOperation}
125
- value={filter.operation.value}
126
- options={operators}
127
- placeholder="Operação..."
128
- />
129
- <Input
130
- data-id={filter.id}
131
- onChange={onChangeValue}
132
- placeholder="Buscar por..."
133
- title="Valor do filtro"
134
- type={filter.type as any}
135
- value={filter.value as string}
136
- />
137
- <div className="flex items-center justify-center mt-5">
138
- <button data-id={filter.id} type="button" onClick={onDelete}>
139
- <Trash2Icon className="text-danger" size={16} />
140
- </button>
141
- </div>
142
- </li>
143
- );
144
- })}
145
- <li>
146
- <button type="button" onClick={onAddFilter} className="text-primary flex items-center gap-1">
147
- <PlusIcon size={14} /> Adicionar novo filtro
148
- </button>
149
- </li>
150
- </ul>
151
- </Dropdown>
152
- </Fragment>
153
- );
154
- };
155
-
156
- type ColumnHeaderFilterProps<T extends {}> = {
157
- filter: FilterConfig<T>;
158
- set: React.Dispatch<React.SetStateAction<FilterConfig<T>[]>>;
159
- };
160
-
161
- export const ColumnHeaderFilter = <T extends {}>({ filter, set }: ColumnHeaderFilterProps<T>) => {
162
- const onSelectOperation = (e: React.ChangeEvent<HTMLSelectElement>) => {
163
- const operator = e.target.value;
164
- const id = e.target.dataset.id || "";
165
- set((prev) => prev.map((x) => (x.id === id ? { ...x, operation: operators[operator as Operator] } : x)));
166
- };
167
-
168
- const onChangeValue = (e: React.ChangeEvent<HTMLInputElement>) => {
169
- const id = e.target.dataset.id || "";
170
- const value = valueFromType(e.target);
171
- set((prev) => prev.map((x) => (x.id === id ? { ...x, value } : x)));
172
- };
173
-
174
- return (
175
- <div className="flex flex-nowrap items-center gap-4 py-2">
176
- <Select onChange={onSelectOperation} value={filter.operation.value} options={operatorOptions[filter.type]!} placeholder="Operation..." />
177
- <Input
178
- type={filter.type as any}
179
- data-id={filter.id}
180
- onChange={onChangeValue}
181
- placeholder="Looking for..."
182
- value={filter.value as string}
183
- />
184
- </div>
185
- );
186
- };